40282c6a3c954ad2b429fe476852811feef47fd7
configuration/syncEC2ElbLogs
| ... | ... | @@ -0,0 +1,2 @@ |
| 1 | +#!/bin/sh |
|
| 2 | +/usr/bin/s3cmd -c /root/.s3cfg sync s3://sapsailing-access-logs/elb-access-logs/ /var/log/old/elb-access-logs/ >/var/log/last-elb-access-logs-sync.out 2>/var/log/last-elb-access-logs-sync.err |
configuration/unique_ips_per_referrer
| ... | ... | @@ -16,6 +16,8 @@ mkdir -p "$STATS" |
| 16 | 16 | # A sample line: |
| 17 | 17 | # 505Worlds2012.sapsailing.com 66.249.73.53 - - [12/Jan/2014:03:33:11 +0000] "GET /robots.txt HTTP/1.1" 404 238 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" |
| 18 | 18 | |
| 19 | +echo Starting "$0" at `date` on file set "$*" |
|
| 20 | + |
|
| 19 | 21 | for i in $*; do |
| 20 | 22 | grep -q "^$i\$" $VISITED_FILES |
| 21 | 23 | if [ "$?" = "0" ]; then |
| ... | ... | @@ -50,4 +52,4 @@ for month in `cat $MONTHS`; do |
| 50 | 52 | done | tee $STATS/unique-ips-days-useragents-per-event-$month |
| 51 | 53 | echo "Wrote per-month results to $STATS/unique-ips-days-useragents-per-event-$month" |
| 52 | 54 | done |
| 53 | -echo "Done." |
|
| 55 | +echo "Done at `date`." |
configuration/unique_ips_per_referrer_generate_month_results_only
| ... | ... | @@ -0,0 +1,16 @@ |
| 1 | +#!/bin/bash |
|
| 2 | + |
|
| 3 | +CACHE=/var/log/old/cache/unique-ips-per-referrer |
|
| 4 | +VISITED_FILES=$CACHE/visited |
|
| 5 | +STATS=$CACHE/stats |
|
| 6 | +MONTHS=$STATS/months |
|
| 7 | + |
|
| 8 | +for month in `cat $MONTHS`; do |
|
| 9 | + for i in ${STATS}/*.ips; do |
|
| 10 | + echo -n "`basename $i .ips` " |
|
| 11 | + cat $i | grep "^[^ ]* [0-9]*/$month .*" | sort -u | wc | awk '{ print $1; }' |
|
| 12 | + # TODO consider producing an AWStats configuration file for each virtual host name and splitting all new logs into virtual host name-specific log files |
|
| 13 | + done | tee $STATS/unique-ips-days-useragents-per-event-`echo $month | tr / -` |
|
| 14 | + echo "Wrote per-month results to $STATS/unique-ips-days-useragents-per-event-$month" |
|
| 15 | +done |
|
| 16 | +echo "Done." |
configuration/unique_ips_per_referrer_generate_results_only
| ... | ... | @@ -0,0 +1,26 @@ |
| 1 | +#!/bin/bash |
|
| 2 | + |
|
| 3 | +CACHE=/var/log/old/cache/unique-ips-per-referrer |
|
| 4 | +VISITED_FILES=$CACHE/visited |
|
| 5 | +STATS=$CACHE/stats |
|
| 6 | +MONTHS=$STATS/months |
|
| 7 | + |
|
| 8 | +for i in ${STATS}/*.ips; do |
|
| 9 | + echo -n "`basename $i .ips` " |
|
| 10 | + cat $i | sort -u | wc | awk '{ print $1; }' |
|
| 11 | + # TODO consider producing an AWStats configuration file for each virtual host name and splitting all new logs into virtual host name-specific log files |
|
| 12 | +done | tee $STATS/unique-ips-days-useragents-per-event |
|
| 13 | +echo "Wrote total results to $STATS/unique-ips-days-useragents-per-event" |
|
| 14 | + |
|
| 15 | + |
|
| 16 | +# Now filter and group by month |
|
| 17 | +cat ${STATS}/*.ips | awk '{ print $2; }' | sed -e 's/^[0-9]*\///' | sort -u >$MONTHS |
|
| 18 | +for month in `cat $MONTHS`; do |
|
| 19 | + for i in ${STATS}/*.ips; do |
|
| 20 | + echo -n "`basename $i .ips` " |
|
| 21 | + cat $i | grep "^[^ ]* [0-9]*/$month .*" | sort -u | wc | awk '{ print $1; }' |
|
| 22 | + # TODO consider producing an AWStats configuration file for each virtual host name and splitting all new logs into virtual host name-specific log files |
|
| 23 | + done | tee $STATS/unique-ips-days-useragents-per-event-`echo $month | tr / -` |
|
| 24 | + echo "Wrote per-month results to $STATS/unique-ips-days-useragents-per-event-$month" |
|
| 25 | +done |
|
| 26 | +echo "Done." |
java/com.sap.sailing.autoload/src/com/sap/sailing/autoload/TracTrac.java
| ... | ... | @@ -94,8 +94,8 @@ public class TracTrac { |
| 94 | 94 | new MillisecondsTimePoint(record.getTrackingStartTime().asMillis()), |
| 95 | 95 | new MillisecondsTimePoint(record.getTrackingEndTime().asMillis()), |
| 96 | 96 | raceLogStore, regattaLogStore, |
| 97 | - RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS, /* simulateWithStartTimeNow */ |
|
| 98 | - false, /*useTracTracMarkPassings*/ false, |
|
| 97 | + RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS, /* offsetToStartTimeOfSimulatedRace */ |
|
| 98 | + null, /*useTracTracMarkPassings*/ false, |
|
| 99 | 99 | /* tracTracUsername */"", /* tracTracPassword */"", record.getRaceStatus(), |
| 100 | 100 | record.getRaceVisibility()); |
| 101 | 101 | } else { |
java/com.sap.sailing.dashboards.gwt/pom.xml
| ... | ... | @@ -11,19 +11,6 @@ |
| 11 | 11 | <artifactId>com.sap.sailing.dashboards.gwt</artifactId> |
| 12 | 12 | <packaging>eclipse-plugin</packaging> |
| 13 | 13 | |
| 14 | - <repositories> |
|
| 15 | - <repository> |
|
| 16 | - <id>sonatype</id> |
|
| 17 | - <url>http://oss.sonatype.org/content/repositories/snapshots</url> |
|
| 18 | - <snapshots> |
|
| 19 | - <enabled>true</enabled> |
|
| 20 | - </snapshots> |
|
| 21 | - <releases> |
|
| 22 | - <enabled>false</enabled> |
|
| 23 | - </releases> |
|
| 24 | - </repository> |
|
| 25 | - </repositories> |
|
| 26 | - |
|
| 27 | 14 | <dependencies> |
| 28 | 15 | <dependency> |
| 29 | 16 | <groupId>javax.validation</groupId> |
java/com.sap.sailing.domain.common/pom.xml
| ... | ... | @@ -1,40 +1,40 @@ |
| 1 | -<?xml version="1.0" encoding="UTF-8"?>
|
|
| 2 | -<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
|
| 3 | - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
| 4 | - <modelVersion>4.0.0</modelVersion>
|
|
| 5 | - <parent>
|
|
| 6 | - <artifactId>root</artifactId>
|
|
| 7 | - <groupId>com.sap.sailing</groupId>
|
|
| 8 | - <version>1.0.0-SNAPSHOT</version>
|
|
| 9 | - </parent>
|
|
| 10 | - <artifactId>com.sap.sailing.domain.common</artifactId>
|
|
| 11 | - <packaging>eclipse-plugin</packaging>
|
|
| 12 | -
|
|
| 13 | - <build>
|
|
| 14 | - <plugins>
|
|
| 15 | - <plugin>
|
|
| 16 | - <groupId>org.eclipse.tycho</groupId>
|
|
| 17 | - <artifactId>tycho-compiler-plugin</artifactId>
|
|
| 18 | - <version>${tycho-version}</version>
|
|
| 19 | - <configuration>
|
|
| 20 | - <source>1.7</source>
|
|
| 21 | - <target>1.7</target>
|
|
| 22 | - </configuration>
|
|
| 23 | - </plugin>
|
|
| 24 | - <plugin>
|
|
| 25 | - <groupId>org.eclipse.tycho</groupId>
|
|
| 26 | - <artifactId>tycho-source-plugin</artifactId>
|
|
| 27 | - <version>${tycho-version}</version>
|
|
| 28 | - <executions>
|
|
| 29 | - <execution>
|
|
| 30 | - <id>plugin-source</id>
|
|
| 31 | - <phase>generate-sources</phase>
|
|
| 32 | - <goals>
|
|
| 33 | - <goal>plugin-source</goal>
|
|
| 34 | - </goals>
|
|
| 35 | - </execution>
|
|
| 36 | - </executions>
|
|
| 37 | - </plugin>
|
|
| 38 | - </plugins>
|
|
| 39 | - </build>
|
|
| 40 | -</project>
|
|
| 1 | +<?xml version="1.0" encoding="UTF-8"?> |
|
| 2 | +<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" |
|
| 3 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
|
| 4 | + <modelVersion>4.0.0</modelVersion> |
|
| 5 | + <parent> |
|
| 6 | + <artifactId>root</artifactId> |
|
| 7 | + <groupId>com.sap.sailing</groupId> |
|
| 8 | + <version>1.0.0-SNAPSHOT</version> |
|
| 9 | + </parent> |
|
| 10 | + <artifactId>com.sap.sailing.domain.common</artifactId> |
|
| 11 | + <packaging>eclipse-plugin</packaging> |
|
| 12 | + |
|
| 13 | + <build> |
|
| 14 | + <plugins> |
|
| 15 | + <plugin> |
|
| 16 | + <groupId>org.eclipse.tycho</groupId> |
|
| 17 | + <artifactId>tycho-compiler-plugin</artifactId> |
|
| 18 | + <version>${tycho-version}</version> |
|
| 19 | + <configuration> |
|
| 20 | + <source>1.7</source> |
|
| 21 | + <target>1.7</target> |
|
| 22 | + </configuration> |
|
| 23 | + </plugin> |
|
| 24 | + <plugin> |
|
| 25 | + <groupId>org.eclipse.tycho</groupId> |
|
| 26 | + <artifactId>tycho-source-plugin</artifactId> |
|
| 27 | + <version>${tycho-version}</version> |
|
| 28 | + <executions> |
|
| 29 | + <execution> |
|
| 30 | + <id>plugin-source</id> |
|
| 31 | + <phase>generate-sources</phase> |
|
| 32 | + <goals> |
|
| 33 | + <goal>plugin-source</goal> |
|
| 34 | + </goals> |
|
| 35 | + </execution> |
|
| 36 | + </executions> |
|
| 37 | + </plugin> |
|
| 38 | + </plugins> |
|
| 39 | + </build> |
|
| 40 | +</project> |
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractSpeedImpl.java
| ... | ... | @@ -21,7 +21,7 @@ public abstract class AbstractSpeedImpl implements Speed { |
| 21 | 21 | |
| 22 | 22 | @Override
|
| 23 | 23 | public Duration getDuration(Distance distance) {
|
| 24 | - return new MillisecondsDurationImpl((long) (1000 * distance.getMeters() / getMetersPerSecond()));
|
|
| 24 | + return distance == null ? null : new MillisecondsDurationImpl((long) (1000 * distance.getMeters() / getMetersPerSecond()));
|
|
| 25 | 25 | }
|
| 26 | 26 | |
| 27 | 27 | @Override
|
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/AbstractLeaderboardDTO.java
| ... | ... | @@ -1,430 +1,433 @@ |
| 1 | -package com.sap.sailing.domain.common.dto;
|
|
| 2 | -
|
|
| 3 | -import java.io.Serializable;
|
|
| 4 | -import java.util.ArrayList;
|
|
| 5 | -import java.util.Arrays;
|
|
| 6 | -import java.util.Date;
|
|
| 7 | -import java.util.HashSet;
|
|
| 8 | -import java.util.List;
|
|
| 9 | -import java.util.Map;
|
|
| 10 | -import java.util.Set;
|
|
| 11 | -import java.util.UUID;
|
|
| 12 | -
|
|
| 13 | -import com.sap.sailing.domain.common.LeaderboardType;
|
|
| 14 | -import com.sap.sailing.domain.common.RaceIdentifier;
|
|
| 15 | -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
|
| 16 | -import com.sap.sailing.domain.common.ScoringSchemeType;
|
|
| 17 | -import com.sap.sse.common.Util;
|
|
| 18 | -
|
|
| 19 | -public abstract class AbstractLeaderboardDTO implements Serializable {
|
|
| 20 | - private static final long serialVersionUID = -205106531931903527L;
|
|
| 21 | -
|
|
| 22 | - public String name;
|
|
| 23 | -
|
|
| 24 | - private List<RaceColumnDTO> races;
|
|
| 25 | - public Map<CompetitorDTO, String> competitorDisplayNames;
|
|
| 26 | - public Map<CompetitorDTO, LeaderboardRowDTO> rows;
|
|
| 27 | - public boolean hasCarriedPoints;
|
|
| 28 | - public int[] discardThresholds;
|
|
| 29 | -
|
|
| 30 | - /**
|
|
| 31 | - * Set to the non-<code>null</code> regatta name if this DTO represents a <code>RegattaLeaderboard</code>.
|
|
| 32 | - */
|
|
| 33 | - public String regattaName;
|
|
| 34 | - public String displayName;
|
|
| 35 | - public UUID defaultCourseAreaId;
|
|
| 36 | - public String defaultCourseAreaName;
|
|
| 37 | - public ScoringSchemeType scoringScheme;
|
|
| 38 | - public LeaderboardType type;
|
|
| 39 | -
|
|
| 40 | - private Long delayToLiveInMillisForLatestRace;
|
|
| 41 | -
|
|
| 42 | - public AbstractLeaderboardDTO() {
|
|
| 43 | - races = new ArrayList<RaceColumnDTO>();
|
|
| 44 | - }
|
|
| 45 | -
|
|
| 46 | - public Set<BoatClassDTO> getBoatClasses() {
|
|
| 47 | - Set<BoatClassDTO> result = new HashSet<BoatClassDTO>();
|
|
| 48 | - if(rows != null) {
|
|
| 49 | - for (CompetitorDTO competitor : rows.keySet()) {
|
|
| 50 | - result.add(competitor.getBoatClass());
|
|
| 51 | - }
|
|
| 52 | - }
|
|
| 53 | - return result;
|
|
| 54 | - }
|
|
| 55 | -
|
|
| 56 | - public String getDisplayName() {
|
|
| 57 | - return displayName;
|
|
| 58 | - }
|
|
| 59 | -
|
|
| 60 | - public String getDisplayName(CompetitorDTO competitor) {
|
|
| 61 | - if (competitorDisplayNames == null || competitorDisplayNames.get(competitor) == null) {
|
|
| 62 | - return competitor.getName();
|
|
| 63 | - } else {
|
|
| 64 | - return competitorDisplayNames.get(competitor);
|
|
| 65 | - }
|
|
| 66 | - }
|
|
| 67 | -
|
|
| 68 | - /**
|
|
| 69 | - * If the race column whose name is specified in <code>raceColumnName</code> has at least one competitor who has valid
|
|
| 70 | - * {@link LeaderboardEntryDTO#legDetails} for that race column, the maximum number of entries of all such competitors in
|
|
| 71 | - * the leg details is returned, telling the number of legs that the race column shall display. Otherwise, -1 is returned.
|
|
| 72 | - * If you specify an non null <code>preselectedRace</code> the leg count is calculated only for that race for the column
|
|
| 73 | - * holding that race. All other columns will be unaffected by <code>preselectedRace</code> and will return their regular
|
|
| 74 | - * leg count. Note that this may lead to a situation where some competitors may have sailed more legs than others because
|
|
| 75 | - * their fleet association may have varied across races.<p>
|
|
| 76 | - *
|
|
| 77 | - * See also bug 2604 and bug 2035.
|
|
| 78 | - */
|
|
| 79 | - public int getLegCount(String raceColumnName, RaceIdentifier preselectedRace) {
|
|
| 80 | - int result = -1;
|
|
| 81 | - final boolean preselectedRaceIsInRaceColumn = isRaceInColumn(preselectedRace, raceColumnName);
|
|
| 82 | - for (LeaderboardRowDTO row : rows.values()) {
|
|
| 83 | - LeaderboardEntryDTO leaderboardEntryDTO = row.fieldsByRaceColumnName.get(raceColumnName);
|
|
| 84 | - if (leaderboardEntryDTO != null && leaderboardEntryDTO.legDetails != null
|
|
| 85 | - // when no race is pre-selected, always use the actual leg count;
|
|
| 86 | - // otherwise, if the pre-selected race is in the column identified by
|
|
| 87 | - // raceColumnName, use the entry only if it's in the pre-selected race;
|
|
| 88 | - // otherwise (a race is pre-selected but a different column's leg count
|
|
| 89 | - // is requested), use the regular leg count as is.
|
|
| 90 | - && (preselectedRace == null || !preselectedRaceIsInRaceColumn || preselectedRace.equals(leaderboardEntryDTO.race))) {
|
|
| 91 | - result = Math.max(result, leaderboardEntryDTO.legDetails.size());
|
|
| 92 | - }
|
|
| 93 | - }
|
|
| 94 | - return result;
|
|
| 95 | - }
|
|
| 96 | -
|
|
| 97 | - private boolean isRaceInColumn(RaceIdentifier preselectedRace, String raceColumnName) {
|
|
| 98 | - RaceColumnDTO raceColumn = getRaceColumnByName(raceColumnName);
|
|
| 99 | - return raceColumn.containsRace(preselectedRace);
|
|
| 100 | - }
|
|
| 101 | -
|
|
| 102 | - /**
|
|
| 103 | - * Tells if the <code>competitor</code> scored (and therefore presumably participated) in a medal race represented
|
|
| 104 | - * in this leaderboard.
|
|
| 105 | - */
|
|
| 106 | - public boolean scoredInMedalRace(CompetitorDTO competitor) {
|
|
| 107 | - LeaderboardRowDTO row = rows.get(competitor);
|
|
| 108 | - for (RaceColumnDTO race : races) {
|
|
| 109 | - if (race.isMedalRace() && row.fieldsByRaceColumnName.get(race.getRaceColumnName()).totalPoints > 0) {
|
|
| 110 | - return true;
|
|
| 111 | - }
|
|
| 112 | - }
|
|
| 113 | - return false;
|
|
| 114 | - }
|
|
| 115 | -
|
|
| 116 | - public Double getNetPoints(CompetitorDTO competitor, String nameOfLastRaceSoFar) {
|
|
| 117 | - Double netPoints = null;
|
|
| 118 | - LeaderboardRowDTO row = rows.get(competitor);
|
|
| 119 | - if (row != null) {
|
|
| 120 | - LeaderboardEntryDTO field = row.fieldsByRaceColumnName.get(nameOfLastRaceSoFar);
|
|
| 121 | - if (field != null && field.netPoints != null) {
|
|
| 122 | - netPoints = field.netPoints;
|
|
| 123 | - }
|
|
| 124 | - }
|
|
| 125 | - return netPoints;
|
|
| 126 | - }
|
|
| 127 | -
|
|
| 128 | - public boolean raceIsTracked(String raceColumnName) {
|
|
| 129 | - for (RaceColumnDTO race : races) {
|
|
| 130 | - if (race.getRaceColumnName().equals(raceColumnName)) {
|
|
| 131 | - for (FleetDTO fleet : race.getFleets()) {
|
|
| 132 | - if (race.isTrackedRace(fleet)) {
|
|
| 133 | - return true;
|
|
| 134 | - }
|
|
| 135 | - }
|
|
| 136 | - }
|
|
| 137 | - }
|
|
| 138 | - return false;
|
|
| 139 | -
|
|
| 140 | - }
|
|
| 141 | -
|
|
| 142 | - public boolean raceIsMedalRace(String raceColumnName) {
|
|
| 143 | - return getRaceColumnByName(raceColumnName).isMedalRace();
|
|
| 144 | - }
|
|
| 145 | -
|
|
| 146 | - /**
|
|
| 147 | - * If the {@link RaceColumnDTO} by the name <code>raceColumnName</code> doesn't exist yet within this leaderboard
|
|
| 148 | - * DTO, it is created, setting is {@link RaceColumnDTO#isValidInTotalScore()} to <code>true</code>. This method
|
|
| 149 | - * ensures that a fleet named <code>fleetName</code> is present. If it's not present yet, it's added to the race
|
|
| 150 | - * column's fleet name list. The <code>trackedRaceIdentifier</code> and <code>race</code> are associated with the
|
|
| 151 | - * column for the fleet identified by <code>fleetName</code>.
|
|
| 152 | - *
|
|
| 153 | - * @param explicitFactor
|
|
| 154 | - * factor by which to multiply the race column's points for the overall score; if <code>null</code>, the
|
|
| 155 | - * default will be determined by whether or not the column is marked as medal race
|
|
| 156 | - * @param effectiveFactor
|
|
| 157 | - * is what you get when you call {@link RaceColumn#getFactor()} on the race column that the resulting
|
|
| 158 | - * {@link RaceColumnDTO} represents
|
|
| 159 | - * @param regattaName
|
|
| 160 | - * must not be <code>null</code> if <code>seriesName</code> is not <code>null</code>; specified
|
|
| 161 | - * separately from <code>trackedRaceIdentifier</code> because a column may belong to regatta/series
|
|
| 162 | - * despite not having a tracked race associated
|
|
| 163 | - * @param seriesName
|
|
| 164 | - * if <code>null</code>, this method will produce a {@link RaceColumnDTO}, otherwise a
|
|
| 165 | - * {@link RaceColumnInSeriesDTO}
|
|
| 166 | - * @param fleetDTO
|
|
| 167 | - * must not be null
|
|
| 168 | - */
|
|
| 169 | - public RaceColumnDTO addRace(String raceColumnName, Double explicitFactor, double effectiveFactor,
|
|
| 170 | - String regattaName, String seriesName, FleetDTO fleetDTO, boolean medalRace, RegattaAndRaceIdentifier trackedRaceIdentifier, RaceDTO race) {
|
|
| 171 | - assert fleetDTO != null;
|
|
| 172 | - RaceColumnDTO raceColumnDTO = getRaceColumnByName(raceColumnName);
|
|
| 173 | - if (raceColumnDTO == null) {
|
|
| 174 | - raceColumnDTO = RaceColumnDTOFactory.INSTANCE.createRaceColumnDTO(raceColumnName, medalRace,
|
|
| 175 | - explicitFactor, regattaName, seriesName);
|
|
| 176 | - races.add(raceColumnDTO);
|
|
| 177 | - }
|
|
| 178 | - raceColumnDTO.setEffectiveFactor(effectiveFactor);
|
|
| 179 | - boolean contains = false;
|
|
| 180 | - for (FleetDTO fleet : raceColumnDTO.getFleets()) {
|
|
| 181 | - if (fleet.getName().equals(fleetDTO.getName())) {
|
|
| 182 | - contains = true;
|
|
| 183 | - break;
|
|
| 184 | - }
|
|
| 185 | - }
|
|
| 186 | - if (!contains) {
|
|
| 187 | - raceColumnDTO.addFleet(fleetDTO);
|
|
| 188 | - }
|
|
| 189 | - raceColumnDTO.setRaceIdentifier(fleetDTO, trackedRaceIdentifier);
|
|
| 190 | - raceColumnDTO.setRace(fleetDTO, race);
|
|
| 191 | - return raceColumnDTO;
|
|
| 192 | - }
|
|
| 193 | -
|
|
| 194 | - public RaceColumnDTO createEmptyRaceColumn(String raceColumnName, boolean medalRace, String regattaName, String seriesName) {
|
|
| 195 | - final RaceColumnDTO raceColumn = RaceColumnDTOFactory.INSTANCE.createRaceColumnDTO(raceColumnName,
|
|
| 196 | - medalRace, /* explicit factor */ null, regattaName, seriesName);
|
|
| 197 | - races.add(raceColumn);
|
|
| 198 | - return raceColumn;
|
|
| 199 | - }
|
|
| 200 | -
|
|
| 201 | - public RaceColumnDTO getRaceColumnByName(String raceColumnName) {
|
|
| 202 | - for (RaceColumnDTO race : races) {
|
|
| 203 | - if (race.getRaceColumnName().equals(raceColumnName)) {
|
|
| 204 | - return race;
|
|
| 205 | - }
|
|
| 206 | - }
|
|
| 207 | - return null;
|
|
| 208 | - }
|
|
| 209 | -
|
|
| 210 | - public int getRaceColumnsCount() {
|
|
| 211 | - return races.size();
|
|
| 212 | - }
|
|
| 213 | -
|
|
| 214 | - public int getRacesCount() {
|
|
| 215 | - int result = 0;
|
|
| 216 | - for (RaceColumnDTO race : getRaceList()) {
|
|
| 217 | - result += race.getFleets().size();
|
|
| 218 | - }
|
|
| 219 | - return result;
|
|
| 220 | - }
|
|
| 221 | -
|
|
| 222 | - public int getTrackedRacesCount() {
|
|
| 223 | - int result = 0;
|
|
| 224 | - for (RaceColumnDTO raceColumn : getRaceList()) {
|
|
| 225 | - for (FleetDTO fleet : raceColumn.getFleets()) {
|
|
| 226 | - RaceDTO race = raceColumn.getRace(fleet);
|
|
| 227 | - if(race != null && race.trackedRace != null && race.trackedRace.hasGPSData && race.trackedRace.hasWindData) {
|
|
| 228 | - result++;
|
|
| 229 | - }
|
|
| 230 | - }
|
|
| 231 | - }
|
|
| 232 | - return result;
|
|
| 233 | - }
|
|
| 234 | -
|
|
| 235 | - public List<RaceColumnDTO> getRaceList() {
|
|
| 236 | - return races;
|
|
| 237 | - }
|
|
| 238 | -
|
|
| 239 | - public void setRaceList(List<RaceColumnDTO> raceList) {
|
|
| 240 | - this.races = raceList;
|
|
| 241 | - }
|
|
| 242 | -
|
|
| 243 | - public boolean raceListContains(String raceColumnName) {
|
|
| 244 | - return getRaceColumnByName(raceColumnName) != null;
|
|
| 245 | - }
|
|
| 246 | -
|
|
| 247 | - public void moveRaceUp(String raceColumnName) {
|
|
| 248 | - RaceColumnDTO race = getRaceColumnByName(raceColumnName);
|
|
| 249 | - int index = races.indexOf(race);
|
|
| 250 | - index--;
|
|
| 251 | - if (index >= 0) {
|
|
| 252 | - races.remove(index + 1);
|
|
| 253 | - races.add(index, race);
|
|
| 254 | - }
|
|
| 255 | - }
|
|
| 256 | -
|
|
| 257 | - public void moveRaceDown(String raceColumnName) {
|
|
| 258 | - RaceColumnDTO race = getRaceColumnByName(raceColumnName);
|
|
| 259 | - int index = races.indexOf(race);
|
|
| 260 | - if (index != -1) {
|
|
| 261 | - index++;
|
|
| 262 | - if (index < races.size()) {
|
|
| 263 | - races.remove(index - 1);
|
|
| 264 | - races.add(index, race);
|
|
| 265 | - }
|
|
| 266 | - }
|
|
| 267 | - }
|
|
| 268 | -
|
|
| 269 | - public void setIsMedalRace(String raceColumnName, boolean medalRace) {
|
|
| 270 | - getRaceColumnByName(raceColumnName).setMedalRace(medalRace);
|
|
| 271 | - }
|
|
| 272 | -
|
|
| 273 | - /**
|
|
| 274 | - * @return The earliest start date of the races, or <code>null</code> if no start dates of the races are available.
|
|
| 275 | - */
|
|
| 276 | - public Date getStartDate() {
|
|
| 277 | - Date leaderboardStart = null;
|
|
| 278 | - for (RaceColumnDTO race : getRaceList()) {
|
|
| 279 | - for (FleetDTO fleet: race.getFleets()) {
|
|
| 280 | - Date raceStart = race.getStartDate(fleet);
|
|
| 281 | - if (raceStart != null) {
|
|
| 282 | - if (leaderboardStart == null) {
|
|
| 283 | - leaderboardStart = new Date();
|
|
| 284 | - } else {
|
|
| 285 | - leaderboardStart = leaderboardStart.before(raceStart) ? leaderboardStart : raceStart;
|
|
| 286 | - }
|
|
| 287 | - }
|
|
| 288 | - }
|
|
| 289 | - }
|
|
| 290 | - return leaderboardStart;
|
|
| 291 | - }
|
|
| 292 | -
|
|
| 293 | - /**
|
|
| 294 | - * Takes the {@link PlacemarkOrderDTO} of all races in this leaderboard, if the PlacemarkOrderDTO for the race is
|
|
| 295 | - * available, and fills all {@link PlacemarkDTO} in a new PlacemarkOrderDTO.<br />
|
|
| 296 | - * The order of the races in this leaderboard determine the order of the PlacemarkDTOs in the PlacemarkOrderDTO.
|
|
| 297 | - *
|
|
| 298 | - * @return The places of this leaderboard in form of a {@link PlacemarkOrderDTO}, or <code>null</code> if the
|
|
| 299 | - * {@link PlacemarkOrderDTO places} of no race are available
|
|
| 300 | - */
|
|
| 301 | - public PlacemarkOrderDTO getPlaces() {
|
|
| 302 | - PlacemarkOrderDTO leaderboardPlaces = null;
|
|
| 303 | - for (RaceColumnDTO race : getRaceList()) {
|
|
| 304 | - PlacemarkOrderDTO racePlaces = race.getPlaces();
|
|
| 305 | - if (racePlaces != null) {
|
|
| 306 | - if (leaderboardPlaces == null) {
|
|
| 307 | - leaderboardPlaces = new PlacemarkOrderDTO();
|
|
| 308 | - }
|
|
| 309 | - leaderboardPlaces.getPlacemarks().addAll(racePlaces.getPlacemarks());
|
|
| 310 | - }
|
|
| 311 | - }
|
|
| 312 | - return leaderboardPlaces;
|
|
| 313 | - }
|
|
| 314 | -
|
|
| 315 | - /**
|
|
| 316 | - * @return <code>true</code> if the leaderboard contains a race which is live
|
|
| 317 | - */
|
|
| 318 | - public boolean hasLiveRace(long serverTimePointAsMillis) {
|
|
| 319 | - for (RaceColumnDTO race : getRaceList()) {
|
|
| 320 | - for (FleetDTO fleet : race.getFleets()) {
|
|
| 321 | - if (race.isLive(fleet, serverTimePointAsMillis)) {
|
|
| 322 | - return true;
|
|
| 323 | - }
|
|
| 324 | - }
|
|
| 325 | - }
|
|
| 326 | - return false;
|
|
| 327 | - }
|
|
| 328 | -
|
|
| 329 | - public List<Util.Pair<RaceColumnDTO, FleetDTO>> getLiveRaces(long serverTimePointAsMillis) {
|
|
| 330 | - List<Util.Pair<RaceColumnDTO, FleetDTO>> result = new ArrayList<Util.Pair<RaceColumnDTO, FleetDTO>>();
|
|
| 331 | - for (RaceColumnDTO race : getRaceList()) {
|
|
| 332 | - for (FleetDTO fleet : race.getFleets()) {
|
|
| 333 | - if (race.isLive(fleet, serverTimePointAsMillis)) {
|
|
| 334 | - result.add(new Util.Pair<RaceColumnDTO, FleetDTO>(race, fleet));
|
|
| 335 | - }
|
|
| 336 | - }
|
|
| 337 | - }
|
|
| 338 | - return result;
|
|
| 339 | - }
|
|
| 340 | -
|
|
| 341 | - @Override
|
|
| 342 | - public int hashCode() {
|
|
| 343 | - final int prime = 31;
|
|
| 344 | - int result = 1;
|
|
| 345 | - result = prime * result + ((competitorDisplayNames == null) ? 0 : competitorDisplayNames.hashCode());
|
|
| 346 | - result = prime * result + Arrays.hashCode(discardThresholds);
|
|
| 347 | - result = prime * result + (hasCarriedPoints ? 1231 : 1237);
|
|
| 348 | - result = prime * result + ((name == null) ? 0 : name.hashCode());
|
|
| 349 | - result = prime * result + ((scoringScheme == null) ? 0 : scoringScheme.hashCode());
|
|
| 350 | - if (races == null) {
|
|
| 351 | - result = prime * result;
|
|
| 352 | - } else {
|
|
| 353 | - List<String> raceNames = new ArrayList<String>();
|
|
| 354 | - for (RaceColumnDTO race : races) {
|
|
| 355 | - raceNames.add(race.getName());
|
|
| 356 | - }
|
|
| 357 | - result = prime * result + raceNames.hashCode();
|
|
| 358 | - }
|
|
| 359 | - result = prime * result + ((rows == null) ? 0 : rows.hashCode());
|
|
| 360 | - return result;
|
|
| 361 | - }
|
|
| 362 | -
|
|
| 363 | - @Override
|
|
| 364 | - public boolean equals(Object obj) {
|
|
| 365 | - if (this == obj)
|
|
| 366 | - return true;
|
|
| 367 | - if (obj == null)
|
|
| 368 | - return false;
|
|
| 369 | - if (getClass() != obj.getClass())
|
|
| 370 | - return false;
|
|
| 371 | - AbstractLeaderboardDTO other = (AbstractLeaderboardDTO) obj;
|
|
| 372 | - if (competitorDisplayNames == null) {
|
|
| 373 | - if (other.competitorDisplayNames != null)
|
|
| 374 | - return false;
|
|
| 375 | - } else if (!competitorDisplayNames.equals(other.competitorDisplayNames))
|
|
| 376 | - return false;
|
|
| 377 | - if (!Arrays.equals(discardThresholds, other.discardThresholds))
|
|
| 378 | - return false;
|
|
| 379 | - if (hasCarriedPoints != other.hasCarriedPoints)
|
|
| 380 | - return false;
|
|
| 381 | - if (type != other.type)
|
|
| 382 | - return false;
|
|
| 383 | - if (scoringScheme != other.scoringScheme)
|
|
| 384 | - return false;
|
|
| 385 | - if (name == null) {
|
|
| 386 | - if (other.name != null)
|
|
| 387 | - return false;
|
|
| 388 | - } else if (!name.equals(other.name))
|
|
| 389 | - return false;
|
|
| 390 | - if (races == null) {
|
|
| 391 | - if (other.races != null)
|
|
| 392 | - return false;
|
|
| 393 | - } else {
|
|
| 394 | - // compare race column names only, not the contents
|
|
| 395 | - if (races.size() != (other.races==null?0:other.races.size())) {
|
|
| 396 | - return false;
|
|
| 397 | - }
|
|
| 398 | - List<String> raceColumnNames = new ArrayList<String>(races.size());
|
|
| 399 | - List<String> otherRaceColumnNames = new ArrayList<String>(races.size());
|
|
| 400 | - for (RaceColumnDTO race : races) {
|
|
| 401 | - raceColumnNames.add(race.getName());
|
|
| 402 | - }
|
|
| 403 | - if (other.races != null) {
|
|
| 404 | - for (RaceColumnDTO otherRace : other.races) {
|
|
| 405 | - otherRaceColumnNames.add(otherRace.getName());
|
|
| 406 | - }
|
|
| 407 | - }
|
|
| 408 | - if (!raceColumnNames.equals(otherRaceColumnNames))
|
|
| 409 | - return false;
|
|
| 410 | - if (rows == null) {
|
|
| 411 | - if (other.rows != null)
|
|
| 412 | - return false;
|
|
| 413 | - } else if (!rows.equals(other.rows))
|
|
| 414 | - return false;
|
|
| 415 | - }
|
|
| 416 | - return true;
|
|
| 417 | - }
|
|
| 418 | -
|
|
| 419 | - public boolean isDisplayNameSet(CompetitorDTO competitor) {
|
|
| 420 | - return competitorDisplayNames.get(competitor) != null;
|
|
| 421 | - }
|
|
| 422 | -
|
|
| 423 | - public Long getDelayToLiveInMillisForLatestRace() {
|
|
| 424 | - return delayToLiveInMillisForLatestRace;
|
|
| 425 | - }
|
|
| 426 | -
|
|
| 427 | - public void setDelayToLiveInMillisForLatestRace(Long delayToLiveInMillisForLatestRace) {
|
|
| 428 | - this.delayToLiveInMillisForLatestRace = delayToLiveInMillisForLatestRace;
|
|
| 429 | - }
|
|
| 430 | -}
|
|
| 1 | +package com.sap.sailing.domain.common.dto; |
|
| 2 | + |
|
| 3 | +import java.io.Serializable; |
|
| 4 | +import java.util.ArrayList; |
|
| 5 | +import java.util.Arrays; |
|
| 6 | +import java.util.Date; |
|
| 7 | +import java.util.HashSet; |
|
| 8 | +import java.util.List; |
|
| 9 | +import java.util.Map; |
|
| 10 | +import java.util.Set; |
|
| 11 | +import java.util.UUID; |
|
| 12 | + |
|
| 13 | +import com.sap.sailing.domain.common.LeaderboardType; |
|
| 14 | +import com.sap.sailing.domain.common.RaceIdentifier; |
|
| 15 | +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; |
|
| 16 | +import com.sap.sailing.domain.common.ScoringSchemeType; |
|
| 17 | +import com.sap.sse.common.Util; |
|
| 18 | + |
|
| 19 | +public abstract class AbstractLeaderboardDTO implements Serializable { |
|
| 20 | + private static final long serialVersionUID = -205106531931903527L; |
|
| 21 | + |
|
| 22 | + public String name; |
|
| 23 | + |
|
| 24 | + private List<RaceColumnDTO> races; |
|
| 25 | + public Map<CompetitorDTO, String> competitorDisplayNames; |
|
| 26 | + public Map<CompetitorDTO, LeaderboardRowDTO> rows; |
|
| 27 | + public boolean hasCarriedPoints; |
|
| 28 | + public int[] discardThresholds; |
|
| 29 | + |
|
| 30 | + /** |
|
| 31 | + * Set to the non-<code>null</code> regatta name if this DTO represents a <code>RegattaLeaderboard</code>. |
|
| 32 | + */ |
|
| 33 | + public String regattaName; |
|
| 34 | + public String displayName; |
|
| 35 | + public UUID defaultCourseAreaId; |
|
| 36 | + public String defaultCourseAreaName; |
|
| 37 | + public ScoringSchemeType scoringScheme; |
|
| 38 | + public LeaderboardType type; |
|
| 39 | + |
|
| 40 | + private Long delayToLiveInMillisForLatestRace; |
|
| 41 | + |
|
| 42 | + public AbstractLeaderboardDTO() { |
|
| 43 | + races = new ArrayList<RaceColumnDTO>(); |
|
| 44 | + } |
|
| 45 | + |
|
| 46 | + public Set<BoatClassDTO> getBoatClasses() { |
|
| 47 | + Set<BoatClassDTO> result = new HashSet<BoatClassDTO>(); |
|
| 48 | + if(rows != null) { |
|
| 49 | + for (CompetitorDTO competitor : rows.keySet()) { |
|
| 50 | + result.add(competitor.getBoatClass()); |
|
| 51 | + } |
|
| 52 | + } |
|
| 53 | + return result; |
|
| 54 | + } |
|
| 55 | + |
|
| 56 | + public String getDisplayName() { |
|
| 57 | + return displayName; |
|
| 58 | + } |
|
| 59 | + |
|
| 60 | + public String getDisplayName(CompetitorDTO competitor) { |
|
| 61 | + if (competitorDisplayNames == null || competitorDisplayNames.get(competitor) == null) { |
|
| 62 | + return competitor.getName(); |
|
| 63 | + } else { |
|
| 64 | + return competitorDisplayNames.get(competitor); |
|
| 65 | + } |
|
| 66 | + } |
|
| 67 | + |
|
| 68 | + /** |
|
| 69 | + * If the race column whose name is specified in <code>raceColumnName</code> has at least one competitor who has valid |
|
| 70 | + * {@link LeaderboardEntryDTO#legDetails} for that race column, the maximum number of entries of all such competitors in |
|
| 71 | + * the leg details is returned, telling the number of legs that the race column shall display. Otherwise, -1 is returned. |
|
| 72 | + * If you specify an non null <code>preselectedRace</code> the leg count is calculated only for that race for the column |
|
| 73 | + * holding that race. All other columns will be unaffected by <code>preselectedRace</code> and will return their regular |
|
| 74 | + * leg count. Note that this may lead to a situation where some competitors may have sailed more legs than others because |
|
| 75 | + * their fleet association may have varied across races.<p> |
|
| 76 | + * |
|
| 77 | + * See also bug 2604 and bug 2035. |
|
| 78 | + */ |
|
| 79 | + public int getLegCount(String raceColumnName, RaceIdentifier preselectedRace) { |
|
| 80 | + int result = -1; |
|
| 81 | + final boolean preselectedRaceIsInRaceColumn = isRaceInColumn(preselectedRace, raceColumnName); |
|
| 82 | + for (LeaderboardRowDTO row : rows.values()) { |
|
| 83 | + LeaderboardEntryDTO leaderboardEntryDTO = row.fieldsByRaceColumnName.get(raceColumnName); |
|
| 84 | + if (leaderboardEntryDTO != null && leaderboardEntryDTO.legDetails != null |
|
| 85 | + // when no race is pre-selected, always use the actual leg count; |
|
| 86 | + // otherwise, if the pre-selected race is in the column identified by |
|
| 87 | + // raceColumnName, use the entry only if it's in the pre-selected race; |
|
| 88 | + // otherwise (a race is pre-selected but a different column's leg count |
|
| 89 | + // is requested), use the regular leg count as is. |
|
| 90 | + && (preselectedRace == null || !preselectedRaceIsInRaceColumn || preselectedRace.equals(leaderboardEntryDTO.race))) { |
|
| 91 | + result = Math.max(result, leaderboardEntryDTO.legDetails.size()); |
|
| 92 | + } |
|
| 93 | + } |
|
| 94 | + return result; |
|
| 95 | + } |
|
| 96 | + |
|
| 97 | + private boolean isRaceInColumn(RaceIdentifier preselectedRace, String raceColumnName) { |
|
| 98 | + RaceColumnDTO raceColumn = getRaceColumnByName(raceColumnName); |
|
| 99 | + return raceColumn.containsRace(preselectedRace); |
|
| 100 | + } |
|
| 101 | + |
|
| 102 | + /** |
|
| 103 | + * Tells if the <code>competitor</code> scored (and therefore presumably participated) in a medal race represented |
|
| 104 | + * in this leaderboard. |
|
| 105 | + */ |
|
| 106 | + public boolean scoredInMedalRace(CompetitorDTO competitor) { |
|
| 107 | + LeaderboardRowDTO row = rows.get(competitor); |
|
| 108 | + for (RaceColumnDTO race : races) { |
|
| 109 | + if (race.isMedalRace() && row.fieldsByRaceColumnName.get(race.getRaceColumnName()).totalPoints > 0) { |
|
| 110 | + return true; |
|
| 111 | + } |
|
| 112 | + } |
|
| 113 | + return false; |
|
| 114 | + } |
|
| 115 | + |
|
| 116 | + public Double getNetPoints(CompetitorDTO competitor, String nameOfLastRaceSoFar) { |
|
| 117 | + Double netPoints = null; |
|
| 118 | + LeaderboardRowDTO row = rows.get(competitor); |
|
| 119 | + if (row != null) { |
|
| 120 | + LeaderboardEntryDTO field = row.fieldsByRaceColumnName.get(nameOfLastRaceSoFar); |
|
| 121 | + if (field != null && field.netPoints != null) { |
|
| 122 | + netPoints = field.netPoints; |
|
| 123 | + } |
|
| 124 | + } |
|
| 125 | + return netPoints; |
|
| 126 | + } |
|
| 127 | + |
|
| 128 | + public boolean raceIsTracked(String raceColumnName) { |
|
| 129 | + for (RaceColumnDTO race : races) { |
|
| 130 | + if (race.getRaceColumnName().equals(raceColumnName)) { |
|
| 131 | + for (FleetDTO fleet : race.getFleets()) { |
|
| 132 | + if (race.isTrackedRace(fleet)) { |
|
| 133 | + return true; |
|
| 134 | + } |
|
| 135 | + } |
|
| 136 | + } |
|
| 137 | + } |
|
| 138 | + return false; |
|
| 139 | + |
|
| 140 | + } |
|
| 141 | + |
|
| 142 | + public boolean raceIsMedalRace(String raceColumnName) { |
|
| 143 | + return getRaceColumnByName(raceColumnName).isMedalRace(); |
|
| 144 | + } |
|
| 145 | + |
|
| 146 | + /** |
|
| 147 | + * If the {@link RaceColumnDTO} by the name <code>raceColumnName</code> doesn't exist yet within this leaderboard |
|
| 148 | + * DTO, it is created, setting is {@link RaceColumnDTO#isValidInTotalScore()} to <code>true</code>. This method |
|
| 149 | + * ensures that a fleet named <code>fleetName</code> is present. If it's not present yet, it's added to the race |
|
| 150 | + * column's fleet name list. The <code>trackedRaceIdentifier</code> and <code>race</code> are associated with the |
|
| 151 | + * column for the fleet identified by <code>fleetName</code>. |
|
| 152 | + * |
|
| 153 | + * @param explicitFactor |
|
| 154 | + * factor by which to multiply the race column's points for the overall score; if <code>null</code>, the |
|
| 155 | + * default will be determined by whether or not the column is marked as medal race |
|
| 156 | + * @param effectiveFactor |
|
| 157 | + * is what you get when you call {@link RaceColumn#getFactor()} on the race column that the resulting |
|
| 158 | + * {@link RaceColumnDTO} represents |
|
| 159 | + * @param regattaName |
|
| 160 | + * must not be <code>null</code> if <code>seriesName</code> is not <code>null</code>; specified |
|
| 161 | + * separately from <code>trackedRaceIdentifier</code> because a column may belong to regatta/series |
|
| 162 | + * despite not having a tracked race associated |
|
| 163 | + * @param seriesName |
|
| 164 | + * if <code>null</code>, this method will produce a {@link RaceColumnDTO}, otherwise a |
|
| 165 | + * {@link RaceColumnInSeriesDTO} |
|
| 166 | + * @param fleetDTO |
|
| 167 | + * must not be null |
|
| 168 | + */ |
|
| 169 | + public RaceColumnDTO addRace(String raceColumnName, Double explicitFactor, double effectiveFactor, |
|
| 170 | + String regattaName, String seriesName, FleetDTO fleetDTO, boolean medalRace, |
|
| 171 | + RegattaAndRaceIdentifier trackedRaceIdentifier, RaceDTO race, boolean isMetaLeaderboardColumn) { |
|
| 172 | + assert fleetDTO != null; |
|
| 173 | + RaceColumnDTO raceColumnDTO = getRaceColumnByName(raceColumnName); |
|
| 174 | + if (raceColumnDTO == null) { |
|
| 175 | + raceColumnDTO = RaceColumnDTOFactory.INSTANCE.createRaceColumnDTO(raceColumnName, medalRace, |
|
| 176 | + explicitFactor, regattaName, seriesName, isMetaLeaderboardColumn); |
|
| 177 | + races.add(raceColumnDTO); |
|
| 178 | + } |
|
| 179 | + raceColumnDTO.setEffectiveFactor(effectiveFactor); |
|
| 180 | + boolean contains = false; |
|
| 181 | + for (FleetDTO fleet : raceColumnDTO.getFleets()) { |
|
| 182 | + if (fleet.getName().equals(fleetDTO.getName())) { |
|
| 183 | + contains = true; |
|
| 184 | + break; |
|
| 185 | + } |
|
| 186 | + } |
|
| 187 | + if (!contains) { |
|
| 188 | + raceColumnDTO.addFleet(fleetDTO); |
|
| 189 | + } |
|
| 190 | + raceColumnDTO.setRaceIdentifier(fleetDTO, trackedRaceIdentifier); |
|
| 191 | + raceColumnDTO.setRace(fleetDTO, race); |
|
| 192 | + return raceColumnDTO; |
|
| 193 | + } |
|
| 194 | + |
|
| 195 | + public RaceColumnDTO createEmptyRaceColumn(String raceColumnName, boolean medalRace, String regattaName, |
|
| 196 | + String seriesName, boolean isMetaLeaderboardColumn) { |
|
| 197 | + final RaceColumnDTO raceColumn = RaceColumnDTOFactory.INSTANCE.createRaceColumnDTO(raceColumnName, |
|
| 198 | + medalRace, /* explicit factor */ null, regattaName, seriesName, isMetaLeaderboardColumn); |
|
| 199 | + races.add(raceColumn); |
|
| 200 | + return raceColumn; |
|
| 201 | + } |
|
| 202 | + |
|
| 203 | + public RaceColumnDTO getRaceColumnByName(String raceColumnName) { |
|
| 204 | + for (RaceColumnDTO race : races) { |
|
| 205 | + if (race.getRaceColumnName().equals(raceColumnName)) { |
|
| 206 | + return race; |
|
| 207 | + } |
|
| 208 | + } |
|
| 209 | + return null; |
|
| 210 | + } |
|
| 211 | + |
|
| 212 | + public int getRaceColumnsCount() { |
|
| 213 | + return races.size(); |
|
| 214 | + } |
|
| 215 | + |
|
| 216 | + public int getRacesCount() { |
|
| 217 | + int result = 0; |
|
| 218 | + for (RaceColumnDTO race : getRaceList()) { |
|
| 219 | + result += race.getFleets().size(); |
|
| 220 | + } |
|
| 221 | + return result; |
|
| 222 | + } |
|
| 223 | + |
|
| 224 | + public int getTrackedRacesCount() { |
|
| 225 | + int result = 0; |
|
| 226 | + for (RaceColumnDTO raceColumn : getRaceList()) { |
|
| 227 | + for (FleetDTO fleet : raceColumn.getFleets()) { |
|
| 228 | + RaceDTO race = raceColumn.getRace(fleet); |
|
| 229 | + if(race != null && race.trackedRace != null && race.trackedRace.hasGPSData && race.trackedRace.hasWindData) { |
|
| 230 | + result++; |
|
| 231 | + } |
|
| 232 | + } |
|
| 233 | + } |
|
| 234 | + return result; |
|
| 235 | + } |
|
| 236 | + |
|
| 237 | + public List<RaceColumnDTO> getRaceList() { |
|
| 238 | + return races; |
|
| 239 | + } |
|
| 240 | + |
|
| 241 | + public void setRaceList(List<RaceColumnDTO> raceList) { |
|
| 242 | + this.races = raceList; |
|
| 243 | + } |
|
| 244 | + |
|
| 245 | + public boolean raceListContains(String raceColumnName) { |
|
| 246 | + return getRaceColumnByName(raceColumnName) != null; |
|
| 247 | + } |
|
| 248 | + |
|
| 249 | + public void moveRaceUp(String raceColumnName) { |
|
| 250 | + RaceColumnDTO race = getRaceColumnByName(raceColumnName); |
|
| 251 | + int index = races.indexOf(race); |
|
| 252 | + index--; |
|
| 253 | + if (index >= 0) { |
|
| 254 | + races.remove(index + 1); |
|
| 255 | + races.add(index, race); |
|
| 256 | + } |
|
| 257 | + } |
|
| 258 | + |
|
| 259 | + public void moveRaceDown(String raceColumnName) { |
|
| 260 | + RaceColumnDTO race = getRaceColumnByName(raceColumnName); |
|
| 261 | + int index = races.indexOf(race); |
|
| 262 | + if (index != -1) { |
|
| 263 | + index++; |
|
| 264 | + if (index < races.size()) { |
|
| 265 | + races.remove(index - 1); |
|
| 266 | + races.add(index, race); |
|
| 267 | + } |
|
| 268 | + } |
|
| 269 | + } |
|
| 270 | + |
|
| 271 | + public void setIsMedalRace(String raceColumnName, boolean medalRace) { |
|
| 272 | + getRaceColumnByName(raceColumnName).setMedalRace(medalRace); |
|
| 273 | + } |
|
| 274 | + |
|
| 275 | + /** |
|
| 276 | + * @return The earliest start date of the races, or <code>null</code> if no start dates of the races are available. |
|
| 277 | + */ |
|
| 278 | + public Date getStartDate() { |
|
| 279 | + Date leaderboardStart = null; |
|
| 280 | + for (RaceColumnDTO race : getRaceList()) { |
|
| 281 | + for (FleetDTO fleet: race.getFleets()) { |
|
| 282 | + Date raceStart = race.getStartDate(fleet); |
|
| 283 | + if (raceStart != null) { |
|
| 284 | + if (leaderboardStart == null) { |
|
| 285 | + leaderboardStart = new Date(); |
|
| 286 | + } else { |
|
| 287 | + leaderboardStart = leaderboardStart.before(raceStart) ? leaderboardStart : raceStart; |
|
| 288 | + } |
|
| 289 | + } |
|
| 290 | + } |
|
| 291 | + } |
|
| 292 | + return leaderboardStart; |
|
| 293 | + } |
|
| 294 | + |
|
| 295 | + /** |
|
| 296 | + * Takes the {@link PlacemarkOrderDTO} of all races in this leaderboard, if the PlacemarkOrderDTO for the race is |
|
| 297 | + * available, and fills all {@link PlacemarkDTO} in a new PlacemarkOrderDTO.<br /> |
|
| 298 | + * The order of the races in this leaderboard determine the order of the PlacemarkDTOs in the PlacemarkOrderDTO. |
|
| 299 | + * |
|
| 300 | + * @return The places of this leaderboard in form of a {@link PlacemarkOrderDTO}, or <code>null</code> if the |
|
| 301 | + * {@link PlacemarkOrderDTO places} of no race are available |
|
| 302 | + */ |
|
| 303 | + public PlacemarkOrderDTO getPlaces() { |
|
| 304 | + PlacemarkOrderDTO leaderboardPlaces = null; |
|
| 305 | + for (RaceColumnDTO race : getRaceList()) { |
|
| 306 | + PlacemarkOrderDTO racePlaces = race.getPlaces(); |
|
| 307 | + if (racePlaces != null) { |
|
| 308 | + if (leaderboardPlaces == null) { |
|
| 309 | + leaderboardPlaces = new PlacemarkOrderDTO(); |
|
| 310 | + } |
|
| 311 | + leaderboardPlaces.getPlacemarks().addAll(racePlaces.getPlacemarks()); |
|
| 312 | + } |
|
| 313 | + } |
|
| 314 | + return leaderboardPlaces; |
|
| 315 | + } |
|
| 316 | + |
|
| 317 | + /** |
|
| 318 | + * @return <code>true</code> if the leaderboard contains a race which is live |
|
| 319 | + */ |
|
| 320 | + public boolean hasLiveRace(long serverTimePointAsMillis) { |
|
| 321 | + for (RaceColumnDTO race : getRaceList()) { |
|
| 322 | + for (FleetDTO fleet : race.getFleets()) { |
|
| 323 | + if (race.isLive(fleet, serverTimePointAsMillis)) { |
|
| 324 | + return true; |
|
| 325 | + } |
|
| 326 | + } |
|
| 327 | + } |
|
| 328 | + return false; |
|
| 329 | + } |
|
| 330 | + |
|
| 331 | + public List<Util.Pair<RaceColumnDTO, FleetDTO>> getLiveRaces(long serverTimePointAsMillis) { |
|
| 332 | + List<Util.Pair<RaceColumnDTO, FleetDTO>> result = new ArrayList<Util.Pair<RaceColumnDTO, FleetDTO>>(); |
|
| 333 | + for (RaceColumnDTO race : getRaceList()) { |
|
| 334 | + for (FleetDTO fleet : race.getFleets()) { |
|
| 335 | + if (race.isLive(fleet, serverTimePointAsMillis)) { |
|
| 336 | + result.add(new Util.Pair<RaceColumnDTO, FleetDTO>(race, fleet)); |
|
| 337 | + } |
|
| 338 | + } |
|
| 339 | + } |
|
| 340 | + return result; |
|
| 341 | + } |
|
| 342 | + |
|
| 343 | + @Override |
|
| 344 | + public int hashCode() { |
|
| 345 | + final int prime = 31; |
|
| 346 | + int result = 1; |
|
| 347 | + result = prime * result + ((competitorDisplayNames == null) ? 0 : competitorDisplayNames.hashCode()); |
|
| 348 | + result = prime * result + Arrays.hashCode(discardThresholds); |
|
| 349 | + result = prime * result + (hasCarriedPoints ? 1231 : 1237); |
|
| 350 | + result = prime * result + ((type == null) ? 0 : type.hashCode()); |
|
| 351 | + result = prime * result + ((name == null) ? 0 : name.hashCode()); |
|
| 352 | + result = prime * result + ((scoringScheme == null) ? 0 : scoringScheme.hashCode()); |
|
| 353 | + if (races == null) { |
|
| 354 | + result = prime * result; |
|
| 355 | + } else { |
|
| 356 | + List<String> raceNames = new ArrayList<String>(); |
|
| 357 | + for (RaceColumnDTO race : races) { |
|
| 358 | + raceNames.add(race.getName()); |
|
| 359 | + } |
|
| 360 | + result = prime * result + raceNames.hashCode(); |
|
| 361 | + } |
|
| 362 | + result = prime * result + ((rows == null) ? 0 : rows.hashCode()); |
|
| 363 | + return result; |
|
| 364 | + } |
|
| 365 | + |
|
| 366 | + @Override |
|
| 367 | + public boolean equals(Object obj) { |
|
| 368 | + if (this == obj) |
|
| 369 | + return true; |
|
| 370 | + if (obj == null) |
|
| 371 | + return false; |
|
| 372 | + if (getClass() != obj.getClass()) |
|
| 373 | + return false; |
|
| 374 | + AbstractLeaderboardDTO other = (AbstractLeaderboardDTO) obj; |
|
| 375 | + if (competitorDisplayNames == null) { |
|
| 376 | + if (other.competitorDisplayNames != null) |
|
| 377 | + return false; |
|
| 378 | + } else if (!competitorDisplayNames.equals(other.competitorDisplayNames)) |
|
| 379 | + return false; |
|
| 380 | + if (!Arrays.equals(discardThresholds, other.discardThresholds)) |
|
| 381 | + return false; |
|
| 382 | + if (hasCarriedPoints != other.hasCarriedPoints) |
|
| 383 | + return false; |
|
| 384 | + if (type != other.type) |
|
| 385 | + return false; |
|
| 386 | + if (scoringScheme != other.scoringScheme) |
|
| 387 | + return false; |
|
| 388 | + if (name == null) { |
|
| 389 | + if (other.name != null) |
|
| 390 | + return false; |
|
| 391 | + } else if (!name.equals(other.name)) |
|
| 392 | + return false; |
|
| 393 | + if (races == null) { |
|
| 394 | + if (other.races != null) |
|
| 395 | + return false; |
|
| 396 | + } else { |
|
| 397 | + // compare race column names only, not the contents |
|
| 398 | + if (races.size() != (other.races==null?0:other.races.size())) { |
|
| 399 | + return false; |
|
| 400 | + } |
|
| 401 | + List<String> raceColumnNames = new ArrayList<String>(races.size()); |
|
| 402 | + List<String> otherRaceColumnNames = new ArrayList<String>(races.size()); |
|
| 403 | + for (RaceColumnDTO race : races) { |
|
| 404 | + raceColumnNames.add(race.getName()); |
|
| 405 | + } |
|
| 406 | + if (other.races != null) { |
|
| 407 | + for (RaceColumnDTO otherRace : other.races) { |
|
| 408 | + otherRaceColumnNames.add(otherRace.getName()); |
|
| 409 | + } |
|
| 410 | + } |
|
| 411 | + if (!raceColumnNames.equals(otherRaceColumnNames)) |
|
| 412 | + return false; |
|
| 413 | + if (rows == null) { |
|
| 414 | + if (other.rows != null) |
|
| 415 | + return false; |
|
| 416 | + } else if (!rows.equals(other.rows)) |
|
| 417 | + return false; |
|
| 418 | + } |
|
| 419 | + return true; |
|
| 420 | + } |
|
| 421 | + |
|
| 422 | + public boolean isDisplayNameSet(CompetitorDTO competitor) { |
|
| 423 | + return competitorDisplayNames.get(competitor) != null; |
|
| 424 | + } |
|
| 425 | + |
|
| 426 | + public Long getDelayToLiveInMillisForLatestRace() { |
|
| 427 | + return delayToLiveInMillisForLatestRace; |
|
| 428 | + } |
|
| 429 | + |
|
| 430 | + public void setDelayToLiveInMillisForLatestRace(Long delayToLiveInMillisForLatestRace) { |
|
| 431 | + this.delayToLiveInMillisForLatestRace = delayToLiveInMillisForLatestRace; |
|
| 432 | + } |
|
| 433 | +} |
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/BasicRaceDTO.java
| ... | ... | @@ -0,0 +1,112 @@ |
| 1 | +package com.sap.sailing.domain.common.dto;
|
|
| 2 | +
|
|
| 3 | +import java.util.Date;
|
|
| 4 | +
|
|
| 5 | +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
|
| 6 | +import com.sap.sailing.domain.common.TimingConstants;
|
|
| 7 | +
|
|
| 8 | +/**
|
|
| 9 | + * Master data about a single race that is to be transferred to the client. Holds only timing and a bit
|
|
| 10 | + * of information about the tracked race, if any. See also {@link RaceDTO} for a more comprehensive set
|
|
| 11 | + * of data about a race to be serialized to a client.<p>
|
|
| 12 | + *
|
|
| 13 | + * @author Axel Uhl (d043530)
|
|
| 14 | + *
|
|
| 15 | + */
|
|
| 16 | +public class BasicRaceDTO extends NamedDTO {
|
|
| 17 | + private static final long serialVersionUID = -7884808503795229609L;
|
|
| 18 | +
|
|
| 19 | + public Date startOfRace;
|
|
| 20 | + public Date endOfRace;
|
|
| 21 | + public TrackedRaceDTO trackedRace;
|
|
| 22 | +
|
|
| 23 | + public BasicRaceDTO() {} // for GWT serialization only
|
|
| 24 | +
|
|
| 25 | + public BasicRaceDTO(RegattaAndRaceIdentifier raceIdentifier, TrackedRaceDTO trackedRace) {
|
|
| 26 | + super(raceIdentifier.getRaceName());
|
|
| 27 | + this.trackedRace = trackedRace;
|
|
| 28 | + }
|
|
| 29 | +
|
|
| 30 | + /**
|
|
| 31 | + * @see {@link TrackedRace#isLive} for further explanation.
|
|
| 32 | + * @param serverTimePointAsMillis
|
|
| 33 | + * the time point (in server clock time) at which to determine whether the race is/was live
|
|
| 34 | + * @return <code>true</code> if <code>serverTimePointAsMillis</code> is between (inclusively) the start and end time
|
|
| 35 | + * point of the "live" interval as defined above.
|
|
| 36 | + */
|
|
| 37 | + public boolean isLive(long serverTimePointAsMillis) {
|
|
| 38 | + final Date startOfLivePeriod;
|
|
| 39 | + final Date endOfLivePeriod;
|
|
| 40 | + if (trackedRace == null || !trackedRace.hasGPSData || !trackedRace.hasWindData) {
|
|
| 41 | + startOfLivePeriod = null;
|
|
| 42 | + endOfLivePeriod = null;
|
|
| 43 | + } else {
|
|
| 44 | + if (startOfRace == null) {
|
|
| 45 | + startOfLivePeriod = trackedRace.startOfTracking;
|
|
| 46 | + } else {
|
|
| 47 | + startOfLivePeriod = new Date(startOfRace.getTime() - TimingConstants.PRE_START_PHASE_DURATION_IN_MILLIS);
|
|
| 48 | + }
|
|
| 49 | + if (endOfRace == null) {
|
|
| 50 | + if (trackedRace.timePointOfNewestEvent != null) {
|
|
| 51 | + endOfLivePeriod = new Date(trackedRace.timePointOfNewestEvent.getTime()
|
|
| 52 | + + TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS);
|
|
| 53 | + } else {
|
|
| 54 | + endOfLivePeriod = null;
|
|
| 55 | + }
|
|
| 56 | + } else {
|
|
| 57 | + endOfLivePeriod = new Date(endOfRace.getTime() + TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS);
|
|
| 58 | + }
|
|
| 59 | + }
|
|
| 60 | +
|
|
| 61 | + // if an empty timepoint is given then take the start of the race
|
|
| 62 | + if (serverTimePointAsMillis == 0) {
|
|
| 63 | + serverTimePointAsMillis = startOfLivePeriod.getTime()+1;
|
|
| 64 | + }
|
|
| 65 | +
|
|
| 66 | + // whenLastTrackedRaceWasLive is null if there is no tracked race for fleet, or the tracked race hasn't started yet at the server time
|
|
| 67 | + // when this DTO was assembled, or there were no GPS or wind data
|
|
| 68 | + final boolean result =
|
|
| 69 | + startOfLivePeriod != null &&
|
|
| 70 | + endOfLivePeriod != null &&
|
|
| 71 | + startOfLivePeriod.getTime() <= serverTimePointAsMillis &&
|
|
| 72 | + serverTimePointAsMillis <= endOfLivePeriod.getTime();
|
|
| 73 | + return result;
|
|
| 74 | + }
|
|
| 75 | +
|
|
| 76 | + @Override
|
|
| 77 | + public int hashCode() {
|
|
| 78 | + final int prime = 31;
|
|
| 79 | + int result = super.hashCode();
|
|
| 80 | + result = prime * result + ((endOfRace == null) ? 0 : endOfRace.hashCode());
|
|
| 81 | + result = prime * result + ((startOfRace == null) ? 0 : startOfRace.hashCode());
|
|
| 82 | + result = prime * result + ((trackedRace == null) ? 0 : trackedRace.hashCode());
|
|
| 83 | + return result;
|
|
| 84 | + }
|
|
| 85 | +
|
|
| 86 | + @Override
|
|
| 87 | + public boolean equals(Object obj) {
|
|
| 88 | + if (this == obj)
|
|
| 89 | + return true;
|
|
| 90 | + if (!super.equals(obj))
|
|
| 91 | + return false;
|
|
| 92 | + if (getClass() != obj.getClass())
|
|
| 93 | + return false;
|
|
| 94 | + BasicRaceDTO other = (BasicRaceDTO) obj;
|
|
| 95 | + if (endOfRace == null) {
|
|
| 96 | + if (other.endOfRace != null)
|
|
| 97 | + return false;
|
|
| 98 | + } else if (!endOfRace.equals(other.endOfRace))
|
|
| 99 | + return false;
|
|
| 100 | + if (startOfRace == null) {
|
|
| 101 | + if (other.startOfRace != null)
|
|
| 102 | + return false;
|
|
| 103 | + } else if (!startOfRace.equals(other.startOfRace))
|
|
| 104 | + return false;
|
|
| 105 | + if (trackedRace == null) {
|
|
| 106 | + if (other.trackedRace != null)
|
|
| 107 | + return false;
|
|
| 108 | + } else if (!trackedRace.equals(other.trackedRace))
|
|
| 109 | + return false;
|
|
| 110 | + return true;
|
|
| 111 | + }
|
|
| 112 | +}
|
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardDTO.java
| ... | ... | @@ -62,7 +62,6 @@ public class LeaderboardDTO extends AbstractLeaderboardDTO implements Serializab |
| 62 | 62 | /**
|
| 63 | 63 | * @param uuidGenerator used to provide the {@link #id ID} for this object (see also {@link #getId()}) and for any clones produced
|
| 64 | 64 | * from it by the {@link #clone()} operation.
|
| 65 | - * @param hasOverallDetails TODO
|
|
| 66 | 65 | */
|
| 67 | 66 | public LeaderboardDTO(Date timePointOfLastCorrectionsValidity, String comment, ScoringSchemeType scoringScheme, boolean higherScoreIsBetter, UUIDGenerator uuidGenerator, boolean hasOverallDetails) {
|
| 68 | 67 | initCollections();
|
| ... | ... | @@ -79,7 +78,7 @@ public class LeaderboardDTO extends AbstractLeaderboardDTO implements Serializab |
| 79 | 78 | this.suppressedCompetitors = new ArrayList<CompetitorDTO>();
|
| 80 | 79 | }
|
| 81 | 80 | |
| 82 | - public LeaderboardDTO(String id) {
|
|
| 81 | + protected LeaderboardDTO(String id) {
|
|
| 83 | 82 | initCollections();
|
| 84 | 83 | this.id = id;
|
| 85 | 84 | }
|
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/MetaLeaderboardRaceColumnDTO.java
| ... | ... | @@ -0,0 +1,49 @@ |
| 1 | +package com.sap.sailing.domain.common.dto; |
|
| 2 | + |
|
| 3 | +import java.util.ArrayList; |
|
| 4 | + |
|
| 5 | +public class MetaLeaderboardRaceColumnDTO extends RaceColumnDTO { |
|
| 6 | + |
|
| 7 | + private static final long serialVersionUID = 6152752963316150432L; |
|
| 8 | + |
|
| 9 | + private ArrayList<BasicRaceDTO> raceList = new ArrayList<BasicRaceDTO>(); |
|
| 10 | + |
|
| 11 | + public boolean isLive(FleetDTO fleet, long serverTimePointAsMillis) { |
|
| 12 | + for (BasicRaceDTO race : raceList) { |
|
| 13 | + if (race.isLive(serverTimePointAsMillis)) { |
|
| 14 | + return true; |
|
| 15 | + } |
|
| 16 | + } |
|
| 17 | + return false; |
|
| 18 | + } |
|
| 19 | + |
|
| 20 | + public void addRace(BasicRaceDTO race) { |
|
| 21 | + raceList.add(race); |
|
| 22 | + } |
|
| 23 | + |
|
| 24 | + @Override |
|
| 25 | + public int hashCode() { |
|
| 26 | + final int prime = 31; |
|
| 27 | + int result = super.hashCode(); |
|
| 28 | + result = prime * result + ((raceList == null) ? 0 : raceList.hashCode()); |
|
| 29 | + return result; |
|
| 30 | + } |
|
| 31 | + |
|
| 32 | + @Override |
|
| 33 | + public boolean equals(Object obj) { |
|
| 34 | + if (this == obj) |
|
| 35 | + return true; |
|
| 36 | + if (!super.equals(obj)) |
|
| 37 | + return false; |
|
| 38 | + if (getClass() != obj.getClass()) |
|
| 39 | + return false; |
|
| 40 | + MetaLeaderboardRaceColumnDTO other = (MetaLeaderboardRaceColumnDTO) obj; |
|
| 41 | + if (raceList == null) { |
|
| 42 | + if (other.raceList != null) |
|
| 43 | + return false; |
|
| 44 | + } else if (!raceList.equals(other.raceList)) |
|
| 45 | + return false; |
|
| 46 | + return true; |
|
| 47 | + } |
|
| 48 | + |
|
| 49 | +} |
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceColumnDTOFactory.java
| ... | ... | @@ -7,15 +7,17 @@ public class RaceColumnDTOFactory { |
| 7 | 7 | * @param regattaName |
| 8 | 8 | * must not be <code>null</code> if <code>seriesName</code> is not <code>null</code> |
| 9 | 9 | * @param seriesName |
| 10 | - * when <code>null</code>, a regular {@link RaceColumnDTO} will be produced and initialized from the |
|
| 11 | - * properties passed; otherwise, a {@link RaceColumnInSeriesDTO} will be created. |
|
| 10 | + * when <code>null</code>, a regular {@link RaceColumnDTO} or {@link MetaLeaderboardRaceColumnDTO} will |
|
| 11 | + * be produced (depending on <code>isMetaLeaderboardColumn</code>) and initialized from the properties |
|
| 12 | + * passed; otherwise, a {@link RaceColumnInSeriesDTO} will be created. |
|
| 12 | 13 | */ |
| 13 | - public RaceColumnDTO createRaceColumnDTO(String columnName, boolean isMedal, Double explicitFactor, String regattaName, String seriesName) { |
|
| 14 | + public RaceColumnDTO createRaceColumnDTO(String columnName, boolean isMedal, Double explicitFactor, |
|
| 15 | + String regattaName, String seriesName, boolean isMetaLeaderboardColumn) { |
|
| 14 | 16 | final RaceColumnDTO raceColumnDTO; |
| 15 | 17 | if (seriesName != null) { |
| 16 | 18 | raceColumnDTO = new RaceColumnInSeriesDTO(seriesName, regattaName); |
| 17 | 19 | } else { |
| 18 | - raceColumnDTO = new RaceColumnDTO(); |
|
| 20 | + raceColumnDTO = isMetaLeaderboardColumn ? new MetaLeaderboardRaceColumnDTO() : new RaceColumnDTO(); |
|
| 19 | 21 | } |
| 20 | 22 | fillRaceColumnDTO(raceColumnDTO, columnName, isMedal, explicitFactor); |
| 21 | 23 | return raceColumnDTO; |
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/RaceDTO.java
| ... | ... | @@ -1,10 +1,7 @@ |
| 1 | 1 | package com.sap.sailing.domain.common.dto;
|
| 2 | 2 | |
| 3 | -import java.util.Date;
|
|
| 4 | -
|
|
| 5 | 3 | import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
| 6 | 4 | import com.sap.sailing.domain.common.RegattaNameAndRaceName;
|
| 7 | -import com.sap.sailing.domain.common.TimingConstants;
|
|
| 8 | 5 | |
| 9 | 6 | /**
|
| 10 | 7 | * Master data about a single race that is to be transferred to the client.<p>
|
| ... | ... | @@ -12,7 +9,7 @@ import com.sap.sailing.domain.common.TimingConstants; |
| 12 | 9 | * @author Axel Uhl (d043530)
|
| 13 | 10 | *
|
| 14 | 11 | */
|
| 15 | -public class RaceDTO extends NamedDTO {
|
|
| 12 | +public class RaceDTO extends BasicRaceDTO {
|
|
| 16 | 13 | private static final long serialVersionUID = 2613189982608149975L;
|
| 17 | 14 | |
| 18 | 15 | public enum RaceLiveState { NOT_TRACKED, TRACKED, TRACKED_AND_LIVE, TRACKED_BUT_NOT_SCHEDULED };
|
| ... | ... | @@ -24,13 +21,10 @@ public class RaceDTO extends NamedDTO { |
| 24 | 21 | */
|
| 25 | 22 | public boolean isTracked;
|
| 26 | 23 | |
| 27 | - public Date startOfRace;
|
|
| 28 | - public Date endOfRace;
|
|
| 29 | 24 | public RaceStatusDTO status;
|
| 30 | 25 | |
| 31 | 26 | public PlacemarkOrderDTO places;
|
| 32 | 27 | |
| 33 | - public TrackedRaceDTO trackedRace;
|
|
| 34 | 28 | public TrackedRaceStatisticsDTO trackedRaceStatistics;
|
| 35 | 29 | |
| 36 | 30 | private String regattaName;
|
| ... | ... | @@ -43,21 +37,20 @@ public class RaceDTO extends NamedDTO { |
| 43 | 37 | }
|
| 44 | 38 | |
| 45 | 39 | public RaceDTO(RegattaAndRaceIdentifier raceIdentifier, TrackedRaceDTO trackedRace, boolean isCurrentlyTracked) {
|
| 46 | - super(raceIdentifier.getRaceName());
|
|
| 40 | + super(raceIdentifier, trackedRace);
|
|
| 47 | 41 | this.regattaName = raceIdentifier.getRegattaName();
|
| 48 | - this.trackedRace = trackedRace;
|
|
| 49 | 42 | this.isTracked = isCurrentlyTracked;
|
| 50 | 43 | }
|
| 51 | 44 | |
| 52 | 45 | public RaceLiveState getLiveState(long serverTimePointAsMillis) {
|
| 53 | - RaceLiveState result = RaceLiveState.NOT_TRACKED;
|
|
| 54 | - if(trackedRace != null && trackedRace.hasGPSData && trackedRace.hasWindData) {
|
|
| 46 | + RaceLiveState result = RaceLiveState.NOT_TRACKED;
|
|
| 47 | + if (trackedRace != null && trackedRace.hasGPSData && trackedRace.hasWindData) {
|
|
| 55 | 48 | result = RaceLiveState.TRACKED;
|
| 56 | - if(isLive(serverTimePointAsMillis)) {
|
|
| 49 | + if (isLive(serverTimePointAsMillis)) {
|
|
| 57 | 50 | result = RaceLiveState.TRACKED_AND_LIVE;
|
| 58 | - if(startOfRace == null) {
|
|
| 51 | + if (startOfRace == null) {
|
|
| 59 | 52 | result = RaceLiveState.TRACKED_BUT_NOT_SCHEDULED;
|
| 60 | - }
|
|
| 53 | + }
|
|
| 61 | 54 | }
|
| 62 | 55 | }
|
| 63 | 56 | return result;
|
| ... | ... | @@ -71,64 +64,15 @@ public class RaceDTO extends NamedDTO { |
| 71 | 64 | return regattaName;
|
| 72 | 65 | }
|
| 73 | 66 | |
| 74 | - /**
|
|
| 75 | - * @see {@link TrackedRace#isLive} for further explanation.
|
|
| 76 | - * @param serverTimePointAsMillis
|
|
| 77 | - * the time point (in server clock time) at which to determine whether the race is/was live
|
|
| 78 | - * @return <code>true</code> if <code>serverTimePointAsMillis</code> is between (inclusively) the start and end time
|
|
| 79 | - * point of the "live" interval as defined above.
|
|
| 80 | - */
|
|
| 81 | - public boolean isLive(long serverTimePointAsMillis) {
|
|
| 82 | - final Date startOfLivePeriod;
|
|
| 83 | - final Date endOfLivePeriod;
|
|
| 84 | - if (trackedRace == null || !trackedRace.hasGPSData || !trackedRace.hasWindData) {
|
|
| 85 | - startOfLivePeriod = null;
|
|
| 86 | - endOfLivePeriod = null;
|
|
| 87 | - } else {
|
|
| 88 | - if (startOfRace == null) {
|
|
| 89 | - startOfLivePeriod = trackedRace.startOfTracking;
|
|
| 90 | - } else {
|
|
| 91 | - startOfLivePeriod = new Date(startOfRace.getTime() - TimingConstants.PRE_START_PHASE_DURATION_IN_MILLIS);
|
|
| 92 | - }
|
|
| 93 | - if (endOfRace == null) {
|
|
| 94 | - if (trackedRace.timePointOfNewestEvent != null) {
|
|
| 95 | - endOfLivePeriod = new Date(trackedRace.timePointOfNewestEvent.getTime()
|
|
| 96 | - + TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS);
|
|
| 97 | - } else {
|
|
| 98 | - endOfLivePeriod = null;
|
|
| 99 | - }
|
|
| 100 | - } else {
|
|
| 101 | - endOfLivePeriod = new Date(endOfRace.getTime() + TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS);
|
|
| 102 | - }
|
|
| 103 | - }
|
|
| 104 | -
|
|
| 105 | - // if an empty timepoint is given then take the start of the race
|
|
| 106 | - if (serverTimePointAsMillis == 0) {
|
|
| 107 | - serverTimePointAsMillis = startOfLivePeriod.getTime()+1;
|
|
| 108 | - }
|
|
| 109 | -
|
|
| 110 | - // whenLastTrackedRaceWasLive is null if there is no tracked race for fleet, or the tracked race hasn't started yet at the server time
|
|
| 111 | - // when this DTO was assembled, or there were no GPS or wind data
|
|
| 112 | - final boolean result =
|
|
| 113 | - startOfLivePeriod != null &&
|
|
| 114 | - endOfLivePeriod != null &&
|
|
| 115 | - startOfLivePeriod.getTime() <= serverTimePointAsMillis &&
|
|
| 116 | - serverTimePointAsMillis <= endOfLivePeriod.getTime();
|
|
| 117 | - return result;
|
|
| 118 | - }
|
|
| 119 | -
|
|
| 120 | 67 | @Override
|
| 121 | 68 | public int hashCode() {
|
| 122 | 69 | final int prime = 31;
|
| 123 | 70 | int result = super.hashCode();
|
| 124 | 71 | result = prime * result + ((boatClass == null) ? 0 : boatClass.hashCode());
|
| 125 | - result = prime * result + ((endOfRace == null) ? 0 : endOfRace.hashCode());
|
|
| 126 | 72 | result = prime * result + (isTracked ? 1231 : 1237);
|
| 127 | 73 | result = prime * result + ((places == null) ? 0 : places.hashCode());
|
| 128 | 74 | result = prime * result + ((regattaName == null) ? 0 : regattaName.hashCode());
|
| 129 | - result = prime * result + ((startOfRace == null) ? 0 : startOfRace.hashCode());
|
|
| 130 | 75 | result = prime * result + ((status == null) ? 0 : status.hashCode());
|
| 131 | - result = prime * result + ((trackedRace == null) ? 0 : trackedRace.hashCode());
|
|
| 132 | 76 | return result;
|
| 133 | 77 | }
|
| 134 | 78 | |
| ... | ... | @@ -146,11 +90,6 @@ public class RaceDTO extends NamedDTO { |
| 146 | 90 | return false;
|
| 147 | 91 | } else if (!boatClass.equals(other.boatClass))
|
| 148 | 92 | return false;
|
| 149 | - if (endOfRace == null) {
|
|
| 150 | - if (other.endOfRace != null)
|
|
| 151 | - return false;
|
|
| 152 | - } else if (!endOfRace.equals(other.endOfRace))
|
|
| 153 | - return false;
|
|
| 154 | 93 | if (isTracked != other.isTracked)
|
| 155 | 94 | return false;
|
| 156 | 95 | if (places == null) {
|
| ... | ... | @@ -163,21 +102,11 @@ public class RaceDTO extends NamedDTO { |
| 163 | 102 | return false;
|
| 164 | 103 | } else if (!regattaName.equals(other.regattaName))
|
| 165 | 104 | return false;
|
| 166 | - if (startOfRace == null) {
|
|
| 167 | - if (other.startOfRace != null)
|
|
| 168 | - return false;
|
|
| 169 | - } else if (!startOfRace.equals(other.startOfRace))
|
|
| 170 | - return false;
|
|
| 171 | 105 | if (status == null) {
|
| 172 | 106 | if (other.status != null)
|
| 173 | 107 | return false;
|
| 174 | 108 | } else if (!status.equals(other.status))
|
| 175 | 109 | return false;
|
| 176 | - if (trackedRace == null) {
|
|
| 177 | - if (other.trackedRace != null)
|
|
| 178 | - return false;
|
|
| 179 | - } else if (!trackedRace.equals(other.trackedRace))
|
|
| 180 | - return false;
|
|
| 181 | 110 | return true;
|
| 182 | 111 | }
|
| 183 | 112 | }
|
java/com.sap.sailing.domain.persistence/META-INF/MANIFEST.MF
| ... | ... | @@ -14,7 +14,8 @@ Require-Bundle: com.sap.sse.mongodb, |
| 14 | 14 | com.sap.sailing.server.gateway.serialization, |
| 15 | 15 | com.sap.sse.common, |
| 16 | 16 | com.sap.sse, |
| 17 | - com.sap.sailing.server.gateway.serialization.shared.android |
|
| 17 | + com.sap.sailing.server.gateway.serialization.shared.android, |
|
| 18 | + com.sap.sse.shared.android |
|
| 18 | 19 | Export-Package: com.sap.sailing.domain.persistence, |
| 19 | 20 | com.sap.sailing.domain.persistence.impl; |
| 20 | 21 | x-friends:="com.sap.sailing.domain.persistence.test, |
java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java
| ... | ... | @@ -224,11 +224,11 @@ import com.sap.sse.common.impl.MillisecondsDurationImpl; |
| 224 | 224 | import com.sap.sse.common.impl.MillisecondsTimePoint; |
| 225 | 225 | import com.sap.sse.common.impl.RGBColor; |
| 226 | 226 | import com.sap.sse.common.impl.TimeRangeImpl; |
| 227 | -import com.sap.sse.common.media.ImageDescriptor; |
|
| 228 | -import com.sap.sse.common.media.ImageDescriptorImpl; |
|
| 229 | 227 | import com.sap.sse.common.media.MimeType; |
| 230 | -import com.sap.sse.common.media.VideoDescriptor; |
|
| 231 | -import com.sap.sse.common.media.VideoDescriptorImpl; |
|
| 228 | +import com.sap.sse.shared.media.ImageDescriptor; |
|
| 229 | +import com.sap.sse.shared.media.VideoDescriptor; |
|
| 230 | +import com.sap.sse.shared.media.impl.ImageDescriptorImpl; |
|
| 231 | +import com.sap.sse.shared.media.impl.VideoDescriptorImpl; |
|
| 232 | 232 | |
| 233 | 233 | public class DomainObjectFactoryImpl implements DomainObjectFactory { |
| 234 | 234 | private static final Logger logger = Logger.getLogger(DomainObjectFactoryImpl.class.getName()); |
java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java
| ... | ... | @@ -1,1533 +1,1533 @@ |
| 1 | -package com.sap.sailing.domain.persistence.impl;
|
|
| 2 | -
|
|
| 3 | -import java.io.Serializable;
|
|
| 4 | -import java.net.URL;
|
|
| 5 | -import java.util.List;
|
|
| 6 | -import java.util.Map.Entry;
|
|
| 7 | -import java.util.logging.Level;
|
|
| 8 | -import java.util.logging.Logger;
|
|
| 9 | -
|
|
| 10 | -import org.bson.types.ObjectId;
|
|
| 11 | -import org.json.simple.JSONObject;
|
|
| 12 | -
|
|
| 13 | -import com.mongodb.BasicDBList;
|
|
| 14 | -import com.mongodb.BasicDBObject;
|
|
| 15 | -import com.mongodb.BasicDBObjectBuilder;
|
|
| 16 | -import com.mongodb.DB;
|
|
| 17 | -import com.mongodb.DBCollection;
|
|
| 18 | -import com.mongodb.DBObject;
|
|
| 19 | -import com.mongodb.WriteConcern;
|
|
| 20 | -import com.mongodb.util.JSON;
|
|
| 21 | -import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
|
|
| 22 | -import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent;
|
|
| 23 | -import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent;
|
|
| 24 | -import com.sap.sailing.domain.abstractlog.race.RaceLogDependentStartTimeEvent;
|
|
| 25 | -import com.sap.sailing.domain.abstractlog.race.RaceLogEndOfTrackingEvent;
|
|
| 26 | -import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
|
|
| 27 | -import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningConfirmedEvent;
|
|
| 28 | -import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningListChangedEvent;
|
|
| 29 | -import com.sap.sailing.domain.abstractlog.race.RaceLogFixedMarkPassingEvent;
|
|
| 30 | -import com.sap.sailing.domain.abstractlog.race.RaceLogFlagEvent;
|
|
| 31 | -import com.sap.sailing.domain.abstractlog.race.RaceLogGateLineOpeningTimeEvent;
|
|
| 32 | -import com.sap.sailing.domain.abstractlog.race.RaceLogPassChangeEvent;
|
|
| 33 | -import com.sap.sailing.domain.abstractlog.race.RaceLogPathfinderEvent;
|
|
| 34 | -import com.sap.sailing.domain.abstractlog.race.RaceLogProtestStartTimeEvent;
|
|
| 35 | -import com.sap.sailing.domain.abstractlog.race.RaceLogRaceStatusEvent;
|
|
| 36 | -import com.sap.sailing.domain.abstractlog.race.RaceLogRevokeEvent;
|
|
| 37 | -import com.sap.sailing.domain.abstractlog.race.RaceLogStartOfTrackingEvent;
|
|
| 38 | -import com.sap.sailing.domain.abstractlog.race.RaceLogStartProcedureChangedEvent;
|
|
| 39 | -import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent;
|
|
| 40 | -import com.sap.sailing.domain.abstractlog.race.RaceLogSuppressedMarkPassingsEvent;
|
|
| 41 | -import com.sap.sailing.domain.abstractlog.race.RaceLogWindFixEvent;
|
|
| 42 | -import com.sap.sailing.domain.abstractlog.race.scoring.RaceLogAdditionalScoringInformationEvent;
|
|
| 43 | -import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogCloseOpenEndedDeviceMappingEvent;
|
|
| 44 | -import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDefineMarkEvent;
|
|
| 45 | -import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDenoteForTrackingEvent;
|
|
| 46 | -import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDeviceCompetitorMappingEvent;
|
|
| 47 | -import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDeviceMarkMappingEvent;
|
|
| 48 | -import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogRegisterCompetitorEvent;
|
|
| 49 | -import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogStartTrackingEvent;
|
|
| 50 | -import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent;
|
|
| 51 | -import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogCloseOpenEndedDeviceMappingEvent;
|
|
| 52 | -import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorMappingEvent;
|
|
| 53 | -import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMarkMappingEvent;
|
|
| 54 | -import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRegisterCompetitorEvent;
|
|
| 55 | -import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRevokeEvent;
|
|
| 56 | -import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogSetCompetitorTimeOnDistanceAllowancePerNauticalMileEvent;
|
|
| 57 | -import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogSetCompetitorTimeOnTimeFactorEvent;
|
|
| 58 | -import com.sap.sailing.domain.abstractlog.shared.events.DeviceMappingEvent;
|
|
| 59 | -import com.sap.sailing.domain.base.Competitor;
|
|
| 60 | -import com.sap.sailing.domain.base.ControlPoint;
|
|
| 61 | -import com.sap.sailing.domain.base.ControlPointWithTwoMarks;
|
|
| 62 | -import com.sap.sailing.domain.base.CourseArea;
|
|
| 63 | -import com.sap.sailing.domain.base.CourseBase;
|
|
| 64 | -import com.sap.sailing.domain.base.Event;
|
|
| 65 | -import com.sap.sailing.domain.base.Fleet;
|
|
| 66 | -import com.sap.sailing.domain.base.Mark;
|
|
| 67 | -import com.sap.sailing.domain.base.RaceColumn;
|
|
| 68 | -import com.sap.sailing.domain.base.RaceDefinition;
|
|
| 69 | -import com.sap.sailing.domain.base.Regatta;
|
|
| 70 | -import com.sap.sailing.domain.base.RemoteSailingServerReference;
|
|
| 71 | -import com.sap.sailing.domain.base.SailingServerConfiguration;
|
|
| 72 | -import com.sap.sailing.domain.base.Series;
|
|
| 73 | -import com.sap.sailing.domain.base.Venue;
|
|
| 74 | -import com.sap.sailing.domain.base.Waypoint;
|
|
| 75 | -import com.sap.sailing.domain.base.configuration.DeviceConfiguration;
|
|
| 76 | -import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher;
|
|
| 77 | -import com.sap.sailing.domain.base.configuration.RegattaConfiguration;
|
|
| 78 | -import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationMatcherMulti;
|
|
| 79 | -import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationMatcherSingle;
|
|
| 80 | -import com.sap.sailing.domain.base.impl.FleetImpl;
|
|
| 81 | -import com.sap.sailing.domain.common.Bearing;
|
|
| 82 | -import com.sap.sailing.domain.common.MaxPointsReason;
|
|
| 83 | -import com.sap.sailing.domain.common.PassingInstruction;
|
|
| 84 | -import com.sap.sailing.domain.common.Positioned;
|
|
| 85 | -import com.sap.sailing.domain.common.RaceIdentifier;
|
|
| 86 | -import com.sap.sailing.domain.common.Speed;
|
|
| 87 | -import com.sap.sailing.domain.common.SpeedWithBearing;
|
|
| 88 | -import com.sap.sailing.domain.common.Wind;
|
|
| 89 | -import com.sap.sailing.domain.common.WindSource;
|
|
| 90 | -import com.sap.sailing.domain.common.racelog.tracking.TransformationException;
|
|
| 91 | -import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
|
|
| 92 | -import com.sap.sailing.domain.leaderboard.Leaderboard;
|
|
| 93 | -import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
|
|
| 94 | -import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
|
|
| 95 | -import com.sap.sailing.domain.leaderboard.ResultDiscardingRule;
|
|
| 96 | -import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
|
|
| 97 | -import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
|
|
| 98 | -import com.sap.sailing.domain.persistence.MongoObjectFactory;
|
|
| 99 | -import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler;
|
|
| 100 | -import com.sap.sailing.domain.persistence.racelog.tracking.impl.PlaceHolderDeviceIdentifierMongoHandler;
|
|
| 101 | -import com.sap.sailing.domain.racelog.RaceLogIdentifier;
|
|
| 102 | -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
|
|
| 103 | -import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
|
|
| 104 | -import com.sap.sailing.domain.tracking.TrackedRace;
|
|
| 105 | -import com.sap.sailing.domain.tracking.TrackedRegatta;
|
|
| 106 | -import com.sap.sailing.domain.tracking.WindTrack;
|
|
| 107 | -import com.sap.sailing.server.gateway.serialization.JsonSerializer;
|
|
| 108 | -import com.sap.sailing.server.gateway.serialization.impl.CompetitorJsonSerializer;
|
|
| 109 | -import com.sap.sailing.server.gateway.serialization.impl.DeviceConfigurationJsonSerializer;
|
|
| 110 | -import com.sap.sailing.server.gateway.serialization.impl.RegattaConfigurationJsonSerializer;
|
|
| 111 | -import com.sap.sse.common.Duration;
|
|
| 112 | -import com.sap.sse.common.NoCorrespondingServiceRegisteredException;
|
|
| 113 | -import com.sap.sse.common.TimePoint;
|
|
| 114 | -import com.sap.sse.common.TimeRange;
|
|
| 115 | -import com.sap.sse.common.Timed;
|
|
| 116 | -import com.sap.sse.common.TypeBasedServiceFinder;
|
|
| 117 | -import com.sap.sse.common.TypeBasedServiceFinderFactory;
|
|
| 118 | -import com.sap.sse.common.Util;
|
|
| 119 | -import com.sap.sse.common.impl.MillisecondsTimePoint;
|
|
| 120 | -import com.sap.sse.common.media.ImageDescriptor;
|
|
| 121 | -import com.sap.sse.common.media.VideoDescriptor;
|
|
| 122 | -
|
|
| 123 | -public class MongoObjectFactoryImpl implements MongoObjectFactory {
|
|
| 124 | - private static Logger logger = Logger.getLogger(MongoObjectFactoryImpl.class.getName());
|
|
| 125 | - private final DB database;
|
|
| 126 | - private final CompetitorJsonSerializer competitorSerializer = CompetitorJsonSerializer.create();
|
|
| 127 | - private final TypeBasedServiceFinder<DeviceIdentifierMongoHandler> deviceIdentifierServiceFinder;
|
|
| 128 | -
|
|
| 129 | - /**
|
|
| 130 | - * Uses <code>null</code> for the device type service finder and hence will be unable to store device identifiers.
|
|
| 131 | - * Use this constructor only for testing purposes or in cases where there will happen absolutely no access to
|
|
| 132 | - * {@link DeviceIdentifier} objects.
|
|
| 133 | - */
|
|
| 134 | - public MongoObjectFactoryImpl(DB database) {
|
|
| 135 | - this(database, /* deviceTypeServiceFinder */ null);
|
|
| 136 | - }
|
|
| 137 | -
|
|
| 138 | - public MongoObjectFactoryImpl(DB database, TypeBasedServiceFinderFactory serviceFinderFactory) {
|
|
| 139 | - this.database = database;
|
|
| 140 | - if (serviceFinderFactory != null) {
|
|
| 141 | - this.deviceIdentifierServiceFinder = serviceFinderFactory.createServiceFinder(DeviceIdentifierMongoHandler.class);
|
|
| 142 | - this.deviceIdentifierServiceFinder.setFallbackService(new PlaceHolderDeviceIdentifierMongoHandler());
|
|
| 143 | - } else {
|
|
| 144 | - this.deviceIdentifierServiceFinder = null;
|
|
| 145 | - }
|
|
| 146 | - }
|
|
| 147 | -
|
|
| 148 | - @Override
|
|
| 149 | - public DB getDatabase() {
|
|
| 150 | - return database;
|
|
| 151 | - }
|
|
| 152 | -
|
|
| 153 | - public DBObject storeWind(Wind wind) {
|
|
| 154 | - DBObject result = new BasicDBObject();
|
|
| 155 | - storePositioned(wind, result);
|
|
| 156 | - storeTimed(wind, result);
|
|
| 157 | - storeSpeedWithBearing(wind, result);
|
|
| 158 | - return result;
|
|
| 159 | - }
|
|
| 160 | -
|
|
| 161 | - public static void storeTimePoint(TimePoint timePoint, DBObject result, String fieldName) {
|
|
| 162 | - if (timePoint != null) {
|
|
| 163 | - result.put(fieldName, timePoint.asMillis());
|
|
| 164 | - }
|
|
| 165 | - }
|
|
| 166 | -
|
|
| 167 | - public static void storeTimePoint(TimePoint timePoint, DBObject result, FieldNames field) {
|
|
| 168 | - storeTimePoint(timePoint, result, field.name());
|
|
| 169 | - }
|
|
| 170 | -
|
|
| 171 | - public static void storeTimeRange(TimeRange timeRange, DBObject result, FieldNames field) {
|
|
| 172 | - if (timeRange != null) {
|
|
| 173 | - DBObject timeRangeObj = new BasicDBObject();
|
|
| 174 | - storeTimePoint(timeRange.from(), timeRangeObj, FieldNames.FROM_MILLIS);
|
|
| 175 | - storeTimePoint(timeRange.to(), timeRangeObj, FieldNames.TO_MILLIS);
|
|
| 176 | - result.put(field.name(), timeRangeObj);
|
|
| 177 | - }
|
|
| 178 | - }
|
|
| 179 | -
|
|
| 180 | - public void storeTimed(Timed timed, DBObject result) {
|
|
| 181 | - if (timed.getTimePoint() != null) {
|
|
| 182 | - storeTimePoint(timed.getTimePoint(), result, FieldNames.TIME_AS_MILLIS);
|
|
| 183 | - }
|
|
| 184 | - }
|
|
| 185 | -
|
|
| 186 | - public void storeSpeedWithBearing(SpeedWithBearing speedWithBearing, DBObject result) {
|
|
| 187 | - storeSpeed(speedWithBearing, result);
|
|
| 188 | - storeBearing(speedWithBearing.getBearing(), result);
|
|
| 189 | -
|
|
| 190 | - }
|
|
| 191 | -
|
|
| 192 | - public void storeBearing(Bearing bearing, DBObject result) {
|
|
| 193 | - result.put(FieldNames.DEGREE_BEARING.name(), bearing.getDegrees());
|
|
| 194 | - }
|
|
| 195 | -
|
|
| 196 | - public void storeSpeed(Speed speed, DBObject result) {
|
|
| 197 | - result.put(FieldNames.KNOT_SPEED.name(), speed.getKnots());
|
|
| 198 | - }
|
|
| 199 | -
|
|
| 200 | - public void storePositioned(Positioned positioned, DBObject result) {
|
|
| 201 | - if (positioned.getPosition() != null) {
|
|
| 202 | - result.put(FieldNames.LAT_DEG.name(), positioned.getPosition().getLatDeg());
|
|
| 203 | - result.put(FieldNames.LNG_DEG.name(), positioned.getPosition().getLngDeg());
|
|
| 204 | - }
|
|
| 205 | - }
|
|
| 206 | -
|
|
| 207 | - @Override
|
|
| 208 | - public void addWindTrackDumper(TrackedRegatta trackedRegatta, TrackedRace trackedRace, WindSource windSource) {
|
|
| 209 | - WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource);
|
|
| 210 | - windTrack.addListener(new MongoWindListener(trackedRace, trackedRegatta.getRegatta().getName(), windSource, this, database));
|
|
| 211 | - }
|
|
| 212 | -
|
|
| 213 | - public DBCollection getWindTrackCollection() {
|
|
| 214 | - DBCollection result = database.getCollection(CollectionNames.WIND_TRACKS.name());
|
|
| 215 | - result.createIndex(new BasicDBObject(FieldNames.REGATTA_NAME.name(), null));
|
|
| 216 | - return result;
|
|
| 217 | - }
|
|
| 218 | -
|
|
| 219 | - public DBCollection getGPSFixCollection() {
|
|
| 220 | - DBCollection gpsFixCollection = database.getCollection(CollectionNames.GPS_FIXES.name());
|
|
| 221 | - DBObject index = new BasicDBObject();
|
|
| 222 | - index.put(FieldNames.DEVICE_ID.name(), null);
|
|
| 223 | - index.put(FieldNames.TIME_AS_MILLIS.name(), null);
|
|
| 224 | - gpsFixCollection.createIndex(index);
|
|
| 225 | - return gpsFixCollection;
|
|
| 226 | - }
|
|
| 227 | -
|
|
| 228 | - public DBCollection getGPSFixMetadataCollection() {
|
|
| 229 | - DBCollection collection = database.getCollection(CollectionNames.GPS_FIXES_METADATA.name());
|
|
| 230 | - DBObject index = new BasicDBObject();
|
|
| 231 | - index.put(FieldNames.DEVICE_ID.name(), null);
|
|
| 232 | - collection.createIndex(index);
|
|
| 233 | - return collection;
|
|
| 234 | - }
|
|
| 235 | -
|
|
| 236 | - /**
|
|
| 237 | - * @param regattaName
|
|
| 238 | - * the regatta name is stored only for human readability purposes because a time stamp may be a bit unhandy for
|
|
| 239 | - * identifying where the wind fix was collected
|
|
| 240 | - */
|
|
| 241 | - public DBObject storeWindTrackEntry(RaceDefinition race, String regattaName, WindSource windSource, Wind wind) {
|
|
| 242 | - BasicDBObject result = new BasicDBObject();
|
|
| 243 | - result.put(FieldNames.RACE_ID.name(), race.getId());
|
|
| 244 | - result.put(FieldNames.REGATTA_NAME.name(), regattaName);
|
|
| 245 | - result.put(FieldNames.WIND_SOURCE_NAME.name(), windSource.name());
|
|
| 246 | - if (windSource.getId() != null) {
|
|
| 247 | - result.put(FieldNames.WIND_SOURCE_ID.name(), windSource.getId());
|
|
| 248 | - }
|
|
| 249 | - result.put(FieldNames.WIND.name(), storeWind(wind));
|
|
| 250 | - return result;
|
|
| 251 | - }
|
|
| 252 | -
|
|
| 253 | - private void storeRaceIdentifiers(RaceColumn raceColumn, DBObject dbObject) {
|
|
| 254 | - BasicDBObject raceIdentifiersPerFleet = new BasicDBObject();
|
|
| 255 | - for (Fleet fleet : raceColumn.getFleets()) {
|
|
| 256 | - RaceIdentifier raceIdentifier = raceColumn.getRaceIdentifier(fleet);
|
|
| 257 | - if (raceIdentifier != null) {
|
|
| 258 | - DBObject raceIdentifierForFleet = new BasicDBObject();
|
|
| 259 | - storeRaceIdentifier(raceIdentifierForFleet, raceIdentifier);
|
|
| 260 | - raceIdentifiersPerFleet.put(MongoUtils.escapeDollarAndDot(fleet.getName()), raceIdentifierForFleet);
|
|
| 261 | - }
|
|
| 262 | - }
|
|
| 263 | - dbObject.put(FieldNames.RACE_IDENTIFIERS.name(), raceIdentifiersPerFleet);
|
|
| 264 | - }
|
|
| 265 | -
|
|
| 266 | - private void storeRaceIdentifier(DBObject dbObject, RaceIdentifier raceIdentifier) {
|
|
| 267 | - if (raceIdentifier != null) {
|
|
| 268 | - dbObject.put(FieldNames.EVENT_NAME.name(), raceIdentifier.getRegattaName());
|
|
| 269 | - dbObject.put(FieldNames.RACE_NAME.name(), raceIdentifier.getRaceName());
|
|
| 270 | - }
|
|
| 271 | - }
|
|
| 272 | -
|
|
| 273 | - @Override
|
|
| 274 | - public void storeLeaderboard(Leaderboard leaderboard) {
|
|
| 275 | - DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
|
|
| 276 | - try {
|
|
| 277 | - leaderboardCollection.createIndex(new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), 1));
|
|
| 278 | - } catch (NullPointerException npe) {
|
|
| 279 | - // sometimes, for reasons yet to be clarified, ensuring an index on the name field causes an NPE
|
|
| 280 | - logger.log(Level.SEVERE, "storeLeaderboard", npe);
|
|
| 281 | - }
|
|
| 282 | - BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), leaderboard.getName());
|
|
| 283 | - BasicDBObject dbLeaderboard = new BasicDBObject();
|
|
| 284 | - dbLeaderboard.put(FieldNames.LEADERBOARD_NAME.name(), leaderboard.getName());
|
|
| 285 | - if (leaderboard.getDisplayName() != null) {
|
|
| 286 | - dbLeaderboard.put(FieldNames.LEADERBOARD_DISPLAY_NAME.name(), leaderboard.getDisplayName());
|
|
| 287 | - }
|
|
| 288 | - BasicDBList dbSuppressedCompetitorIds = new BasicDBList();
|
|
| 289 | - for (Competitor suppressedCompetitor : leaderboard.getSuppressedCompetitors()) {
|
|
| 290 | - dbSuppressedCompetitorIds.add(suppressedCompetitor.getId());
|
|
| 291 | - }
|
|
| 292 | - dbLeaderboard.put(FieldNames.LEADERBOARD_SUPPRESSED_COMPETITOR_IDS.name(), dbSuppressedCompetitorIds);
|
|
| 293 | - if (leaderboard instanceof FlexibleLeaderboard) {
|
|
| 294 | - storeFlexibleLeaderboard((FlexibleLeaderboard) leaderboard, dbLeaderboard);
|
|
| 295 | - } else if (leaderboard instanceof RegattaLeaderboard) {
|
|
| 296 | - storeRegattaLeaderboard((RegattaLeaderboard) leaderboard, dbLeaderboard);
|
|
| 297 | - } else {
|
|
| 298 | - // at least store the scoring scheme
|
|
| 299 | - dbLeaderboard.put(FieldNames.SCORING_SCHEME_TYPE.name(), leaderboard.getScoringScheme().getType().name());
|
|
| 300 | - }
|
|
| 301 | - if (leaderboard.getDefaultCourseArea() != null) {
|
|
| 302 | - dbLeaderboard.put(FieldNames.COURSE_AREA_ID.name(), leaderboard.getDefaultCourseArea().getId().toString());
|
|
| 303 | - } else {
|
|
| 304 | - dbLeaderboard.put(FieldNames.COURSE_AREA_ID.name(), null);
|
|
| 305 | - }
|
|
| 306 | - storeColumnFactors(leaderboard, dbLeaderboard);
|
|
| 307 | - storeLeaderboardCorrectionsAndDiscards(leaderboard, dbLeaderboard);
|
|
| 308 | - leaderboardCollection.update(query, dbLeaderboard, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
|
|
| 309 | - }
|
|
| 310 | -
|
|
| 311 | - private void storeColumnFactors(Leaderboard leaderboard, BasicDBObject dbLeaderboard) {
|
|
| 312 | - DBObject raceColumnFactors = new BasicDBObject();
|
|
| 313 | - for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
|
|
| 314 | - Double explicitFactor = raceColumn.getExplicitFactor();
|
|
| 315 | - if (explicitFactor != null) {
|
|
| 316 | - raceColumnFactors.put(MongoUtils.escapeDollarAndDot(raceColumn.getName()), explicitFactor);
|
|
| 317 | - }
|
|
| 318 | - }
|
|
| 319 | - dbLeaderboard.put(FieldNames.LEADERBOARD_COLUMN_FACTORS.name(), raceColumnFactors);
|
|
| 320 | - }
|
|
| 321 | -
|
|
| 322 | - private void storeRegattaLeaderboard(RegattaLeaderboard leaderboard, DBObject dbLeaderboard) {
|
|
| 323 | - dbLeaderboard.put(FieldNames.REGATTA_NAME.name(), leaderboard.getRegatta().getName());
|
|
| 324 | - }
|
|
| 325 | -
|
|
| 326 | - private void storeFlexibleLeaderboard(FlexibleLeaderboard leaderboard, BasicDBObject dbLeaderboard) {
|
|
| 327 | - BasicDBList dbRaceColumns = new BasicDBList();
|
|
| 328 | - dbLeaderboard.put(FieldNames.SCORING_SCHEME_TYPE.name(), leaderboard.getScoringScheme().getType().name());
|
|
| 329 | - dbLeaderboard.put(FieldNames.LEADERBOARD_COLUMNS.name(), dbRaceColumns);
|
|
| 330 | - for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
|
|
| 331 | - BasicDBObject dbRaceColumn = storeRaceColumn(raceColumn);
|
|
| 332 | - dbRaceColumns.add(dbRaceColumn);
|
|
| 333 | - }
|
|
| 334 | - }
|
|
| 335 | -
|
|
| 336 | - private void storeLeaderboardCorrectionsAndDiscards(Leaderboard leaderboard, BasicDBObject dbLeaderboard) {
|
|
| 337 | - if (leaderboard.hasCarriedPoints()) {
|
|
| 338 | - BasicDBList dbCarriedPoints = new BasicDBList();
|
|
| 339 | - dbLeaderboard.put(FieldNames.LEADERBOARD_CARRIED_POINTS_BY_ID.name(), dbCarriedPoints);
|
|
| 340 | - for (Entry<Competitor, Double> competitorWithCarriedPoints : leaderboard
|
|
| 341 | - .getCompetitorsForWhichThereAreCarriedPoints().entrySet()) {
|
|
| 342 | - double carriedPoints = competitorWithCarriedPoints.getValue();
|
|
| 343 | - Competitor competitor = competitorWithCarriedPoints.getKey();
|
|
| 344 | - DBObject dbCarriedPointsForCompetitor = new BasicDBObject();
|
|
| 345 | - dbCarriedPointsForCompetitor.put(FieldNames.COMPETITOR_ID.name(), competitor.getId());
|
|
| 346 | - dbCarriedPointsForCompetitor.put(FieldNames.LEADERBOARD_CARRIED_POINTS.name(), carriedPoints);
|
|
| 347 | - dbCarriedPoints.add(dbCarriedPointsForCompetitor);
|
|
| 348 | - }
|
|
| 349 | - }
|
|
| 350 | - BasicDBObject dbScoreCorrections = new BasicDBObject();
|
|
| 351 | - storeScoreCorrections(leaderboard, dbScoreCorrections);
|
|
| 352 | - dbLeaderboard.put(FieldNames.LEADERBOARD_SCORE_CORRECTIONS.name(), dbScoreCorrections);
|
|
| 353 | - final ResultDiscardingRule resultDiscardingRule = leaderboard.getResultDiscardingRule();
|
|
| 354 | - storeResultDiscardingRule(dbLeaderboard, resultDiscardingRule, FieldNames.LEADERBOARD_DISCARDING_THRESHOLDS);
|
|
| 355 | - BasicDBList competitorDisplayNames = new BasicDBList();
|
|
| 356 | - for (Competitor competitor : leaderboard.getCompetitors()) {
|
|
| 357 | - String displayNameForCompetitor = leaderboard.getDisplayName(competitor);
|
|
| 358 | - if (displayNameForCompetitor != null) {
|
|
| 359 | - DBObject dbDisplayName = new BasicDBObject();
|
|
| 360 | - dbDisplayName.put(FieldNames.COMPETITOR_ID.name(), competitor.getId());
|
|
| 361 | - dbDisplayName.put(FieldNames.COMPETITOR_DISPLAY_NAME.name(), displayNameForCompetitor);
|
|
| 362 | - competitorDisplayNames.add(dbDisplayName);
|
|
| 363 | - }
|
|
| 364 | - }
|
|
| 365 | - dbLeaderboard.put(FieldNames.LEADERBOARD_COMPETITOR_DISPLAY_NAMES.name(), competitorDisplayNames);
|
|
| 366 | - }
|
|
| 367 | -
|
|
| 368 | - /**
|
|
| 369 | - * Stores the result discarding rule to <code>dbObject</code>'s field identified by <code>field</code> if the result discarding
|
|
| 370 | - * rule is not <code>null</code> and is of type {@link ThresholdBasedResultDiscardingRule}. Otherwise, it is assumed that the
|
|
| 371 | - * result discarding rule is otherwise implicitly obtained, e.g., from a definition of a regatta with its series, stored elsewhere.
|
|
| 372 | - */
|
|
| 373 | - private void storeResultDiscardingRule(DBObject dbObject,
|
|
| 374 | - final ResultDiscardingRule resultDiscardingRule, FieldNames field) {
|
|
| 375 | - if (resultDiscardingRule != null && resultDiscardingRule instanceof ThresholdBasedResultDiscardingRule) {
|
|
| 376 | - BasicDBList dbResultDiscardingThresholds = new BasicDBList();
|
|
| 377 | - for (int threshold : ((ThresholdBasedResultDiscardingRule) resultDiscardingRule).getDiscardIndexResultsStartingWithHowManyRaces()) {
|
|
| 378 | - dbResultDiscardingThresholds.add(threshold);
|
|
| 379 | - }
|
|
| 380 | - dbObject.put(field.name(), dbResultDiscardingThresholds);
|
|
| 381 | - }
|
|
| 382 | - }
|
|
| 383 | -
|
|
| 384 | - private BasicDBObject storeRaceColumn(RaceColumn raceColumn) {
|
|
| 385 | - BasicDBObject dbRaceColumn = new BasicDBObject();
|
|
| 386 | - dbRaceColumn.put(FieldNames.LEADERBOARD_COLUMN_NAME.name(), raceColumn.getName());
|
|
| 387 | - dbRaceColumn.put(FieldNames.LEADERBOARD_IS_MEDAL_RACE_COLUMN.name(), raceColumn.isMedalRace());
|
|
| 388 | - storeRaceIdentifiers(raceColumn, dbRaceColumn);
|
|
| 389 | - return dbRaceColumn;
|
|
| 390 | - }
|
|
| 391 | -
|
|
| 392 | - private void storeScoreCorrections(Leaderboard leaderboard, BasicDBObject dbScoreCorrections) {
|
|
| 393 | - TimePoint now = MillisecondsTimePoint.now();
|
|
| 394 | - SettableScoreCorrection scoreCorrection = leaderboard.getScoreCorrection();
|
|
| 395 | - for (RaceColumn raceColumn : scoreCorrection.getRaceColumnsThatHaveCorrections()) {
|
|
| 396 | - BasicDBList dbCorrectionForRace = new BasicDBList();
|
|
| 397 | - for (Competitor competitor : scoreCorrection.getCompetitorsThatHaveCorrectionsIn(raceColumn)) {
|
|
| 398 | - // TODO bug 655: make score corrections time dependent
|
|
| 399 | - if (scoreCorrection.isScoreCorrected(competitor, raceColumn, now)) {
|
|
| 400 | - BasicDBObject dbCorrectionForCompetitor = new BasicDBObject();
|
|
| 401 | - dbCorrectionForCompetitor.put(FieldNames.COMPETITOR_ID.name(), competitor.getId());
|
|
| 402 | - MaxPointsReason maxPointsReason = scoreCorrection.getMaxPointsReason(competitor, raceColumn, now);
|
|
| 403 | - if (maxPointsReason != MaxPointsReason.NONE) {
|
|
| 404 | - dbCorrectionForCompetitor.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON.name(),
|
|
| 405 | - maxPointsReason.name());
|
|
| 406 | - }
|
|
| 407 | - Double explicitScoreCorrection = scoreCorrection
|
|
| 408 | - .getExplicitScoreCorrection(competitor, raceColumn);
|
|
| 409 | - if (explicitScoreCorrection != null) {
|
|
| 410 | - dbCorrectionForCompetitor.put(FieldNames.LEADERBOARD_CORRECTED_SCORE.name(),
|
|
| 411 | - explicitScoreCorrection);
|
|
| 412 | - }
|
|
| 413 | - dbCorrectionForRace.add(dbCorrectionForCompetitor);
|
|
| 414 | - }
|
|
| 415 | - }
|
|
| 416 | - if (!dbCorrectionForRace.isEmpty()) {
|
|
| 417 | - // using the column name as the key for the score corrections requires re-writing the score corrections
|
|
| 418 | - // of a meta-leaderboard if the name of one of its leaderboards changes
|
|
| 419 | - dbScoreCorrections.put(MongoUtils.escapeDollarAndDot(raceColumn.getName()), dbCorrectionForRace);
|
|
| 420 | - }
|
|
| 421 | - }
|
|
| 422 | - final TimePoint timePointOfLastCorrectionsValidity = scoreCorrection.getTimePointOfLastCorrectionsValidity();
|
|
| 423 | - if (timePointOfLastCorrectionsValidity != null) {
|
|
| 424 | - dbScoreCorrections.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_TIMESTAMP.name(), timePointOfLastCorrectionsValidity.asMillis());
|
|
| 425 | - }
|
|
| 426 | - if (scoreCorrection.getComment() != null) {
|
|
| 427 | - dbScoreCorrections.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_COMMENT.name(), scoreCorrection.getComment());
|
|
| 428 | - }
|
|
| 429 | - }
|
|
| 430 | -
|
|
| 431 | - @Override
|
|
| 432 | - public void removeLeaderboard(String leaderboardName) {
|
|
| 433 | - DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
|
|
| 434 | - BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), leaderboardName);
|
|
| 435 | - leaderboardCollection.remove(query);
|
|
| 436 | - }
|
|
| 437 | -
|
|
| 438 | - @Override
|
|
| 439 | - public void renameLeaderboard(String oldName, String newName) {
|
|
| 440 | - DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
|
|
| 441 | - BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), oldName);
|
|
| 442 | - BasicDBObject renameUpdate = new BasicDBObject("$set", new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), newName));
|
|
| 443 | - leaderboardCollection.update(query, renameUpdate, /* upsert */ true, /* multi */ false, WriteConcern.SAFE);
|
|
| 444 | - }
|
|
| 445 | -
|
|
| 446 | - @Override
|
|
| 447 | - public void storeLeaderboardGroup(LeaderboardGroup leaderboardGroup) {
|
|
| 448 | - DBCollection leaderboardGroupCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name());
|
|
| 449 | - DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
|
|
| 450 | -
|
|
| 451 | - try {
|
|
| 452 | - leaderboardGroupCollection.createIndex(new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), 1));
|
|
| 453 | - } catch (NullPointerException npe) {
|
|
| 454 | - // sometimes, for reasons yet to be clarified, ensuring an index on the name field causes an NPE
|
|
| 455 | - logger.log(Level.SEVERE, "storeLeaderboardGroup", npe);
|
|
| 456 | - }
|
|
| 457 | - BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), leaderboardGroup.getName());
|
|
| 458 | - BasicDBObject dbLeaderboardGroup = new BasicDBObject();
|
|
| 459 | - dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_UUID.name(), leaderboardGroup.getId());
|
|
| 460 | - dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_NAME.name(), leaderboardGroup.getName());
|
|
| 461 | - dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_DESCRIPTION.name(), leaderboardGroup.getDescription());
|
|
| 462 | - dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_DISPLAY_NAME.name(), leaderboardGroup.getDisplayName());
|
|
| 463 | - dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_DISPLAY_IN_REVERSE_ORDER.name(), leaderboardGroup.isDisplayGroupsInReverseOrder());
|
|
| 464 | - final Leaderboard overallLeaderboard = leaderboardGroup.getOverallLeaderboard();
|
|
| 465 | - if (overallLeaderboard != null) {
|
|
| 466 | - BasicDBObject overallLeaderboardQuery = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), overallLeaderboard.getName());
|
|
| 467 | - DBObject dbOverallLeaderboard = leaderboardCollection.findOne(overallLeaderboardQuery);
|
|
| 468 | - if (dbOverallLeaderboard == null) {
|
|
| 469 | - storeLeaderboard(overallLeaderboard);
|
|
| 470 | - dbOverallLeaderboard = leaderboardCollection.findOne(overallLeaderboardQuery);
|
|
| 471 | - }
|
|
| 472 | - ObjectId dbOverallLeaderboardId = (ObjectId) dbOverallLeaderboard.get("_id");
|
|
| 473 | - dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_OVERALL_LEADERBOARD.name(), dbOverallLeaderboardId);
|
|
| 474 | - }
|
|
| 475 | - BasicDBList dbLeaderboardIds = new BasicDBList();
|
|
| 476 | - for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
|
|
| 477 | - BasicDBObject leaderboardQuery = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), leaderboard.getName());
|
|
| 478 | - DBObject dbLeaderboard = leaderboardCollection.findOne(leaderboardQuery);
|
|
| 479 | - if (dbLeaderboard == null) {
|
|
| 480 | - storeLeaderboard(leaderboard);
|
|
| 481 | - dbLeaderboard = leaderboardCollection.findOne(leaderboardQuery);
|
|
| 482 | - }
|
|
| 483 | - ObjectId dbLeaderboardId = (ObjectId) dbLeaderboard.get("_id");
|
|
| 484 | - dbLeaderboardIds.add(dbLeaderboardId);
|
|
| 485 | - }
|
|
| 486 | - dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_LEADERBOARDS.name(), dbLeaderboardIds);
|
|
| 487 | - leaderboardGroupCollection.update(query, dbLeaderboardGroup, true, false, WriteConcern.SAFE);
|
|
| 488 | - }
|
|
| 489 | -
|
|
| 490 | - @Override
|
|
| 491 | - public void removeLeaderboardGroup(String groupName) {
|
|
| 492 | - DBCollection leaderboardGroupCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name());
|
|
| 493 | - BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), groupName);
|
|
| 494 | - leaderboardGroupCollection.remove(query);
|
|
| 495 | - }
|
|
| 496 | -
|
|
| 497 | - @Override
|
|
| 498 | - public void renameLeaderboardGroup(String oldName, String newName) {
|
|
| 499 | - DBCollection leaderboardGroupCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name());
|
|
| 500 | - BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), oldName);
|
|
| 501 | - BasicDBObject update = new BasicDBObject("$set", new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), newName));
|
|
| 502 | - leaderboardGroupCollection.update(query, update, /* upsert */ true, /* multi */ false, WriteConcern.SAFE);
|
|
| 503 | - }
|
|
| 504 | -
|
|
| 505 | - @Override
|
|
| 506 | - public void storeServerConfiguration(SailingServerConfiguration serverConfiguration) {
|
|
| 507 | - DBCollection serverCollection = database.getCollection(CollectionNames.SERVER_CONFIGURATION.name());
|
|
| 508 | - DBObject newServerConfig = new BasicDBObject();
|
|
| 509 | - newServerConfig.put(FieldNames.SERVER_IS_STANDALONE.name(), serverConfiguration.isStandaloneServer());
|
|
| 510 | - DBObject currentServerConfig = serverCollection.findOne();
|
|
| 511 | - if(currentServerConfig != null) {
|
|
| 512 | - serverCollection.update(currentServerConfig, newServerConfig, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
|
|
| 513 | - } else {
|
|
| 514 | - serverCollection.save(newServerConfig);
|
|
| 515 | - }
|
|
| 516 | - }
|
|
| 517 | -
|
|
| 518 | - @Override
|
|
| 519 | - public void storeSailingServer(RemoteSailingServerReference server) {
|
|
| 520 | - DBCollection serverCollection = database.getCollection(CollectionNames.SAILING_SERVERS.name());
|
|
| 521 | - serverCollection.createIndex(new BasicDBObject(FieldNames.SERVER_NAME.name(), 1));
|
|
| 522 | - DBObject query = new BasicDBObject();
|
|
| 523 | - query.put(FieldNames.SERVER_NAME.name(), server.getName());
|
|
| 524 | - DBObject serverDBObject = new BasicDBObject();
|
|
| 525 | - serverDBObject.put(FieldNames.SERVER_NAME.name(), server.getName());
|
|
| 526 | - serverDBObject.put(FieldNames.SERVER_URL.name(), server.getURL().toExternalForm());
|
|
| 527 | - serverCollection.update(query, serverDBObject, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
|
|
| 528 | - }
|
|
| 529 | -
|
|
| 530 | - @Override
|
|
| 531 | - public void removeSailingServer(String name) {
|
|
| 532 | - DBCollection serverCollection = database.getCollection(CollectionNames.SAILING_SERVERS.name());
|
|
| 533 | - BasicDBObject query = new BasicDBObject(FieldNames.SERVER_NAME.name(), name);
|
|
| 534 | - serverCollection.remove(query);
|
|
| 535 | - }
|
|
| 536 | -
|
|
| 537 | - /**
|
|
| 538 | - * StoreEvent() uses some deprecated methods of event to keep backward compatibility.
|
|
| 539 | - */
|
|
| 540 | - @SuppressWarnings("deprecation")
|
|
| 541 | - @Override
|
|
| 542 | - public void storeEvent(Event event) {
|
|
| 543 | - DBCollection eventCollection = database.getCollection(CollectionNames.EVENTS.name());
|
|
| 544 | - eventCollection.createIndex(new BasicDBObject(FieldNames.EVENT_ID.name(), 1));
|
|
| 545 | - DBObject query = new BasicDBObject();
|
|
| 546 | - query.put(FieldNames.EVENT_ID.name(), event.getId());
|
|
| 547 | - DBObject eventDBObject = new BasicDBObject();
|
|
| 548 | - eventDBObject.put(FieldNames.EVENT_NAME.name(), event.getName());
|
|
| 549 | - eventDBObject.put(FieldNames.EVENT_DESCRIPTION.name(), event.getDescription());
|
|
| 550 | - eventDBObject.put(FieldNames.EVENT_ID.name(), event.getId());
|
|
| 551 | - eventDBObject.put(FieldNames.EVENT_LOGO_IMAGE_URL.name(), event.getLogoImageURL() != null ? event.getLogoImageURL().toString() : null);
|
|
| 552 | - eventDBObject.put(FieldNames.EVENT_OFFICIAL_WEBSITE_URL.name(), event.getOfficialWebsiteURL() != null ? event.getOfficialWebsiteURL().toString() : null);
|
|
| 553 | - eventDBObject.put(FieldNames.EVENT_SAILORS_INFO_WEBSITE_URL.name(), event.getSailorsInfoWebsiteURL() != null ? event.getSailorsInfoWebsiteURL().toString() : null);
|
|
| 554 | - storeTimePoint(event.getStartDate(), eventDBObject, FieldNames.EVENT_START_DATE);
|
|
| 555 | - storeTimePoint(event.getEndDate(), eventDBObject, FieldNames.EVENT_END_DATE);
|
|
| 556 | - eventDBObject.put(FieldNames.EVENT_IS_PUBLIC.name(), event.isPublic());
|
|
| 557 | - DBObject venueDBObject = getVenueAsDBObject(event.getVenue());
|
|
| 558 | - eventDBObject.put(FieldNames.VENUE.name(), venueDBObject);
|
|
| 559 | - BasicDBList imageURLs = new BasicDBList();
|
|
| 560 | - for (URL imageURL : event.getImageURLs()) {
|
|
| 561 | - imageURLs.add(imageURL.toString());
|
|
| 562 | - }
|
|
| 563 | - eventDBObject.put(FieldNames.EVENT_IMAGE_URLS.name(), imageURLs);
|
|
| 564 | - BasicDBList videoURLs = new BasicDBList();
|
|
| 565 | - for (URL videoURL : event.getVideoURLs()) {
|
|
| 566 | - videoURLs.add(videoURL.toString());
|
|
| 567 | - }
|
|
| 568 | - eventDBObject.put(FieldNames.EVENT_VIDEO_URLS.name(), videoURLs);
|
|
| 569 | - BasicDBList sponsorImageURLs = new BasicDBList();
|
|
| 570 | - for (URL sponsorImageURL : event.getSponsorImageURLs()) {
|
|
| 571 | - sponsorImageURLs.add(sponsorImageURL.toString());
|
|
| 572 | - }
|
|
| 573 | - eventDBObject.put(FieldNames.EVENT_SPONSOR_IMAGE_URLS.name(), sponsorImageURLs);
|
|
| 574 | - BasicDBList images = new BasicDBList();
|
|
| 575 | - for (ImageDescriptor image : event.getImages()) {
|
|
| 576 | - DBObject imageObject = createImageObject(image);
|
|
| 577 | - images.add(imageObject);
|
|
| 578 | - }
|
|
| 579 | - eventDBObject.put(FieldNames.EVENT_IMAGES.name(), images);
|
|
| 580 | - BasicDBList videos = new BasicDBList();
|
|
| 581 | - for (VideoDescriptor video: event.getVideos()) {
|
|
| 582 | - DBObject videoObject = createVideoObject(video);
|
|
| 583 | - videos.add(videoObject);
|
|
| 584 | - }
|
|
| 585 | - eventDBObject.put(FieldNames.EVENT_VIDEOS.name(), videos);
|
|
| 586 | - eventCollection.update(query, eventDBObject, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
|
|
| 587 | - // now store the links to the leaderboard groups
|
|
| 588 | - DBCollection linksCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUP_LINKS_FOR_EVENTS.name());
|
|
| 589 | - linksCollection.createIndex(new BasicDBObject(FieldNames.EVENT_ID.name(), 1));
|
|
| 590 | - BasicDBList lgUUIDs = new BasicDBList();
|
|
| 591 | - for (LeaderboardGroup lg : event.getLeaderboardGroups()) {
|
|
| 592 | - lgUUIDs.add(lg.getId());
|
|
| 593 | - }
|
|
| 594 | - DBObject dbLinks = new BasicDBObject();
|
|
| 595 | - dbLinks.put(FieldNames.EVENT_ID.name(), event.getId());
|
|
| 596 | - dbLinks.put(FieldNames.LEADERBOARD_GROUP_UUID.name(), lgUUIDs);
|
|
| 597 | - linksCollection.update(query, dbLinks, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
|
|
| 598 | - }
|
|
| 599 | -
|
|
| 600 | - @Override
|
|
| 601 | - public void renameEvent(Serializable id, String newName) {
|
|
| 602 | - DBCollection eventCollection = database.getCollection(CollectionNames.EVENTS.name());
|
|
| 603 | - BasicDBObject query = new BasicDBObject(FieldNames.EVENT_ID.name(), id);
|
|
| 604 | - BasicDBObject renameUpdate = new BasicDBObject("$set", new BasicDBObject(FieldNames.EVENT_NAME.name(), newName));
|
|
| 605 | - eventCollection.update(query, renameUpdate, /* upsert */ true, /* multi */ false, WriteConcern.SAFE);
|
|
| 606 | - }
|
|
| 607 | -
|
|
| 608 | - @Override
|
|
| 609 | - public void removeEvent(Serializable id) {
|
|
| 610 | - DBCollection eventsCollection = database.getCollection(CollectionNames.EVENTS.name());
|
|
| 611 | - BasicDBObject query = new BasicDBObject(FieldNames.EVENT_ID.name(), id);
|
|
| 612 | - eventsCollection.remove(query);
|
|
| 613 | - }
|
|
| 614 | -
|
|
| 615 | - private DBObject getVenueAsDBObject(Venue venue) {
|
|
| 616 | - DBObject result = new BasicDBObject();
|
|
| 617 | - result.put(FieldNames.VENUE_NAME.name(), venue.getName());
|
|
| 618 | - BasicDBList courseAreaList = new BasicDBList();
|
|
| 619 | - result.put(FieldNames.COURSE_AREAS.name(), courseAreaList);
|
|
| 620 | - for (CourseArea courseArea : venue.getCourseAreas()) {
|
|
| 621 | - DBObject dbCourseArea = new BasicDBObject();
|
|
| 622 | - courseAreaList.add(dbCourseArea);
|
|
| 623 | - dbCourseArea.put(FieldNames.COURSE_AREA_NAME.name(), courseArea.getName());
|
|
| 624 | - dbCourseArea.put(FieldNames.COURSE_AREA_ID.name(), courseArea.getId());
|
|
| 625 | - }
|
|
| 626 | - return result;
|
|
| 627 | - }
|
|
| 628 | -
|
|
| 629 | - @Override
|
|
| 630 | - public void storeRegatta(Regatta regatta) {
|
|
| 631 | - DBCollection regattasCollection = database.getCollection(CollectionNames.REGATTAS.name());
|
|
| 632 | - regattasCollection.createIndex(new BasicDBObject(FieldNames.REGATTA_NAME.name(), 1));
|
|
| 633 | - regattasCollection.createIndex(new BasicDBObject(FieldNames.REGATTA_ID.name(), 1));
|
|
| 634 | - DBObject dbRegatta = new BasicDBObject();
|
|
| 635 | - DBObject query = new BasicDBObject(FieldNames.REGATTA_NAME.name(), regatta.getName());
|
|
| 636 | - dbRegatta.put(FieldNames.REGATTA_NAME.name(), regatta.getName());
|
|
| 637 | - dbRegatta.put(FieldNames.REGATTA_ID.name(), regatta.getId());
|
|
| 638 | - storeTimePoint(regatta.getStartDate(), dbRegatta, FieldNames.REGATTA_START_DATE);
|
|
| 639 | - storeTimePoint(regatta.getEndDate(), dbRegatta, FieldNames.REGATTA_END_DATE);
|
|
| 640 | - dbRegatta.put(FieldNames.SCORING_SCHEME_TYPE.name(), regatta.getScoringScheme().getType().name());
|
|
| 641 | - if (regatta.getBoatClass() != null) {
|
|
| 642 | - dbRegatta.put(FieldNames.BOAT_CLASS_NAME.name(), regatta.getBoatClass().getName());
|
|
| 643 | - dbRegatta.put(FieldNames.BOAT_CLASS_TYPICALLY_STARTS_UPWIND.name(), regatta.getBoatClass().typicallyStartsUpwind());
|
|
| 644 | - }
|
|
| 645 | - dbRegatta.put(FieldNames.REGATTA_SERIES.name(), storeSeries(regatta.getSeries()));
|
|
| 646 | -
|
|
| 647 | - if (regatta.getDefaultCourseArea() != null) {
|
|
| 648 | - dbRegatta.put(FieldNames.COURSE_AREA_ID.name(), regatta.getDefaultCourseArea().getId().toString());
|
|
| 649 | - } else {
|
|
| 650 | - dbRegatta.put(FieldNames.COURSE_AREA_ID.name(), null);
|
|
| 651 | - }
|
|
| 652 | - if (regatta.getRegattaConfiguration() != null) {
|
|
| 653 | - JsonSerializer<RegattaConfiguration> serializer = RegattaConfigurationJsonSerializer.create();
|
|
| 654 | - JSONObject json = serializer.serialize(regatta.getRegattaConfiguration());
|
|
| 655 | - DBObject configurationObject = (DBObject) JSON.parse(json.toString());
|
|
| 656 | - dbRegatta.put(FieldNames.REGATTA_REGATTA_CONFIGURATION.name(), configurationObject);
|
|
| 657 | - }
|
|
| 658 | - dbRegatta.put(FieldNames.REGATTA_USE_START_TIME_INFERENCE.name(), regatta.useStartTimeInference());
|
|
| 659 | - dbRegatta.put(FieldNames.REGATTA_RANKING_METRIC.name(), storeRankingMetric(regatta));
|
|
| 660 | - regattasCollection.update(query, dbRegatta, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
|
|
| 661 | - }
|
|
| 662 | -
|
|
| 663 | - private DBObject storeRankingMetric(Regatta regatta) {
|
|
| 664 | - DBObject rankingMetricJson = new BasicDBObject();
|
|
| 665 | - final String rankingMetricTypeName = regatta.getRankingMetricType().name();
|
|
| 666 | - rankingMetricJson.put(FieldNames.REGATTA_RANKING_METRIC_TYPE.name(), rankingMetricTypeName);
|
|
| 667 | - return rankingMetricJson;
|
|
| 668 | - }
|
|
| 669 | -
|
|
| 670 | - @Override
|
|
| 671 | - public void removeRegatta(Regatta regatta) {
|
|
| 672 | - DBCollection regattasCollection = database.getCollection(CollectionNames.REGATTAS.name());
|
|
| 673 | - DBObject query = new BasicDBObject(FieldNames.REGATTA_NAME.name(), regatta.getName());
|
|
| 674 | - regattasCollection.remove(query);
|
|
| 675 | - }
|
|
| 676 | -
|
|
| 677 | - private BasicDBList storeSeries(Iterable<? extends Series> series) {
|
|
| 678 | - BasicDBList dbSeries = new BasicDBList();
|
|
| 679 | - for (Series s : series) {
|
|
| 680 | - dbSeries.add(storeSeries(s));
|
|
| 681 | - }
|
|
| 682 | - return dbSeries;
|
|
| 683 | - }
|
|
| 684 | -
|
|
| 685 | - private DBObject storeSeries(Series s) {
|
|
| 686 | - DBObject dbSeries = new BasicDBObject();
|
|
| 687 | - dbSeries.put(FieldNames.SERIES_NAME.name(), s.getName());
|
|
| 688 | - dbSeries.put(FieldNames.SERIES_IS_MEDAL.name(), s.isMedal());
|
|
| 689 | - dbSeries.put(FieldNames.SERIES_HAS_SPLIT_FLEET_CONTIGUOUS_SCORING.name(), s.hasSplitFleetContiguousScoring());
|
|
| 690 | - dbSeries.put(FieldNames.SERIES_STARTS_WITH_ZERO_SCORE.name(), s.isStartsWithZeroScore());
|
|
| 691 | - dbSeries.put(FieldNames.SERIES_STARTS_WITH_NON_DISCARDABLE_CARRY_FORWARD.name(), s.isFirstColumnIsNonDiscardableCarryForward());
|
|
| 692 | - BasicDBList dbFleets = new BasicDBList();
|
|
| 693 | - for (Fleet fleet : s.getFleets()) {
|
|
| 694 | - dbFleets.add(storeFleet(fleet));
|
|
| 695 | - }
|
|
| 696 | - dbSeries.put(FieldNames.SERIES_FLEETS.name(), dbFleets);
|
|
| 697 | - BasicDBList dbRaceColumns = new BasicDBList();
|
|
| 698 | - for (RaceColumn raceColumn : s.getRaceColumns()) {
|
|
| 699 | - dbRaceColumns.add(storeRaceColumn(raceColumn));
|
|
| 700 | - }
|
|
| 701 | - dbSeries.put(FieldNames.SERIES_RACE_COLUMNS.name(), dbRaceColumns);
|
|
| 702 | - if (s.getResultDiscardingRule() != null) {
|
|
| 703 | - storeResultDiscardingRule(dbSeries, s.getResultDiscardingRule(), FieldNames.SERIES_DISCARDING_THRESHOLDS);
|
|
| 704 | - }
|
|
| 705 | - return dbSeries;
|
|
| 706 | - }
|
|
| 707 | -
|
|
| 708 | - private DBObject storeFleet(Fleet fleet) {
|
|
| 709 | - DBObject dbFleet = new BasicDBObject(FieldNames.FLEET_NAME.name(), fleet.getName());
|
|
| 710 | - if (fleet instanceof FleetImpl) {
|
|
| 711 | - dbFleet.put(FieldNames.FLEET_ORDERING.name(), ((FleetImpl) fleet).getOrdering());
|
|
| 712 | - if(fleet.getColor() != null) {
|
|
| 713 | - com.sap.sse.common.Util.Triple<Integer, Integer, Integer> colorAsRGB = fleet.getColor().getAsRGB();
|
|
| 714 | - // we save the color as a integer value representing the RGB values
|
|
| 715 | - int colorAsInt = (256 * 256 * colorAsRGB.getC()) + colorAsRGB.getB() * 256 + colorAsRGB.getA();
|
|
| 716 | - dbFleet.put(FieldNames.FLEET_COLOR.name(), colorAsInt);
|
|
| 717 | - } else {
|
|
| 718 | - dbFleet.put(FieldNames.FLEET_COLOR.name(), null);
|
|
| 719 | - }
|
|
| 720 | - }
|
|
| 721 | - return dbFleet;
|
|
| 722 | - }
|
|
| 723 | -
|
|
| 724 | - @Override
|
|
| 725 | - public void storeRegattaForRaceID(String raceIDAsString, Regatta regatta) {
|
|
| 726 | - DBCollection regattaForRaceIDCollection = database.getCollection(CollectionNames.REGATTA_FOR_RACE_ID.name());
|
|
| 727 | - DBObject query = new BasicDBObject(FieldNames.RACE_ID_AS_STRING.name(), raceIDAsString);
|
|
| 728 | - DBObject entry = new BasicDBObject(FieldNames.RACE_ID_AS_STRING.name(), raceIDAsString);
|
|
| 729 | - entry.put(FieldNames.REGATTA_NAME.name(), regatta.getName());
|
|
| 730 | - regattaForRaceIDCollection.update(query, entry, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
|
|
| 731 | - }
|
|
| 732 | -
|
|
| 733 | - @Override
|
|
| 734 | - public void removeRegattaForRaceID(String raceIDAsString, Regatta regatta) {
|
|
| 735 | - DBCollection regattaForRaceIDCollection = database.getCollection(CollectionNames.REGATTA_FOR_RACE_ID.name());
|
|
| 736 | - DBObject query = new BasicDBObject(FieldNames.RACE_ID_AS_STRING.name(), raceIDAsString);
|
|
| 737 | - regattaForRaceIDCollection.remove(query);
|
|
| 738 | - }
|
|
| 739 | -
|
|
| 740 | - public DBCollection getRaceLogCollection() {
|
|
| 741 | - DBCollection result = database.getCollection(CollectionNames.RACE_LOGS.name());
|
|
| 742 | - result.createIndex(new BasicDBObject(FieldNames.RACE_LOG_IDENTIFIER.name(), null));
|
|
| 743 | - return result;
|
|
| 744 | - }
|
|
| 745 | -
|
|
| 746 | - private void storeRaceLogEventAuthor(DBObject dbObject, AbstractLogEventAuthor author) {
|
|
| 747 | - if (author != null) {
|
|
| 748 | - dbObject.put(FieldNames.RACE_LOG_EVENT_AUTHOR_NAME.name(), author.getName());
|
|
| 749 | - dbObject.put(FieldNames.RACE_LOG_EVENT_AUTHOR_PRIORITY.name(), author.getPriority());
|
|
| 750 | - }
|
|
| 751 | - }
|
|
| 752 | -
|
|
| 753 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogFlagEvent flagEvent) {
|
|
| 754 | - BasicDBObject result = new BasicDBObject();
|
|
| 755 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 756 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogFlagEvent(flagEvent));
|
|
| 757 | - return result;
|
|
| 758 | - }
|
|
| 759 | -
|
|
| 760 | - private void storeRaceLogIdentifier(RaceLogIdentifier raceLogIdentifier, DBObject result) {
|
|
| 761 | - result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), TripleSerializer.serialize(raceLogIdentifier.getIdentifier()));
|
|
| 762 | - }
|
|
| 763 | -
|
|
| 764 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogStartTimeEvent startTimeEvent) {
|
|
| 765 | - BasicDBObject result = new BasicDBObject();
|
|
| 766 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 767 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogStartTimeEvent(startTimeEvent));
|
|
| 768 | - return result;
|
|
| 769 | - }
|
|
| 770 | -
|
|
| 771 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogPassChangeEvent passChangeEvent) {
|
|
| 772 | - BasicDBObject result = new BasicDBObject();
|
|
| 773 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 774 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogPassChangeEvent(passChangeEvent));
|
|
| 775 | - return result;
|
|
| 776 | - }
|
|
| 777 | -
|
|
| 778 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogRaceStatusEvent raceStatusEvent) {
|
|
| 779 | - BasicDBObject result = new BasicDBObject();
|
|
| 780 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 781 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogRaceStatusEvent(raceStatusEvent));
|
|
| 782 | - return result;
|
|
| 783 | - }
|
|
| 784 | -
|
|
| 785 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogCourseAreaChangedEvent courseAreaChangedEvent) {
|
|
| 786 | - BasicDBObject result = new BasicDBObject();
|
|
| 787 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 788 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogCourseAreaChangedEvent(courseAreaChangedEvent));
|
|
| 789 | - return result;
|
|
| 790 | - }
|
|
| 791 | -
|
|
| 792 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogCourseDesignChangedEvent courseDesignChangedEvent) {
|
|
| 793 | - BasicDBObject result = new BasicDBObject();
|
|
| 794 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 795 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogCourseDesignChangedEvent(courseDesignChangedEvent));
|
|
| 796 | - return result;
|
|
| 797 | - }
|
|
| 798 | -
|
|
| 799 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogFinishPositioningListChangedEvent finishPositioningListChangedEvent) {
|
|
| 800 | - BasicDBObject result = new BasicDBObject();
|
|
| 801 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 802 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogFinishPositioningListChangedEvent(finishPositioningListChangedEvent));
|
|
| 803 | - return result;
|
|
| 804 | - }
|
|
| 805 | -
|
|
| 806 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogFinishPositioningConfirmedEvent finishPositioningConfirmedEvent) {
|
|
| 807 | - BasicDBObject result = new BasicDBObject();
|
|
| 808 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 809 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogFinishPositioningConfirmedEvent(finishPositioningConfirmedEvent));
|
|
| 810 | - return result;
|
|
| 811 | - }
|
|
| 812 | -
|
|
| 813 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogPathfinderEvent pathfinderEvent) {
|
|
| 814 | - BasicDBObject result = new BasicDBObject();
|
|
| 815 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 816 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogPathfinderEvent(pathfinderEvent));
|
|
| 817 | - return result;
|
|
| 818 | - }
|
|
| 819 | -
|
|
| 820 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogGateLineOpeningTimeEvent gateLineOpeningTimeEvent) {
|
|
| 821 | - BasicDBObject result = new BasicDBObject();
|
|
| 822 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 823 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogGateLineOpeningTimeEvent(gateLineOpeningTimeEvent));
|
|
| 824 | - return result;
|
|
| 825 | - }
|
|
| 826 | -
|
|
| 827 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogStartProcedureChangedEvent event) {
|
|
| 828 | - DBObject result = new BasicDBObject();
|
|
| 829 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 830 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogStartProcedureChangedEvent(event));
|
|
| 831 | - return result;
|
|
| 832 | - }
|
|
| 833 | -
|
|
| 834 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogProtestStartTimeEvent event) {
|
|
| 835 | - DBObject result = new BasicDBObject();
|
|
| 836 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 837 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogProtestStartTimeEvent(event));
|
|
| 838 | - return result;
|
|
| 839 | - }
|
|
| 840 | -
|
|
| 841 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogWindFixEvent event) {
|
|
| 842 | - DBObject result = new BasicDBObject();
|
|
| 843 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 844 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogWindFix(event));
|
|
| 845 | - return result;
|
|
| 846 | - }
|
|
| 847 | -
|
|
| 848 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDeviceCompetitorMappingEvent event) {
|
|
| 849 | - BasicDBObject result = new BasicDBObject();
|
|
| 850 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 851 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDeviceCompetitorMappingEvent(event));
|
|
| 852 | - return result;
|
|
| 853 | - }
|
|
| 854 | -
|
|
| 855 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDeviceMarkMappingEvent event) {
|
|
| 856 | - BasicDBObject result = new BasicDBObject();
|
|
| 857 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 858 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDeviceMarkMappingEvent(event));
|
|
| 859 | - return result;
|
|
| 860 | - }
|
|
| 861 | -
|
|
| 862 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDenoteForTrackingEvent event) {
|
|
| 863 | - BasicDBObject result = new BasicDBObject();
|
|
| 864 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 865 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDenoteForTrackingEvent(event));
|
|
| 866 | - return result;
|
|
| 867 | - }
|
|
| 868 | -
|
|
| 869 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogStartTrackingEvent event) {
|
|
| 870 | - BasicDBObject result = new BasicDBObject();
|
|
| 871 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 872 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogStartTrackingEvent(event));
|
|
| 873 | - return result;
|
|
| 874 | - }
|
|
| 875 | -
|
|
| 876 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogRevokeEvent event) {
|
|
| 877 | - BasicDBObject result = new BasicDBObject();
|
|
| 878 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 879 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogRevokeEvent(event));
|
|
| 880 | - return result;
|
|
| 881 | - }
|
|
| 882 | -
|
|
| 883 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogRegisterCompetitorEvent event) {
|
|
| 884 | - BasicDBObject result = new BasicDBObject();
|
|
| 885 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 886 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogRegisterCompetitorEvent(event));
|
|
| 887 | - return result;
|
|
| 888 | - }
|
|
| 889 | -
|
|
| 890 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDefineMarkEvent event) {
|
|
| 891 | - BasicDBObject result = new BasicDBObject();
|
|
| 892 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 893 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDefineMarkEvent(event));
|
|
| 894 | - return result;
|
|
| 895 | - }
|
|
| 896 | -
|
|
| 897 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogCloseOpenEndedDeviceMappingEvent event) {
|
|
| 898 | - BasicDBObject result = new BasicDBObject();
|
|
| 899 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 900 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogCloseOpenEndedDeviceMappingEvent(event));
|
|
| 901 | - return result;
|
|
| 902 | - }
|
|
| 903 | -
|
|
| 904 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogEndOfTrackingEvent event) {
|
|
| 905 | - BasicDBObject result = new BasicDBObject();
|
|
| 906 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 907 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogEndOfTrackingEvent(event));
|
|
| 908 | - return result;
|
|
| 909 | - }
|
|
| 910 | -
|
|
| 911 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogStartOfTrackingEvent event) {
|
|
| 912 | - BasicDBObject result = new BasicDBObject();
|
|
| 913 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 914 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogStartOfTrackingEvent(event));
|
|
| 915 | - return result;
|
|
| 916 | - }
|
|
| 917 | -
|
|
| 918 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogAdditionalScoringInformationEvent event) {
|
|
| 919 | - BasicDBObject result = new BasicDBObject();
|
|
| 920 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 921 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeAdditionalScoringInformation(event));
|
|
| 922 | - return result;
|
|
| 923 | - }
|
|
| 924 | -
|
|
| 925 | - private Object storeAdditionalScoringInformation(RaceLogAdditionalScoringInformationEvent event) {
|
|
| 926 | - DBObject result = new BasicDBObject();
|
|
| 927 | - storeRaceLogEventProperties(event, result);
|
|
| 928 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogAdditionalScoringInformationEvent.class.getSimpleName());
|
|
| 929 | - result.put(FieldNames.RACE_LOG_ADDITIONAL_SCORING_INFORMATION_TYPE.name(), event.getType().name());
|
|
| 930 | - return result;
|
|
| 931 | - }
|
|
| 932 | -
|
|
| 933 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogFixedMarkPassingEvent event) {
|
|
| 934 | - BasicDBObject result = new BasicDBObject();
|
|
| 935 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 936 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogFixedMarkPassingEvent(event));
|
|
| 937 | - return result;
|
|
| 938 | - }
|
|
| 939 | -
|
|
| 940 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogSuppressedMarkPassingsEvent event) {
|
|
| 941 | - BasicDBObject result = new BasicDBObject();
|
|
| 942 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 943 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogSuppressedMarkPassingsEvent(event));
|
|
| 944 | - return result;
|
|
| 945 | - }
|
|
| 946 | -
|
|
| 947 | - private Object storeRaceLogWindFix(RaceLogWindFixEvent event) {
|
|
| 948 | - DBObject result = new BasicDBObject();
|
|
| 949 | - storeRaceLogEventProperties(event, result);
|
|
| 950 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogWindFixEvent.class.getSimpleName());
|
|
| 951 | - result.put(FieldNames.WIND.name(), storeWind(event.getWindFix()));
|
|
| 952 | - result.put(FieldNames.IS_MAGNETIC.name(), event.isMagnetic());
|
|
| 953 | - return result;
|
|
| 954 | - }
|
|
| 955 | -
|
|
| 956 | - private Object storeRaceLogProtestStartTimeEvent(RaceLogProtestStartTimeEvent event) {
|
|
| 957 | - DBObject result = new BasicDBObject();
|
|
| 958 | - storeRaceLogEventProperties(event, result);
|
|
| 959 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogProtestStartTimeEvent.class.getSimpleName());
|
|
| 960 | - storeTimePoint(event.getProtestStartTime(), result, FieldNames.RACE_LOG_PROTEST_START_TIME);
|
|
| 961 | - return result;
|
|
| 962 | - }
|
|
| 963 | -
|
|
| 964 | - private Object storeRaceLogEndOfTrackingEvent(RaceLogEndOfTrackingEvent event) {
|
|
| 965 | - DBObject result = new BasicDBObject();
|
|
| 966 | - storeRaceLogEventProperties(event, result);
|
|
| 967 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogEndOfTrackingEvent.class.getSimpleName());
|
|
| 968 | - return result;
|
|
| 969 | - }
|
|
| 970 | -
|
|
| 971 | - private Object storeRaceLogStartOfTrackingEvent(RaceLogStartOfTrackingEvent event) {
|
|
| 972 | - DBObject result = new BasicDBObject();
|
|
| 973 | - storeRaceLogEventProperties(event, result);
|
|
| 974 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogStartOfTrackingEvent.class.getSimpleName());
|
|
| 975 | - return result;
|
|
| 976 | - }
|
|
| 977 | -
|
|
| 978 | - private Object storeRaceLogStartProcedureChangedEvent(RaceLogStartProcedureChangedEvent event) {
|
|
| 979 | - DBObject result = new BasicDBObject();
|
|
| 980 | - storeRaceLogEventProperties(event, result);
|
|
| 981 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogStartProcedureChangedEvent.class.getSimpleName());
|
|
| 982 | - result.put(FieldNames.RACE_LOG_START_PROCEDURE_TYPE.name(), event.getStartProcedureType().name());
|
|
| 983 | - return result;
|
|
| 984 | - }
|
|
| 985 | -
|
|
| 986 | - private Object storeRaceLogPathfinderEvent(RaceLogPathfinderEvent pathfinderEvent) {
|
|
| 987 | - DBObject result = new BasicDBObject();
|
|
| 988 | - storeRaceLogEventProperties(pathfinderEvent, result);
|
|
| 989 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogPathfinderEvent.class.getSimpleName());
|
|
| 990 | - result.put(FieldNames.RACE_LOG_PATHFINDER_ID.name(), pathfinderEvent.getPathfinderId());
|
|
| 991 | - return result;
|
|
| 992 | - }
|
|
| 993 | -
|
|
| 994 | - private void storeDeviceMappingEvent(DeviceMappingEvent<?, ?> event, DBObject result, FieldNames fromField, FieldNames toField) {
|
|
| 995 | - try {
|
|
| 996 | - result.put(FieldNames.DEVICE_ID.name(), storeDeviceId(deviceIdentifierServiceFinder, event.getDevice()));
|
|
| 997 | - } catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
|
|
| 998 | - logger.log(Level.WARNING, "Could not store device identifier for mappng event", e);
|
|
| 999 | - }
|
|
| 1000 | - if (event.getFrom() != null) {
|
|
| 1001 | - storeTimePoint(event.getFrom(), result, fromField);
|
|
| 1002 | - }
|
|
| 1003 | - if (event.getTo() != null) {
|
|
| 1004 | - storeTimePoint(event.getTo(), result, toField);
|
|
| 1005 | - }
|
|
| 1006 | - }
|
|
| 1007 | -
|
|
| 1008 | - private Object storeRaceLogDeviceCompetitorMappingEvent(RaceLogDeviceCompetitorMappingEvent event) {
|
|
| 1009 | - DBObject result = new BasicDBObject();
|
|
| 1010 | - storeRaceLogEventProperties(event, result);
|
|
| 1011 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDeviceCompetitorMappingEvent.class.getSimpleName());
|
|
| 1012 | - storeDeviceMappingEvent(event, result, FieldNames.RACE_LOG_FROM, FieldNames.RACE_LOG_TO);
|
|
| 1013 | - result.put(FieldNames.COMPETITOR_ID.name(), event.getMappedTo().getId());
|
|
| 1014 | - return result;
|
|
| 1015 | - }
|
|
| 1016 | -
|
|
| 1017 | - private Object storeRaceLogDeviceMarkMappingEvent(RaceLogDeviceMarkMappingEvent event) {
|
|
| 1018 | - DBObject result = new BasicDBObject();
|
|
| 1019 | - storeRaceLogEventProperties(event, result);
|
|
| 1020 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDeviceMarkMappingEvent.class.getSimpleName());
|
|
| 1021 | - storeDeviceMappingEvent(event, result, FieldNames.RACE_LOG_FROM, FieldNames.RACE_LOG_TO);
|
|
| 1022 | - result.put(FieldNames.MARK.name(), storeMark(event.getMappedTo()));
|
|
| 1023 | - return result;
|
|
| 1024 | - }
|
|
| 1025 | -
|
|
| 1026 | - private Object storeRaceLogDenoteForTrackingEvent(RaceLogDenoteForTrackingEvent event) {
|
|
| 1027 | - DBObject result = new BasicDBObject();
|
|
| 1028 | - storeRaceLogEventProperties(event, result);
|
|
| 1029 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDenoteForTrackingEvent.class.getSimpleName());
|
|
| 1030 | - result.put(FieldNames.RACE_NAME.name(), event.getRaceName());
|
|
| 1031 | - result.put(FieldNames.BOAT_CLASS_NAME.name(), event.getBoatClass().getName());
|
|
| 1032 | - result.put(FieldNames.RACE_ID.name(), event.getRaceId());
|
|
| 1033 | - return result;
|
|
| 1034 | - }
|
|
| 1035 | -
|
|
| 1036 | - private Object storeRaceLogStartTrackingEvent(RaceLogStartTrackingEvent event) {
|
|
| 1037 | - DBObject result = new BasicDBObject();
|
|
| 1038 | - storeRaceLogEventProperties(event, result);
|
|
| 1039 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogStartTrackingEvent.class.getSimpleName());
|
|
| 1040 | - return result;
|
|
| 1041 | - }
|
|
| 1042 | -
|
|
| 1043 | - private Object storeRaceLogRevokeEvent(RaceLogRevokeEvent event) {
|
|
| 1044 | - DBObject result = new BasicDBObject();
|
|
| 1045 | - storeRaceLogEventProperties(event, result);
|
|
| 1046 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogRevokeEvent.class.getSimpleName());
|
|
| 1047 | - result.put(FieldNames.RACE_LOG_REVOKED_EVENT_ID.name(), event.getRevokedEventId());
|
|
| 1048 | - result.put(FieldNames.RACE_LOG_REVOKED_EVENT_TYPE.name(), event.getRevokedEventType());
|
|
| 1049 | - result.put(FieldNames.RACE_LOG_REVOKED_EVENT_SHORT_INFO.name(), event.getRevokedEventShortInfo());
|
|
| 1050 | - result.put(FieldNames.RACE_LOG_REVOKED_REASON.name(), event.getReason());
|
|
| 1051 | - return result;
|
|
| 1052 | - }
|
|
| 1053 | -
|
|
| 1054 | - private Object storeRaceLogRegisterCompetitorEvent(RaceLogRegisterCompetitorEvent event) {
|
|
| 1055 | - DBObject result = new BasicDBObject();
|
|
| 1056 | - storeRaceLogEventProperties(event, result);
|
|
| 1057 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogRegisterCompetitorEvent.class.getSimpleName());
|
|
| 1058 | - result.put(FieldNames.RACE_LOG_COMPETITOR_ID.name(), event.getCompetitor().getId());
|
|
| 1059 | - return result;
|
|
| 1060 | - }
|
|
| 1061 | -
|
|
| 1062 | - private Object storeRaceLogDefineMarkEvent(RaceLogDefineMarkEvent event) {
|
|
| 1063 | - DBObject result = new BasicDBObject();
|
|
| 1064 | - storeRaceLogEventProperties(event, result);
|
|
| 1065 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDefineMarkEvent.class.getSimpleName());
|
|
| 1066 | - result.put(FieldNames.RACE_LOG_MARK.name(), storeMark(event.getMark()));
|
|
| 1067 | - return result;
|
|
| 1068 | - }
|
|
| 1069 | -
|
|
| 1070 | - private Object storeRaceLogCloseOpenEndedDeviceMappingEvent(RaceLogCloseOpenEndedDeviceMappingEvent event) {
|
|
| 1071 | - DBObject result = new BasicDBObject();
|
|
| 1072 | - storeRaceLogEventProperties(event, result);
|
|
| 1073 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName());
|
|
| 1074 | - result.put(FieldNames.RACE_LOG_DEVICE_MAPPING_EVENT_ID.name(), event.getDeviceMappingEventId());
|
|
| 1075 | - storeTimePoint(event.getClosingTimePoint(), result, FieldNames.RACE_LOG_CLOSING_TIMEPOINT);
|
|
| 1076 | - return result;
|
|
| 1077 | - }
|
|
| 1078 | -
|
|
| 1079 | - private Object storeRaceLogFixedMarkPassingEvent(RaceLogFixedMarkPassingEvent event) {
|
|
| 1080 | - DBObject result = new BasicDBObject();
|
|
| 1081 | - storeRaceLogEventProperties(event, result);
|
|
| 1082 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogFixedMarkPassingEvent.class.getSimpleName());
|
|
| 1083 | - result.put(FieldNames.INDEX_OF_PASSED_WAYPOINT.name(), event.getZeroBasedIndexOfPassedWaypoint());
|
|
| 1084 | - result.put(FieldNames.TIMEPOINT_OF_FIXED_MARKPASSING.name(), event.getTimePointOfFixedPassing().asMillis());
|
|
| 1085 | - return result;
|
|
| 1086 | - }
|
|
| 1087 | -
|
|
| 1088 | - private Object storeRaceLogSuppressedMarkPassingsEvent(RaceLogSuppressedMarkPassingsEvent event) {
|
|
| 1089 | - DBObject result = new BasicDBObject();
|
|
| 1090 | - storeRaceLogEventProperties(event, result);
|
|
| 1091 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogSuppressedMarkPassingsEvent.class.getSimpleName());
|
|
| 1092 | - result.put(FieldNames.INDEX_OF_FIRST_SUPPRESSED_WAYPOINT.name(), event.getZeroBasedIndexOfFirstSuppressedWaypoint());
|
|
| 1093 | - return result;
|
|
| 1094 | - }
|
|
| 1095 | -
|
|
| 1096 | - public DBObject storeRaceLogFlagEvent(RaceLogFlagEvent flagEvent) {
|
|
| 1097 | - DBObject result = new BasicDBObject();
|
|
| 1098 | - storeRaceLogEventProperties(flagEvent, result);
|
|
| 1099 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogFlagEvent.class.getSimpleName());
|
|
| 1100 | - result.put(FieldNames.RACE_LOG_EVENT_FLAG_UPPER.name(), flagEvent.getUpperFlag().name());
|
|
| 1101 | - result.put(FieldNames.RACE_LOG_EVENT_FLAG_LOWER.name(), flagEvent.getLowerFlag().name());
|
|
| 1102 | - result.put(FieldNames.RACE_LOG_EVENT_FLAG_DISPLAYED.name(), String.valueOf(flagEvent.isDisplayed()));
|
|
| 1103 | - return result;
|
|
| 1104 | - }
|
|
| 1105 | -
|
|
| 1106 | - private DBObject storeRaceLogStartTimeEvent(RaceLogStartTimeEvent startTimeEvent) {
|
|
| 1107 | - DBObject result = new BasicDBObject();
|
|
| 1108 | - storeRaceLogEventProperties(startTimeEvent, result);
|
|
| 1109 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogStartTimeEvent.class.getSimpleName());
|
|
| 1110 | - storeTimePoint(startTimeEvent.getStartTime(), result, FieldNames.RACE_LOG_EVENT_START_TIME);
|
|
| 1111 | - result.put(FieldNames.RACE_LOG_EVENT_NEXT_STATUS.name(), startTimeEvent.getNextStatus().name());
|
|
| 1112 | - return result;
|
|
| 1113 | - }
|
|
| 1114 | -
|
|
| 1115 | - private void storeRaceLogEventProperties(RaceLogEvent event, DBObject result) {
|
|
| 1116 | - // for compatibility reasons we reuse the field name of Timed
|
|
| 1117 | - storeTimePoint(event.getLogicalTimePoint(), result, FieldNames.TIME_AS_MILLIS);
|
|
| 1118 | - storeTimePoint(event.getCreatedAt(), result, FieldNames.RACE_LOG_EVENT_CREATED_AT);
|
|
| 1119 | - result.put(FieldNames.RACE_LOG_EVENT_ID.name(), event.getId());
|
|
| 1120 | - result.put(FieldNames.RACE_LOG_EVENT_PASS_ID.name(), event.getPassId());
|
|
| 1121 | - result.put(FieldNames.RACE_LOG_EVENT_INVOLVED_BOATS.name(), storeInvolvedBoatsForRaceLogEvent(event.getInvolvedBoats()));
|
|
| 1122 | - storeRaceLogEventAuthor(result, event.getAuthor());
|
|
| 1123 | - }
|
|
| 1124 | -
|
|
| 1125 | -
|
|
| 1126 | - private BasicDBList storeInvolvedBoatsForRaceLogEvent(List<Competitor> competitors) {
|
|
| 1127 | - BasicDBList dbInvolvedCompetitorIds = new BasicDBList();
|
|
| 1128 | - for (Competitor competitor : competitors) {
|
|
| 1129 | - dbInvolvedCompetitorIds.add(competitor.getId());
|
|
| 1130 | - }
|
|
| 1131 | - return dbInvolvedCompetitorIds;
|
|
| 1132 | - }
|
|
| 1133 | -
|
|
| 1134 | - private DBObject storeRaceLogPassChangeEvent(RaceLogPassChangeEvent passChangeEvent) {
|
|
| 1135 | - DBObject result = new BasicDBObject();
|
|
| 1136 | - storeRaceLogEventProperties(passChangeEvent, result);
|
|
| 1137 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogPassChangeEvent.class.getSimpleName());
|
|
| 1138 | - return result;
|
|
| 1139 | - }
|
|
| 1140 | -
|
|
| 1141 | - private DBObject storeRaceLogDependentStartTimeEvent(RaceLogDependentStartTimeEvent dependentStartTimeEvent) {
|
|
| 1142 | - DBObject result = new BasicDBObject();
|
|
| 1143 | - storeRaceLogEventProperties(dependentStartTimeEvent, result);
|
|
| 1144 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDependentStartTimeEvent.class.getSimpleName());
|
|
| 1145 | - result.put(FieldNames.RACE_LOG_DEPDENDENT_ON_REGATTALIKE.name(), dependentStartTimeEvent.getDependentOnRaceIdentifier().getRegattaLikeParentName());
|
|
| 1146 | - result.put(FieldNames.RACE_LOG_DEPDENDENT_ON_RACECOLUMN.name(), dependentStartTimeEvent.getDependentOnRaceIdentifier().getRaceColumnName());
|
|
| 1147 | - result.put(FieldNames.RACE_LOG_DEPDENDENT_ON_FLEET.name(), dependentStartTimeEvent.getDependentOnRaceIdentifier().getFleetName());
|
|
| 1148 | - storeDuration(dependentStartTimeEvent.getStartTimeDifference(), result, FieldNames.RACE_LOG_START_TIME_DIFFERENCE_IN_MS);
|
|
| 1149 | - result.put(FieldNames.RACE_LOG_EVENT_NEXT_STATUS.name(), dependentStartTimeEvent.getNextStatus().name());
|
|
| 1150 | - return result;
|
|
| 1151 | - }
|
|
| 1152 | -
|
|
| 1153 | - private void storeDuration(Duration duration, DBObject result, FieldNames fieldName) {
|
|
| 1154 | - if (duration != null) {
|
|
| 1155 | - result.put(fieldName.name(), duration.asMillis());
|
|
| 1156 | - }
|
|
| 1157 | - }
|
|
| 1158 | -
|
|
| 1159 | - private DBObject storeRaceLogRaceStatusEvent(RaceLogRaceStatusEvent raceStatusEvent) {
|
|
| 1160 | - DBObject result = new BasicDBObject();
|
|
| 1161 | - storeRaceLogEventProperties(raceStatusEvent, result);
|
|
| 1162 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogRaceStatusEvent.class.getSimpleName());
|
|
| 1163 | - result.put(FieldNames.RACE_LOG_EVENT_NEXT_STATUS.name(), raceStatusEvent.getNextStatus().name());
|
|
| 1164 | - return result;
|
|
| 1165 | - }
|
|
| 1166 | -
|
|
| 1167 | - private DBObject storeRaceLogCourseAreaChangedEvent(RaceLogCourseAreaChangedEvent courseAreaChangedEvent) {
|
|
| 1168 | - DBObject result = new BasicDBObject();
|
|
| 1169 | - storeRaceLogEventProperties(courseAreaChangedEvent, result);
|
|
| 1170 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogCourseAreaChangedEvent.class.getSimpleName());
|
|
| 1171 | - result.put(FieldNames.COURSE_AREA_ID.name(), courseAreaChangedEvent.getCourseAreaId());
|
|
| 1172 | - return result;
|
|
| 1173 | - }
|
|
| 1174 | -
|
|
| 1175 | - private DBObject storeRaceLogCourseDesignChangedEvent(RaceLogCourseDesignChangedEvent courseDesignChangedEvent) {
|
|
| 1176 | - DBObject result = new BasicDBObject();
|
|
| 1177 | - storeRaceLogEventProperties(courseDesignChangedEvent, result);
|
|
| 1178 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogCourseDesignChangedEvent.class.getSimpleName());
|
|
| 1179 | - result.put(FieldNames.RACE_LOG_COURSE_DESIGN_NAME.name(), courseDesignChangedEvent.getCourseDesign().getName());
|
|
| 1180 | - result.put(FieldNames.RACE_LOG_COURSE_DESIGN.name(), storeCourseBase(courseDesignChangedEvent.getCourseDesign()));
|
|
| 1181 | - return result;
|
|
| 1182 | - }
|
|
| 1183 | -
|
|
| 1184 | - private Object storeRaceLogFinishPositioningListChangedEvent(RaceLogFinishPositioningListChangedEvent finishPositioningListChangedEvent) {
|
|
| 1185 | - DBObject result = new BasicDBObject();
|
|
| 1186 | - storeRaceLogEventProperties(finishPositioningListChangedEvent, result);
|
|
| 1187 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogFinishPositioningListChangedEvent.class.getSimpleName());
|
|
| 1188 | - result.put(FieldNames.RACE_LOG_POSITIONED_COMPETITORS.name(), storePositionedCompetitors(finishPositioningListChangedEvent.getPositionedCompetitorsIDsNamesMaxPointsReasons()));
|
|
| 1189 | -
|
|
| 1190 | - return result;
|
|
| 1191 | - }
|
|
| 1192 | -
|
|
| 1193 | - private Object storeRaceLogFinishPositioningConfirmedEvent(RaceLogFinishPositioningConfirmedEvent finishPositioningConfirmedEvent) {
|
|
| 1194 | - DBObject result = new BasicDBObject();
|
|
| 1195 | - storeRaceLogEventProperties(finishPositioningConfirmedEvent, result);
|
|
| 1196 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogFinishPositioningConfirmedEvent.class.getSimpleName());
|
|
| 1197 | - result.put(FieldNames.RACE_LOG_POSITIONED_COMPETITORS.name(), storePositionedCompetitors(finishPositioningConfirmedEvent.getPositionedCompetitorsIDsNamesMaxPointsReasons()));
|
|
| 1198 | -
|
|
| 1199 | - return result;
|
|
| 1200 | - }
|
|
| 1201 | -
|
|
| 1202 | - private Object storeRaceLogGateLineOpeningTimeEvent(RaceLogGateLineOpeningTimeEvent gateLineOpeningTimeEvent){
|
|
| 1203 | - DBObject result = new BasicDBObject();
|
|
| 1204 | - storeRaceLogEventProperties(gateLineOpeningTimeEvent, result);
|
|
| 1205 | - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogGateLineOpeningTimeEvent.class.getSimpleName());
|
|
| 1206 | - result.put(FieldNames.RACE_LOG_GATE_LINE_OPENING_TIME.name(), gateLineOpeningTimeEvent.getGateLineOpeningTimes().getGateLaunchStopTime());
|
|
| 1207 | - result.put(FieldNames.RACE_LOG_GOLF_DOWN_TIME.name(), gateLineOpeningTimeEvent.getGateLineOpeningTimes().getGolfDownTime());
|
|
| 1208 | - return result;
|
|
| 1209 | - }
|
|
| 1210 | -
|
|
| 1211 | - private BasicDBList storePositionedCompetitors(List<com.sap.sse.common.Util.Triple<Serializable, String, MaxPointsReason>> positionedCompetitors) {
|
|
| 1212 | - BasicDBList dbList = new BasicDBList();
|
|
| 1213 | - if (positionedCompetitors != null) {
|
|
| 1214 | - for (com.sap.sse.common.Util.Triple<Serializable, String, MaxPointsReason> competitorPair : positionedCompetitors) {
|
|
| 1215 | - dbList.add(storePositionedCompetitor(competitorPair));
|
|
| 1216 | - }
|
|
| 1217 | - }
|
|
| 1218 | - return dbList;
|
|
| 1219 | - }
|
|
| 1220 | -
|
|
| 1221 | - private DBObject storePositionedCompetitor(com.sap.sse.common.Util.Triple<Serializable, String, MaxPointsReason> competitorTriple) {
|
|
| 1222 | - DBObject result = new BasicDBObject();
|
|
| 1223 | - result.put(FieldNames.COMPETITOR_ID.name(), competitorTriple.getA());
|
|
| 1224 | - result.put(FieldNames.COMPETITOR_DISPLAY_NAME.name(), competitorTriple.getB());
|
|
| 1225 | - result.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON.name(), competitorTriple.getC().name());
|
|
| 1226 | -
|
|
| 1227 | - return result;
|
|
| 1228 | - }
|
|
| 1229 | -
|
|
| 1230 | - private BasicDBList storeCourseBase(CourseBase courseData) {
|
|
| 1231 | - BasicDBList dbList = new BasicDBList();
|
|
| 1232 | -
|
|
| 1233 | - for (Waypoint waypoint : courseData.getWaypoints()) {
|
|
| 1234 | - dbList.add(storeWaypoint(waypoint));
|
|
| 1235 | - }
|
|
| 1236 | - return dbList;
|
|
| 1237 | - }
|
|
| 1238 | -
|
|
| 1239 | - private DBObject storeWaypoint(Waypoint waypoint) {
|
|
| 1240 | - DBObject result = new BasicDBObject();
|
|
| 1241 | - result.put(FieldNames.WAYPOINT_PASSINGINSTRUCTIONS.name(), getPassingInstructions(waypoint.getPassingInstructions()));
|
|
| 1242 | - result.put(FieldNames.CONTROLPOINT.name(), storeControlPoint(waypoint.getControlPoint()));
|
|
| 1243 | - return result;
|
|
| 1244 | - }
|
|
| 1245 | -
|
|
| 1246 | - private DBObject storeControlPoint(ControlPoint controlPoint) {
|
|
| 1247 | - DBObject result = new BasicDBObject();
|
|
| 1248 | - if (controlPoint instanceof Mark) {
|
|
| 1249 | - result.put(FieldNames.CONTROLPOINT_CLASS.name(), Mark.class.getSimpleName());
|
|
| 1250 | - result.put(FieldNames.CONTROLPOINT_VALUE.name(), storeMark((Mark) controlPoint));
|
|
| 1251 | - } else if (controlPoint instanceof ControlPointWithTwoMarks) {
|
|
| 1252 | - result.put(FieldNames.CONTROLPOINT_CLASS.name(), ControlPointWithTwoMarks.class.getSimpleName());
|
|
| 1253 | - result.put(FieldNames.CONTROLPOINT_VALUE.name(), storeControlPointWithTwoMarks((ControlPointWithTwoMarks) controlPoint));
|
|
| 1254 | - }
|
|
| 1255 | - return result;
|
|
| 1256 | - }
|
|
| 1257 | -
|
|
| 1258 | - private DBObject storeControlPointWithTwoMarks(ControlPointWithTwoMarks cpwtm) {
|
|
| 1259 | - DBObject result = new BasicDBObject();
|
|
| 1260 | - result.put(FieldNames.CONTROLPOINTWITHTWOMARKS_ID.name(), cpwtm.getId());
|
|
| 1261 | - result.put(FieldNames.CONTROLPOINTWITHTWOMARKS_NAME.name(), cpwtm.getName());
|
|
| 1262 | - result.put(FieldNames.CONTROLPOINTWITHTWOMARKS_LEFT.name(), storeMark(cpwtm.getLeft()));
|
|
| 1263 | - result.put(FieldNames.CONTROLPOINTWITHTWOMARKS_RIGHT.name(), storeMark(cpwtm.getRight()));
|
|
| 1264 | - return result;
|
|
| 1265 | - }
|
|
| 1266 | -
|
|
| 1267 | - private DBObject storeMark(Mark mark) {
|
|
| 1268 | - DBObject result = new BasicDBObject();
|
|
| 1269 | - result.put(FieldNames.MARK_ID.name(), mark.getId());
|
|
| 1270 | - result.put(FieldNames.MARK_COLOR.name(), mark.getColor());
|
|
| 1271 | - result.put(FieldNames.MARK_NAME.name(), mark.getName());
|
|
| 1272 | - result.put(FieldNames.MARK_PATTERN.name(), mark.getPattern());
|
|
| 1273 | - result.put(FieldNames.MARK_SHAPE.name(), mark.getShape());
|
|
| 1274 | - result.put(FieldNames.MARK_TYPE.name(), mark.getType() == null ? null : mark.getType().name());
|
|
| 1275 | - return result;
|
|
| 1276 | - }
|
|
| 1277 | -
|
|
| 1278 | - private String getPassingInstructions(PassingInstruction passingInstructions) {
|
|
| 1279 | - final String passing;
|
|
| 1280 | - if (passingInstructions != null) {
|
|
| 1281 | - passing = passingInstructions.name();
|
|
| 1282 | - } else {
|
|
| 1283 | - passing = null;
|
|
| 1284 | - }
|
|
| 1285 | - return passing;
|
|
| 1286 | - }
|
|
| 1287 | - @Override
|
|
| 1288 | - public void storeCompetitor(Competitor competitor) {
|
|
| 1289 | - DBCollection collection = database.getCollection(CollectionNames.COMPETITORS.name());
|
|
| 1290 | - JSONObject json = competitorSerializer.serialize(competitor);
|
|
| 1291 | - DBObject query = (DBObject) JSON.parse(CompetitorJsonSerializer.getCompetitorIdQuery(competitor).toString());
|
|
| 1292 | - DBObject entry = (DBObject) JSON.parse(json.toString());
|
|
| 1293 | - collection.update(query, entry, /* upsrt */true, /* multi */false, WriteConcern.SAFE);
|
|
| 1294 | - }
|
|
| 1295 | -
|
|
| 1296 | - @Override
|
|
| 1297 | - public void removeAllCompetitors() {
|
|
| 1298 | - logger.info("Removing all persistent competitor info");
|
|
| 1299 | - DBCollection collection = database.getCollection(CollectionNames.COMPETITORS.name());
|
|
| 1300 | - collection.drop();
|
|
| 1301 | - }
|
|
| 1302 | -
|
|
| 1303 | - @Override
|
|
| 1304 | - public void removeCompetitor(Competitor competitor) {
|
|
| 1305 | - logger.info("Removing persistent competitor info for competitor "+competitor.getName()+" with ID "+competitor.getId());
|
|
| 1306 | - DBCollection collection = database.getCollection(CollectionNames.COMPETITORS.name());
|
|
| 1307 | - DBObject query = (DBObject) JSON.parse(CompetitorJsonSerializer.getCompetitorIdQuery(competitor).toString());
|
|
| 1308 | - collection.remove(query, WriteConcern.SAFE);
|
|
| 1309 | - }
|
|
| 1310 | -
|
|
| 1311 | - @Override
|
|
| 1312 | - public void storeDeviceConfiguration(DeviceConfigurationMatcher matcher, DeviceConfiguration configuration) {
|
|
| 1313 | - DBCollection configurationsCollections = database.getCollection(CollectionNames.CONFIGURATIONS.name());
|
|
| 1314 | -
|
|
| 1315 | - DBObject query = new BasicDBObject();
|
|
| 1316 | - query.put(FieldNames.CONFIGURATION_MATCHER_ID.name(), matcher.getMatcherIdentifier());
|
|
| 1317 | -
|
|
| 1318 | - DBObject entryObject = new BasicDBObject();
|
|
| 1319 | - entryObject.put(FieldNames.CONFIGURATION_MATCHER_ID.name(), matcher.getMatcherIdentifier());
|
|
| 1320 | - entryObject.put(FieldNames.CONFIGURATION_MATCHER.name(), createDeviceConfigurationMatcherObject(matcher));
|
|
| 1321 | - entryObject.put(FieldNames.CONFIGURATION_CONFIG.name(), createDeviceConfigurationObject(configuration));
|
|
| 1322 | -
|
|
| 1323 | - configurationsCollections.update(query, entryObject, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
|
|
| 1324 | - }
|
|
| 1325 | -
|
|
| 1326 | - private DBObject createDeviceConfigurationMatcherObject(DeviceConfigurationMatcher matcher) {
|
|
| 1327 | - DBObject matcherObject = new BasicDBObject();
|
|
| 1328 | - matcherObject.put(FieldNames.CONFIGURATION_MATCHER_TYPE.name(), matcher.getMatcherType().name());
|
|
| 1329 | - if (matcher instanceof DeviceConfigurationMatcherSingle) {
|
|
| 1330 | - BasicDBList client = new BasicDBList();
|
|
| 1331 | - client.add(((DeviceConfigurationMatcherSingle)matcher).getClientIdentifier());
|
|
| 1332 | - matcherObject.put(FieldNames.CONFIGURATION_MATCHER_CLIENTS.name(), client);
|
|
| 1333 | - } else if (matcher instanceof DeviceConfigurationMatcherMulti) {
|
|
| 1334 | - BasicDBList clients = new BasicDBList();
|
|
| 1335 | - Util.addAll(((DeviceConfigurationMatcherMulti)matcher).getClientIdentifiers(), clients);
|
|
| 1336 | - matcherObject.put(FieldNames.CONFIGURATION_MATCHER_CLIENTS.name(), clients);
|
|
| 1337 | - }
|
|
| 1338 | - return matcherObject;
|
|
| 1339 | - }
|
|
| 1340 | -
|
|
| 1341 | - private DBObject createDeviceConfigurationObject(DeviceConfiguration configuration) {
|
|
| 1342 | - JsonSerializer<DeviceConfiguration> serializer = DeviceConfigurationJsonSerializer.create();
|
|
| 1343 | - JSONObject json = serializer.serialize(configuration);
|
|
| 1344 | - DBObject entry = (DBObject) JSON.parse(json.toString());
|
|
| 1345 | - return entry;
|
|
| 1346 | - }
|
|
| 1347 | -
|
|
| 1348 | - @Override
|
|
| 1349 | - public void removeDeviceConfiguration(DeviceConfigurationMatcher matcher) {
|
|
| 1350 | - DBCollection configurationsCollections = database.getCollection(CollectionNames.CONFIGURATIONS.name());
|
|
| 1351 | - DBObject query = new BasicDBObject();
|
|
| 1352 | - query.put(FieldNames.CONFIGURATION_MATCHER_ID.name(), matcher.getMatcherIdentifier());
|
|
| 1353 | - configurationsCollections.remove(query);
|
|
| 1354 | - }
|
|
| 1355 | -
|
|
| 1356 | - public static DBObject storeDeviceId(
|
|
| 1357 | - TypeBasedServiceFinder<DeviceIdentifierMongoHandler> deviceIdentifierServiceFinder, DeviceIdentifier device)
|
|
| 1358 | - throws TransformationException, NoCorrespondingServiceRegisteredException {
|
|
| 1359 | - String type = device.getIdentifierType();
|
|
| 1360 | - DeviceIdentifierMongoHandler handler = deviceIdentifierServiceFinder.findService(type);
|
|
| 1361 | - com.sap.sse.common.Util.Pair<String, ? extends Object> pair = handler.serialize(device);
|
|
| 1362 | - type = pair.getA();
|
|
| 1363 | - Object deviceTypeSpecificId = pair.getB();
|
|
| 1364 | - return new BasicDBObjectBuilder()
|
|
| 1365 | - .add(FieldNames.DEVICE_TYPE.name(), type)
|
|
| 1366 | - .add(FieldNames.DEVICE_TYPE_SPECIFIC_ID.name(), deviceTypeSpecificId)
|
|
| 1367 | - .add(FieldNames.DEVICE_STRING_REPRESENTATION.name(), device.getStringRepresentation()).get();
|
|
| 1368 | - }
|
|
| 1369 | -
|
|
| 1370 | - void storeRaceLogEventEvent(DBObject eventEntry) {
|
|
| 1371 | - getRaceLogCollection().insert(eventEntry);
|
|
| 1372 | - }
|
|
| 1373 | -
|
|
| 1374 | - @Override
|
|
| 1375 | - public void removeRaceLog(RaceLogIdentifier identifier) {
|
|
| 1376 | - DBObject query = new BasicDBObject();
|
|
| 1377 | - storeRaceLogIdentifier(identifier, query);
|
|
| 1378 | - getRaceLogCollection().remove(query);
|
|
| 1379 | - }
|
|
| 1380 | -
|
|
| 1381 | - @Override
|
|
| 1382 | - public void removeRegattaLog(RegattaLikeIdentifier identifier) {
|
|
| 1383 | - DBObject query = new BasicDBObject();
|
|
| 1384 | - addRegattaLikeIdentifier(identifier, query);
|
|
| 1385 | - getRegattaLogCollection().remove(query);
|
|
| 1386 | - }
|
|
| 1387 | -
|
|
| 1388 | - @Override
|
|
| 1389 | - public void storeResultUrl(String resultProviderName, URL url) {
|
|
| 1390 | - DBCollection resultUrlsCollection = database.getCollection(CollectionNames.RESULT_URLS.name());
|
|
| 1391 | - DBObject query = new BasicDBObject(FieldNames.RESULT_PROVIDERNAME.name(), resultProviderName);
|
|
| 1392 | - DBObject entry = new BasicDBObject(FieldNames.RESULT_PROVIDERNAME.name(), resultProviderName);
|
|
| 1393 | - entry.put(FieldNames.RESULT_URL.name(), url.toString());
|
|
| 1394 | - resultUrlsCollection.update(query, entry, /* upsrt */true, /* multi */false, WriteConcern.SAFE);
|
|
| 1395 | - }
|
|
| 1396 | -
|
|
| 1397 | - @Override
|
|
| 1398 | - public void removeResultUrl(String resultProviderName, URL url) {
|
|
| 1399 | - DBCollection resultUrlsCollection = database.getCollection(CollectionNames.RESULT_URLS.name());
|
|
| 1400 | - DBObject query = new BasicDBObjectBuilder().add(FieldNames.RESULT_PROVIDERNAME.name(), resultProviderName)
|
|
| 1401 | - .add(FieldNames.RESULT_URL.name(), url.toString()).get();
|
|
| 1402 | - resultUrlsCollection.remove(query);
|
|
| 1403 | - }
|
|
| 1404 | -
|
|
| 1405 | - public DBCollection getRegattaLogCollection() {
|
|
| 1406 | - DBCollection result = database.getCollection(CollectionNames.REGATTA_LOGS.name());
|
|
| 1407 | - DBObject index = new BasicDBObject(FieldNames.REGATTA_LOG_IDENTIFIER_TYPE.name(), null);
|
|
| 1408 | - index.put(FieldNames.REGATTA_LOG_IDENTIFIER_NAME.name(), null);
|
|
| 1409 | - result.createIndex(index);
|
|
| 1410 | - return result;
|
|
| 1411 | - }
|
|
| 1412 | -
|
|
| 1413 | - private DBObject createBasicRegattaLogEventDBObject(RegattaLogEvent event) {
|
|
| 1414 | - DBObject result = new BasicDBObject();
|
|
| 1415 | - storeTimed(event, result);
|
|
| 1416 | - storeTimePoint(event.getCreatedAt(), result, FieldNames.REGATTA_LOG_EVENT_CREATED_AT);
|
|
| 1417 | - result.put(FieldNames.REGATTA_LOG_EVENT_ID.name(), event.getId());
|
|
| 1418 | - result.put(FieldNames.REGATTA_LOG_EVENT_AUTHOR_NAME.name(), event.getAuthor().getName());
|
|
| 1419 | - result.put(FieldNames.REGATTA_LOG_EVENT_AUTHOR_PRIORITY.name(), event.getAuthor().getPriority());
|
|
| 1420 | - return result;
|
|
| 1421 | - }
|
|
| 1422 | -
|
|
| 1423 | - private void addRegattaLikeIdentifier(RegattaLikeIdentifier regattaLikeId, DBObject toObject) {
|
|
| 1424 | - toObject.put(FieldNames.REGATTA_LOG_IDENTIFIER_TYPE.name(), regattaLikeId.getIdentifierType());
|
|
| 1425 | - toObject.put(FieldNames.REGATTA_LOG_IDENTIFIER_NAME.name(), regattaLikeId.getName());
|
|
| 1426 | - }
|
|
| 1427 | -
|
|
| 1428 | - private void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, DBObject innerObject) {
|
|
| 1429 | - DBObject result = new BasicDBObject(FieldNames.REGATTA_LOG_EVENT.name(), innerObject);
|
|
| 1430 | - addRegattaLikeIdentifier(regattaLikeId, result);
|
|
| 1431 | - getRegattaLogCollection().insert(result);
|
|
| 1432 | - }
|
|
| 1433 | -
|
|
| 1434 | - public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogDeviceCompetitorMappingEvent event) {
|
|
| 1435 | - DBObject result = createBasicRegattaLogEventDBObject(event);
|
|
| 1436 | - result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogDeviceCompetitorMappingEvent.class.getSimpleName());
|
|
| 1437 | - storeDeviceMappingEvent(event, result, FieldNames.REGATTA_LOG_FROM, FieldNames.REGATTA_LOG_TO);
|
|
| 1438 | - result.put(FieldNames.COMPETITOR_ID.name(), event.getMappedTo().getId());
|
|
| 1439 | - storeRegattaLogEvent(regattaLikeId, result);
|
|
| 1440 | - }
|
|
| 1441 | -
|
|
| 1442 | - public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogDeviceMarkMappingEvent event) {
|
|
| 1443 | - DBObject result = createBasicRegattaLogEventDBObject(event);
|
|
| 1444 | - result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogDeviceMarkMappingEvent.class.getSimpleName());
|
|
| 1445 | - storeDeviceMappingEvent(event, result, FieldNames.REGATTA_LOG_FROM, FieldNames.REGATTA_LOG_TO);
|
|
| 1446 | - result.put(FieldNames.MARK.name(), storeMark(event.getMappedTo()));
|
|
| 1447 | - storeRegattaLogEvent(regattaLikeId, result);
|
|
| 1448 | - }
|
|
| 1449 | -
|
|
| 1450 | - public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogRevokeEvent event) {
|
|
| 1451 | - DBObject result = createBasicRegattaLogEventDBObject(event);
|
|
| 1452 | - result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogRevokeEvent.class.getSimpleName());
|
|
| 1453 | - result.put(FieldNames.REGATTA_LOG_REVOKED_EVENT_ID.name(), event.getRevokedEventId());
|
|
| 1454 | - result.put(FieldNames.REGATTA_LOG_REVOKED_EVENT_TYPE.name(), event.getRevokedEventType());
|
|
| 1455 | - result.put(FieldNames.REGATTA_LOG_REVOKED_EVENT_SHORT_INFO.name(), event.getRevokedEventShortInfo());
|
|
| 1456 | - result.put(FieldNames.REGATTA_LOG_REVOKED_REASON.name(), event.getReason());
|
|
| 1457 | - storeRegattaLogEvent(regattaLikeId, result);
|
|
| 1458 | - }
|
|
| 1459 | -
|
|
| 1460 | - public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogRegisterCompetitorEvent event) {
|
|
| 1461 | - DBObject result = createBasicRegattaLogEventDBObject(event);
|
|
| 1462 | - result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogRegisterCompetitorEvent.class.getSimpleName());
|
|
| 1463 | - result.put(FieldNames.REGATTA_LOG_COMPETITOR_ID.name(), event.getCompetitor().getId());
|
|
| 1464 | - storeRegattaLogEvent(regattaLikeId, result);
|
|
| 1465 | - }
|
|
| 1466 | -
|
|
| 1467 | - public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogCloseOpenEndedDeviceMappingEvent event) {
|
|
| 1468 | - DBObject result = createBasicRegattaLogEventDBObject(event);
|
|
| 1469 | - result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName());
|
|
| 1470 | - result.put(FieldNames.REGATTA_LOG_DEVICE_MAPPING_EVENT_ID.name(), event.getDeviceMappingEventId());
|
|
| 1471 | - storeTimePoint(event.getClosingTimePoint(), result, FieldNames.REGATTA_LOG_CLOSING_TIMEPOINT);
|
|
| 1472 | - storeRegattaLogEvent(regattaLikeId, result);
|
|
| 1473 | - }
|
|
| 1474 | -
|
|
| 1475 | - public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogSetCompetitorTimeOnTimeFactorEvent event) {
|
|
| 1476 | - DBObject result = createBasicRegattaLogEventDBObject(event);
|
|
| 1477 | - result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName());
|
|
| 1478 | - result.put(FieldNames.REGATTA_LOG_COMPETITOR_ID.name(), event.getCompetitor().getId());
|
|
| 1479 | - result.put(FieldNames.REGATTA_LOG_TIME_ON_TIME_FACTOR.name(), event.getTimeOnTimeFactor());
|
|
| 1480 | - }
|
|
| 1481 | -
|
|
| 1482 | - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDependentStartTimeEvent event) {
|
|
| 1483 | - BasicDBObject result = new BasicDBObject();
|
|
| 1484 | - storeRaceLogIdentifier(raceLogIdentifier, result);
|
|
| 1485 | - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDependentStartTimeEvent(event));
|
|
| 1486 | - return result;
|
|
| 1487 | - }
|
|
| 1488 | -
|
|
| 1489 | - public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogSetCompetitorTimeOnDistanceAllowancePerNauticalMileEvent event) {
|
|
| 1490 | - DBObject result = createBasicRegattaLogEventDBObject(event);
|
|
| 1491 | - result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName());
|
|
| 1492 | - result.put(FieldNames.REGATTA_LOG_COMPETITOR_ID.name(), event.getCompetitor().getId());
|
|
| 1493 | - result.put(FieldNames.REGATTA_LOG_TIME_ON_DISTANCE_SECONDS_ALLOWANCE_PER_NAUTICAL_MILE.name(), event.getTimeOnDistanceAllowancePerNauticalMile().asSeconds());
|
|
| 1494 | - storeRegattaLogEvent(regattaLikeId, result);
|
|
| 1495 | - }
|
|
| 1496 | -
|
|
| 1497 | - private DBObject createImageObject(ImageDescriptor image) {
|
|
| 1498 | - DBObject result = new BasicDBObject();
|
|
| 1499 | - result.put(FieldNames.IMAGE_URL.name(), image.getURL().toString());
|
|
| 1500 | - result.put(FieldNames.IMAGE_LOCALE.name(), image.getLocale() != null ? image.getLocale().toLanguageTag() : null);
|
|
| 1501 | - result.put(FieldNames.IMAGE_TITLE.name(), image.getTitle());
|
|
| 1502 | - result.put(FieldNames.IMAGE_SUBTITLE.name(), image.getSubtitle());
|
|
| 1503 | - result.put(FieldNames.IMAGE_COPYRIGHT.name(), image.getCopyright());
|
|
| 1504 | - result.put(FieldNames.IMAGE_WIDTH_IN_PX.name(), image.getWidthInPx());
|
|
| 1505 | - result.put(FieldNames.IMAGE_HEIGHT_IN_PX.name(), image.getHeightInPx());
|
|
| 1506 | - storeTimePoint(image.getCreatedAtDate(), result, FieldNames.IMAGE_CREATEDATDATE);
|
|
| 1507 | - BasicDBList tags = new BasicDBList();
|
|
| 1508 | - for (String tag : image.getTags()) {
|
|
| 1509 | - tags.add(tag);
|
|
| 1510 | - }
|
|
| 1511 | - result.put(FieldNames.IMAGE_TAGS.name(), tags);
|
|
| 1512 | - return result;
|
|
| 1513 | - }
|
|
| 1514 | -
|
|
| 1515 | - private DBObject createVideoObject(VideoDescriptor video) {
|
|
| 1516 | - DBObject result = new BasicDBObject();
|
|
| 1517 | - result.put(FieldNames.VIDEO_URL.name(), video.getURL().toString());
|
|
| 1518 | - result.put(FieldNames.VIDEO_LOCALE.name(), video.getLocale() != null ? video.getLocale().toLanguageTag() : null);
|
|
| 1519 | - result.put(FieldNames.VIDEO_THUMBNAIL_URL.name(), video.getThumbnailURL() != null ? video.getThumbnailURL().toString() : null);
|
|
| 1520 | - result.put(FieldNames.VIDEO_TITLE.name(), video.getTitle());
|
|
| 1521 | - result.put(FieldNames.VIDEO_SUBTITLE.name(), video.getSubtitle());
|
|
| 1522 | - result.put(FieldNames.VIDEO_MIMETYPE.name(), video.getMimeType() != null ? video.getMimeType().name() : null);
|
|
| 1523 | - result.put(FieldNames.VIDEO_COPYRIGHT.name(), video.getCopyright());
|
|
| 1524 | - result.put(FieldNames.VIDEO_LENGTH_IN_SECONDS.name(), video.getLengthInSeconds());
|
|
| 1525 | - storeTimePoint(video.getCreatedAtDate(), result, FieldNames.VIDEO_CREATEDATDATE);
|
|
| 1526 | - BasicDBList tags = new BasicDBList();
|
|
| 1527 | - for (String tag : video.getTags()) {
|
|
| 1528 | - tags.add(tag);
|
|
| 1529 | - }
|
|
| 1530 | - result.put(FieldNames.VIDEO_TAGS.name(), tags);
|
|
| 1531 | - return result;
|
|
| 1532 | - }
|
|
| 1533 | -}
|
|
| 1 | +package com.sap.sailing.domain.persistence.impl; |
|
| 2 | + |
|
| 3 | +import java.io.Serializable; |
|
| 4 | +import java.net.URL; |
|
| 5 | +import java.util.List; |
|
| 6 | +import java.util.Map.Entry; |
|
| 7 | +import java.util.logging.Level; |
|
| 8 | +import java.util.logging.Logger; |
|
| 9 | + |
|
| 10 | +import org.bson.types.ObjectId; |
|
| 11 | +import org.json.simple.JSONObject; |
|
| 12 | + |
|
| 13 | +import com.mongodb.BasicDBList; |
|
| 14 | +import com.mongodb.BasicDBObject; |
|
| 15 | +import com.mongodb.BasicDBObjectBuilder; |
|
| 16 | +import com.mongodb.DB; |
|
| 17 | +import com.mongodb.DBCollection; |
|
| 18 | +import com.mongodb.DBObject; |
|
| 19 | +import com.mongodb.WriteConcern; |
|
| 20 | +import com.mongodb.util.JSON; |
|
| 21 | +import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; |
|
| 22 | +import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent; |
|
| 23 | +import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent; |
|
| 24 | +import com.sap.sailing.domain.abstractlog.race.RaceLogDependentStartTimeEvent; |
|
| 25 | +import com.sap.sailing.domain.abstractlog.race.RaceLogEndOfTrackingEvent; |
|
| 26 | +import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; |
|
| 27 | +import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningConfirmedEvent; |
|
| 28 | +import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningListChangedEvent; |
|
| 29 | +import com.sap.sailing.domain.abstractlog.race.RaceLogFixedMarkPassingEvent; |
|
| 30 | +import com.sap.sailing.domain.abstractlog.race.RaceLogFlagEvent; |
|
| 31 | +import com.sap.sailing.domain.abstractlog.race.RaceLogGateLineOpeningTimeEvent; |
|
| 32 | +import com.sap.sailing.domain.abstractlog.race.RaceLogPassChangeEvent; |
|
| 33 | +import com.sap.sailing.domain.abstractlog.race.RaceLogPathfinderEvent; |
|
| 34 | +import com.sap.sailing.domain.abstractlog.race.RaceLogProtestStartTimeEvent; |
|
| 35 | +import com.sap.sailing.domain.abstractlog.race.RaceLogRaceStatusEvent; |
|
| 36 | +import com.sap.sailing.domain.abstractlog.race.RaceLogRevokeEvent; |
|
| 37 | +import com.sap.sailing.domain.abstractlog.race.RaceLogStartOfTrackingEvent; |
|
| 38 | +import com.sap.sailing.domain.abstractlog.race.RaceLogStartProcedureChangedEvent; |
|
| 39 | +import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent; |
|
| 40 | +import com.sap.sailing.domain.abstractlog.race.RaceLogSuppressedMarkPassingsEvent; |
|
| 41 | +import com.sap.sailing.domain.abstractlog.race.RaceLogWindFixEvent; |
|
| 42 | +import com.sap.sailing.domain.abstractlog.race.scoring.RaceLogAdditionalScoringInformationEvent; |
|
| 43 | +import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogCloseOpenEndedDeviceMappingEvent; |
|
| 44 | +import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDefineMarkEvent; |
|
| 45 | +import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDenoteForTrackingEvent; |
|
| 46 | +import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDeviceCompetitorMappingEvent; |
|
| 47 | +import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDeviceMarkMappingEvent; |
|
| 48 | +import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogRegisterCompetitorEvent; |
|
| 49 | +import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogStartTrackingEvent; |
|
| 50 | +import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent; |
|
| 51 | +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogCloseOpenEndedDeviceMappingEvent; |
|
| 52 | +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorMappingEvent; |
|
| 53 | +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMarkMappingEvent; |
|
| 54 | +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRegisterCompetitorEvent; |
|
| 55 | +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRevokeEvent; |
|
| 56 | +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogSetCompetitorTimeOnDistanceAllowancePerNauticalMileEvent; |
|
| 57 | +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogSetCompetitorTimeOnTimeFactorEvent; |
|
| 58 | +import com.sap.sailing.domain.abstractlog.shared.events.DeviceMappingEvent; |
|
| 59 | +import com.sap.sailing.domain.base.Competitor; |
|
| 60 | +import com.sap.sailing.domain.base.ControlPoint; |
|
| 61 | +import com.sap.sailing.domain.base.ControlPointWithTwoMarks; |
|
| 62 | +import com.sap.sailing.domain.base.CourseArea; |
|
| 63 | +import com.sap.sailing.domain.base.CourseBase; |
|
| 64 | +import com.sap.sailing.domain.base.Event; |
|
| 65 | +import com.sap.sailing.domain.base.Fleet; |
|
| 66 | +import com.sap.sailing.domain.base.Mark; |
|
| 67 | +import com.sap.sailing.domain.base.RaceColumn; |
|
| 68 | +import com.sap.sailing.domain.base.RaceDefinition; |
|
| 69 | +import com.sap.sailing.domain.base.Regatta; |
|
| 70 | +import com.sap.sailing.domain.base.RemoteSailingServerReference; |
|
| 71 | +import com.sap.sailing.domain.base.SailingServerConfiguration; |
|
| 72 | +import com.sap.sailing.domain.base.Series; |
|
| 73 | +import com.sap.sailing.domain.base.Venue; |
|
| 74 | +import com.sap.sailing.domain.base.Waypoint; |
|
| 75 | +import com.sap.sailing.domain.base.configuration.DeviceConfiguration; |
|
| 76 | +import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher; |
|
| 77 | +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; |
|
| 78 | +import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationMatcherMulti; |
|
| 79 | +import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationMatcherSingle; |
|
| 80 | +import com.sap.sailing.domain.base.impl.FleetImpl; |
|
| 81 | +import com.sap.sailing.domain.common.Bearing; |
|
| 82 | +import com.sap.sailing.domain.common.MaxPointsReason; |
|
| 83 | +import com.sap.sailing.domain.common.PassingInstruction; |
|
| 84 | +import com.sap.sailing.domain.common.Positioned; |
|
| 85 | +import com.sap.sailing.domain.common.RaceIdentifier; |
|
| 86 | +import com.sap.sailing.domain.common.Speed; |
|
| 87 | +import com.sap.sailing.domain.common.SpeedWithBearing; |
|
| 88 | +import com.sap.sailing.domain.common.Wind; |
|
| 89 | +import com.sap.sailing.domain.common.WindSource; |
|
| 90 | +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; |
|
| 91 | +import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; |
|
| 92 | +import com.sap.sailing.domain.leaderboard.Leaderboard; |
|
| 93 | +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; |
|
| 94 | +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; |
|
| 95 | +import com.sap.sailing.domain.leaderboard.ResultDiscardingRule; |
|
| 96 | +import com.sap.sailing.domain.leaderboard.SettableScoreCorrection; |
|
| 97 | +import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; |
|
| 98 | +import com.sap.sailing.domain.persistence.MongoObjectFactory; |
|
| 99 | +import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler; |
|
| 100 | +import com.sap.sailing.domain.persistence.racelog.tracking.impl.PlaceHolderDeviceIdentifierMongoHandler; |
|
| 101 | +import com.sap.sailing.domain.racelog.RaceLogIdentifier; |
|
| 102 | +import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; |
|
| 103 | +import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier; |
|
| 104 | +import com.sap.sailing.domain.tracking.TrackedRace; |
|
| 105 | +import com.sap.sailing.domain.tracking.TrackedRegatta; |
|
| 106 | +import com.sap.sailing.domain.tracking.WindTrack; |
|
| 107 | +import com.sap.sailing.server.gateway.serialization.JsonSerializer; |
|
| 108 | +import com.sap.sailing.server.gateway.serialization.impl.CompetitorJsonSerializer; |
|
| 109 | +import com.sap.sailing.server.gateway.serialization.impl.DeviceConfigurationJsonSerializer; |
|
| 110 | +import com.sap.sailing.server.gateway.serialization.impl.RegattaConfigurationJsonSerializer; |
|
| 111 | +import com.sap.sse.common.Duration; |
|
| 112 | +import com.sap.sse.common.NoCorrespondingServiceRegisteredException; |
|
| 113 | +import com.sap.sse.common.TimePoint; |
|
| 114 | +import com.sap.sse.common.TimeRange; |
|
| 115 | +import com.sap.sse.common.Timed; |
|
| 116 | +import com.sap.sse.common.TypeBasedServiceFinder; |
|
| 117 | +import com.sap.sse.common.TypeBasedServiceFinderFactory; |
|
| 118 | +import com.sap.sse.common.Util; |
|
| 119 | +import com.sap.sse.common.impl.MillisecondsTimePoint; |
|
| 120 | +import com.sap.sse.shared.media.ImageDescriptor; |
|
| 121 | +import com.sap.sse.shared.media.VideoDescriptor; |
|
| 122 | + |
|
| 123 | +public class MongoObjectFactoryImpl implements MongoObjectFactory { |
|
| 124 | + private static Logger logger = Logger.getLogger(MongoObjectFactoryImpl.class.getName()); |
|
| 125 | + private final DB database; |
|
| 126 | + private final CompetitorJsonSerializer competitorSerializer = CompetitorJsonSerializer.create(); |
|
| 127 | + private final TypeBasedServiceFinder<DeviceIdentifierMongoHandler> deviceIdentifierServiceFinder; |
|
| 128 | + |
|
| 129 | + /** |
|
| 130 | + * Uses <code>null</code> for the device type service finder and hence will be unable to store device identifiers. |
|
| 131 | + * Use this constructor only for testing purposes or in cases where there will happen absolutely no access to |
|
| 132 | + * {@link DeviceIdentifier} objects. |
|
| 133 | + */ |
|
| 134 | + public MongoObjectFactoryImpl(DB database) { |
|
| 135 | + this(database, /* deviceTypeServiceFinder */ null); |
|
| 136 | + } |
|
| 137 | + |
|
| 138 | + public MongoObjectFactoryImpl(DB database, TypeBasedServiceFinderFactory serviceFinderFactory) { |
|
| 139 | + this.database = database; |
|
| 140 | + if (serviceFinderFactory != null) { |
|
| 141 | + this.deviceIdentifierServiceFinder = serviceFinderFactory.createServiceFinder(DeviceIdentifierMongoHandler.class); |
|
| 142 | + this.deviceIdentifierServiceFinder.setFallbackService(new PlaceHolderDeviceIdentifierMongoHandler()); |
|
| 143 | + } else { |
|
| 144 | + this.deviceIdentifierServiceFinder = null; |
|
| 145 | + } |
|
| 146 | + } |
|
| 147 | + |
|
| 148 | + @Override |
|
| 149 | + public DB getDatabase() { |
|
| 150 | + return database; |
|
| 151 | + } |
|
| 152 | + |
|
| 153 | + public DBObject storeWind(Wind wind) { |
|
| 154 | + DBObject result = new BasicDBObject(); |
|
| 155 | + storePositioned(wind, result); |
|
| 156 | + storeTimed(wind, result); |
|
| 157 | + storeSpeedWithBearing(wind, result); |
|
| 158 | + return result; |
|
| 159 | + } |
|
| 160 | + |
|
| 161 | + public static void storeTimePoint(TimePoint timePoint, DBObject result, String fieldName) { |
|
| 162 | + if (timePoint != null) { |
|
| 163 | + result.put(fieldName, timePoint.asMillis()); |
|
| 164 | + } |
|
| 165 | + } |
|
| 166 | + |
|
| 167 | + public static void storeTimePoint(TimePoint timePoint, DBObject result, FieldNames field) { |
|
| 168 | + storeTimePoint(timePoint, result, field.name()); |
|
| 169 | + } |
|
| 170 | + |
|
| 171 | + public static void storeTimeRange(TimeRange timeRange, DBObject result, FieldNames field) { |
|
| 172 | + if (timeRange != null) { |
|
| 173 | + DBObject timeRangeObj = new BasicDBObject(); |
|
| 174 | + storeTimePoint(timeRange.from(), timeRangeObj, FieldNames.FROM_MILLIS); |
|
| 175 | + storeTimePoint(timeRange.to(), timeRangeObj, FieldNames.TO_MILLIS); |
|
| 176 | + result.put(field.name(), timeRangeObj); |
|
| 177 | + } |
|
| 178 | + } |
|
| 179 | + |
|
| 180 | + public void storeTimed(Timed timed, DBObject result) { |
|
| 181 | + if (timed.getTimePoint() != null) { |
|
| 182 | + storeTimePoint(timed.getTimePoint(), result, FieldNames.TIME_AS_MILLIS); |
|
| 183 | + } |
|
| 184 | + } |
|
| 185 | + |
|
| 186 | + public void storeSpeedWithBearing(SpeedWithBearing speedWithBearing, DBObject result) { |
|
| 187 | + storeSpeed(speedWithBearing, result); |
|
| 188 | + storeBearing(speedWithBearing.getBearing(), result); |
|
| 189 | + |
|
| 190 | + } |
|
| 191 | + |
|
| 192 | + public void storeBearing(Bearing bearing, DBObject result) { |
|
| 193 | + result.put(FieldNames.DEGREE_BEARING.name(), bearing.getDegrees()); |
|
| 194 | + } |
|
| 195 | + |
|
| 196 | + public void storeSpeed(Speed speed, DBObject result) { |
|
| 197 | + result.put(FieldNames.KNOT_SPEED.name(), speed.getKnots()); |
|
| 198 | + } |
|
| 199 | + |
|
| 200 | + public void storePositioned(Positioned positioned, DBObject result) { |
|
| 201 | + if (positioned.getPosition() != null) { |
|
| 202 | + result.put(FieldNames.LAT_DEG.name(), positioned.getPosition().getLatDeg()); |
|
| 203 | + result.put(FieldNames.LNG_DEG.name(), positioned.getPosition().getLngDeg()); |
|
| 204 | + } |
|
| 205 | + } |
|
| 206 | + |
|
| 207 | + @Override |
|
| 208 | + public void addWindTrackDumper(TrackedRegatta trackedRegatta, TrackedRace trackedRace, WindSource windSource) { |
|
| 209 | + WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource); |
|
| 210 | + windTrack.addListener(new MongoWindListener(trackedRace, trackedRegatta.getRegatta().getName(), windSource, this, database)); |
|
| 211 | + } |
|
| 212 | + |
|
| 213 | + public DBCollection getWindTrackCollection() { |
|
| 214 | + DBCollection result = database.getCollection(CollectionNames.WIND_TRACKS.name()); |
|
| 215 | + result.createIndex(new BasicDBObject(FieldNames.REGATTA_NAME.name(), null)); |
|
| 216 | + return result; |
|
| 217 | + } |
|
| 218 | + |
|
| 219 | + public DBCollection getGPSFixCollection() { |
|
| 220 | + DBCollection gpsFixCollection = database.getCollection(CollectionNames.GPS_FIXES.name()); |
|
| 221 | + DBObject index = new BasicDBObject(); |
|
| 222 | + index.put(FieldNames.DEVICE_ID.name(), null); |
|
| 223 | + index.put(FieldNames.TIME_AS_MILLIS.name(), null); |
|
| 224 | + gpsFixCollection.createIndex(index); |
|
| 225 | + return gpsFixCollection; |
|
| 226 | + } |
|
| 227 | + |
|
| 228 | + public DBCollection getGPSFixMetadataCollection() { |
|
| 229 | + DBCollection collection = database.getCollection(CollectionNames.GPS_FIXES_METADATA.name()); |
|
| 230 | + DBObject index = new BasicDBObject(); |
|
| 231 | + index.put(FieldNames.DEVICE_ID.name(), null); |
|
| 232 | + collection.createIndex(index); |
|
| 233 | + return collection; |
|
| 234 | + } |
|
| 235 | + |
|
| 236 | + /** |
|
| 237 | + * @param regattaName |
|
| 238 | + * the regatta name is stored only for human readability purposes because a time stamp may be a bit unhandy for |
|
| 239 | + * identifying where the wind fix was collected |
|
| 240 | + */ |
|
| 241 | + public DBObject storeWindTrackEntry(RaceDefinition race, String regattaName, WindSource windSource, Wind wind) { |
|
| 242 | + BasicDBObject result = new BasicDBObject(); |
|
| 243 | + result.put(FieldNames.RACE_ID.name(), race.getId()); |
|
| 244 | + result.put(FieldNames.REGATTA_NAME.name(), regattaName); |
|
| 245 | + result.put(FieldNames.WIND_SOURCE_NAME.name(), windSource.name()); |
|
| 246 | + if (windSource.getId() != null) { |
|
| 247 | + result.put(FieldNames.WIND_SOURCE_ID.name(), windSource.getId()); |
|
| 248 | + } |
|
| 249 | + result.put(FieldNames.WIND.name(), storeWind(wind)); |
|
| 250 | + return result; |
|
| 251 | + } |
|
| 252 | + |
|
| 253 | + private void storeRaceIdentifiers(RaceColumn raceColumn, DBObject dbObject) { |
|
| 254 | + BasicDBObject raceIdentifiersPerFleet = new BasicDBObject(); |
|
| 255 | + for (Fleet fleet : raceColumn.getFleets()) { |
|
| 256 | + RaceIdentifier raceIdentifier = raceColumn.getRaceIdentifier(fleet); |
|
| 257 | + if (raceIdentifier != null) { |
|
| 258 | + DBObject raceIdentifierForFleet = new BasicDBObject(); |
|
| 259 | + storeRaceIdentifier(raceIdentifierForFleet, raceIdentifier); |
|
| 260 | + raceIdentifiersPerFleet.put(MongoUtils.escapeDollarAndDot(fleet.getName()), raceIdentifierForFleet); |
|
| 261 | + } |
|
| 262 | + } |
|
| 263 | + dbObject.put(FieldNames.RACE_IDENTIFIERS.name(), raceIdentifiersPerFleet); |
|
| 264 | + } |
|
| 265 | + |
|
| 266 | + private void storeRaceIdentifier(DBObject dbObject, RaceIdentifier raceIdentifier) { |
|
| 267 | + if (raceIdentifier != null) { |
|
| 268 | + dbObject.put(FieldNames.EVENT_NAME.name(), raceIdentifier.getRegattaName()); |
|
| 269 | + dbObject.put(FieldNames.RACE_NAME.name(), raceIdentifier.getRaceName()); |
|
| 270 | + } |
|
| 271 | + } |
|
| 272 | + |
|
| 273 | + @Override |
|
| 274 | + public void storeLeaderboard(Leaderboard leaderboard) { |
|
| 275 | + DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name()); |
|
| 276 | + try { |
|
| 277 | + leaderboardCollection.createIndex(new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), 1)); |
|
| 278 | + } catch (NullPointerException npe) { |
|
| 279 | + // sometimes, for reasons yet to be clarified, ensuring an index on the name field causes an NPE |
|
| 280 | + logger.log(Level.SEVERE, "storeLeaderboard", npe); |
|
| 281 | + } |
|
| 282 | + BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), leaderboard.getName()); |
|
| 283 | + BasicDBObject dbLeaderboard = new BasicDBObject(); |
|
| 284 | + dbLeaderboard.put(FieldNames.LEADERBOARD_NAME.name(), leaderboard.getName()); |
|
| 285 | + if (leaderboard.getDisplayName() != null) { |
|
| 286 | + dbLeaderboard.put(FieldNames.LEADERBOARD_DISPLAY_NAME.name(), leaderboard.getDisplayName()); |
|
| 287 | + } |
|
| 288 | + BasicDBList dbSuppressedCompetitorIds = new BasicDBList(); |
|
| 289 | + for (Competitor suppressedCompetitor : leaderboard.getSuppressedCompetitors()) { |
|
| 290 | + dbSuppressedCompetitorIds.add(suppressedCompetitor.getId()); |
|
| 291 | + } |
|
| 292 | + dbLeaderboard.put(FieldNames.LEADERBOARD_SUPPRESSED_COMPETITOR_IDS.name(), dbSuppressedCompetitorIds); |
|
| 293 | + if (leaderboard instanceof FlexibleLeaderboard) { |
|
| 294 | + storeFlexibleLeaderboard((FlexibleLeaderboard) leaderboard, dbLeaderboard); |
|
| 295 | + } else if (leaderboard instanceof RegattaLeaderboard) { |
|
| 296 | + storeRegattaLeaderboard((RegattaLeaderboard) leaderboard, dbLeaderboard); |
|
| 297 | + } else { |
|
| 298 | + // at least store the scoring scheme |
|
| 299 | + dbLeaderboard.put(FieldNames.SCORING_SCHEME_TYPE.name(), leaderboard.getScoringScheme().getType().name()); |
|
| 300 | + } |
|
| 301 | + if (leaderboard.getDefaultCourseArea() != null) { |
|
| 302 | + dbLeaderboard.put(FieldNames.COURSE_AREA_ID.name(), leaderboard.getDefaultCourseArea().getId().toString()); |
|
| 303 | + } else { |
|
| 304 | + dbLeaderboard.put(FieldNames.COURSE_AREA_ID.name(), null); |
|
| 305 | + } |
|
| 306 | + storeColumnFactors(leaderboard, dbLeaderboard); |
|
| 307 | + storeLeaderboardCorrectionsAndDiscards(leaderboard, dbLeaderboard); |
|
| 308 | + leaderboardCollection.update(query, dbLeaderboard, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE); |
|
| 309 | + } |
|
| 310 | + |
|
| 311 | + private void storeColumnFactors(Leaderboard leaderboard, BasicDBObject dbLeaderboard) { |
|
| 312 | + DBObject raceColumnFactors = new BasicDBObject(); |
|
| 313 | + for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { |
|
| 314 | + Double explicitFactor = raceColumn.getExplicitFactor(); |
|
| 315 | + if (explicitFactor != null) { |
|
| 316 | + raceColumnFactors.put(MongoUtils.escapeDollarAndDot(raceColumn.getName()), explicitFactor); |
|
| 317 | + } |
|
| 318 | + } |
|
| 319 | + dbLeaderboard.put(FieldNames.LEADERBOARD_COLUMN_FACTORS.name(), raceColumnFactors); |
|
| 320 | + } |
|
| 321 | + |
|
| 322 | + private void storeRegattaLeaderboard(RegattaLeaderboard leaderboard, DBObject dbLeaderboard) { |
|
| 323 | + dbLeaderboard.put(FieldNames.REGATTA_NAME.name(), leaderboard.getRegatta().getName()); |
|
| 324 | + } |
|
| 325 | + |
|
| 326 | + private void storeFlexibleLeaderboard(FlexibleLeaderboard leaderboard, BasicDBObject dbLeaderboard) { |
|
| 327 | + BasicDBList dbRaceColumns = new BasicDBList(); |
|
| 328 | + dbLeaderboard.put(FieldNames.SCORING_SCHEME_TYPE.name(), leaderboard.getScoringScheme().getType().name()); |
|
| 329 | + dbLeaderboard.put(FieldNames.LEADERBOARD_COLUMNS.name(), dbRaceColumns); |
|
| 330 | + for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { |
|
| 331 | + BasicDBObject dbRaceColumn = storeRaceColumn(raceColumn); |
|
| 332 | + dbRaceColumns.add(dbRaceColumn); |
|
| 333 | + } |
|
| 334 | + } |
|
| 335 | + |
|
| 336 | + private void storeLeaderboardCorrectionsAndDiscards(Leaderboard leaderboard, BasicDBObject dbLeaderboard) { |
|
| 337 | + if (leaderboard.hasCarriedPoints()) { |
|
| 338 | + BasicDBList dbCarriedPoints = new BasicDBList(); |
|
| 339 | + dbLeaderboard.put(FieldNames.LEADERBOARD_CARRIED_POINTS_BY_ID.name(), dbCarriedPoints); |
|
| 340 | + for (Entry<Competitor, Double> competitorWithCarriedPoints : leaderboard |
|
| 341 | + .getCompetitorsForWhichThereAreCarriedPoints().entrySet()) { |
|
| 342 | + double carriedPoints = competitorWithCarriedPoints.getValue(); |
|
| 343 | + Competitor competitor = competitorWithCarriedPoints.getKey(); |
|
| 344 | + DBObject dbCarriedPointsForCompetitor = new BasicDBObject(); |
|
| 345 | + dbCarriedPointsForCompetitor.put(FieldNames.COMPETITOR_ID.name(), competitor.getId()); |
|
| 346 | + dbCarriedPointsForCompetitor.put(FieldNames.LEADERBOARD_CARRIED_POINTS.name(), carriedPoints); |
|
| 347 | + dbCarriedPoints.add(dbCarriedPointsForCompetitor); |
|
| 348 | + } |
|
| 349 | + } |
|
| 350 | + BasicDBObject dbScoreCorrections = new BasicDBObject(); |
|
| 351 | + storeScoreCorrections(leaderboard, dbScoreCorrections); |
|
| 352 | + dbLeaderboard.put(FieldNames.LEADERBOARD_SCORE_CORRECTIONS.name(), dbScoreCorrections); |
|
| 353 | + final ResultDiscardingRule resultDiscardingRule = leaderboard.getResultDiscardingRule(); |
|
| 354 | + storeResultDiscardingRule(dbLeaderboard, resultDiscardingRule, FieldNames.LEADERBOARD_DISCARDING_THRESHOLDS); |
|
| 355 | + BasicDBList competitorDisplayNames = new BasicDBList(); |
|
| 356 | + for (Competitor competitor : leaderboard.getCompetitors()) { |
|
| 357 | + String displayNameForCompetitor = leaderboard.getDisplayName(competitor); |
|
| 358 | + if (displayNameForCompetitor != null) { |
|
| 359 | + DBObject dbDisplayName = new BasicDBObject(); |
|
| 360 | + dbDisplayName.put(FieldNames.COMPETITOR_ID.name(), competitor.getId()); |
|
| 361 | + dbDisplayName.put(FieldNames.COMPETITOR_DISPLAY_NAME.name(), displayNameForCompetitor); |
|
| 362 | + competitorDisplayNames.add(dbDisplayName); |
|
| 363 | + } |
|
| 364 | + } |
|
| 365 | + dbLeaderboard.put(FieldNames.LEADERBOARD_COMPETITOR_DISPLAY_NAMES.name(), competitorDisplayNames); |
|
| 366 | + } |
|
| 367 | + |
|
| 368 | + /** |
|
| 369 | + * Stores the result discarding rule to <code>dbObject</code>'s field identified by <code>field</code> if the result discarding |
|
| 370 | + * rule is not <code>null</code> and is of type {@link ThresholdBasedResultDiscardingRule}. Otherwise, it is assumed that the |
|
| 371 | + * result discarding rule is otherwise implicitly obtained, e.g., from a definition of a regatta with its series, stored elsewhere. |
|
| 372 | + */ |
|
| 373 | + private void storeResultDiscardingRule(DBObject dbObject, |
|
| 374 | + final ResultDiscardingRule resultDiscardingRule, FieldNames field) { |
|
| 375 | + if (resultDiscardingRule != null && resultDiscardingRule instanceof ThresholdBasedResultDiscardingRule) { |
|
| 376 | + BasicDBList dbResultDiscardingThresholds = new BasicDBList(); |
|
| 377 | + for (int threshold : ((ThresholdBasedResultDiscardingRule) resultDiscardingRule).getDiscardIndexResultsStartingWithHowManyRaces()) { |
|
| 378 | + dbResultDiscardingThresholds.add(threshold); |
|
| 379 | + } |
|
| 380 | + dbObject.put(field.name(), dbResultDiscardingThresholds); |
|
| 381 | + } |
|
| 382 | + } |
|
| 383 | + |
|
| 384 | + private BasicDBObject storeRaceColumn(RaceColumn raceColumn) { |
|
| 385 | + BasicDBObject dbRaceColumn = new BasicDBObject(); |
|
| 386 | + dbRaceColumn.put(FieldNames.LEADERBOARD_COLUMN_NAME.name(), raceColumn.getName()); |
|
| 387 | + dbRaceColumn.put(FieldNames.LEADERBOARD_IS_MEDAL_RACE_COLUMN.name(), raceColumn.isMedalRace()); |
|
| 388 | + storeRaceIdentifiers(raceColumn, dbRaceColumn); |
|
| 389 | + return dbRaceColumn; |
|
| 390 | + } |
|
| 391 | + |
|
| 392 | + private void storeScoreCorrections(Leaderboard leaderboard, BasicDBObject dbScoreCorrections) { |
|
| 393 | + TimePoint now = MillisecondsTimePoint.now(); |
|
| 394 | + SettableScoreCorrection scoreCorrection = leaderboard.getScoreCorrection(); |
|
| 395 | + for (RaceColumn raceColumn : scoreCorrection.getRaceColumnsThatHaveCorrections()) { |
|
| 396 | + BasicDBList dbCorrectionForRace = new BasicDBList(); |
|
| 397 | + for (Competitor competitor : scoreCorrection.getCompetitorsThatHaveCorrectionsIn(raceColumn)) { |
|
| 398 | + // TODO bug 655: make score corrections time dependent |
|
| 399 | + if (scoreCorrection.isScoreCorrected(competitor, raceColumn, now)) { |
|
| 400 | + BasicDBObject dbCorrectionForCompetitor = new BasicDBObject(); |
|
| 401 | + dbCorrectionForCompetitor.put(FieldNames.COMPETITOR_ID.name(), competitor.getId()); |
|
| 402 | + MaxPointsReason maxPointsReason = scoreCorrection.getMaxPointsReason(competitor, raceColumn, now); |
|
| 403 | + if (maxPointsReason != MaxPointsReason.NONE) { |
|
| 404 | + dbCorrectionForCompetitor.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON.name(), |
|
| 405 | + maxPointsReason.name()); |
|
| 406 | + } |
|
| 407 | + Double explicitScoreCorrection = scoreCorrection |
|
| 408 | + .getExplicitScoreCorrection(competitor, raceColumn); |
|
| 409 | + if (explicitScoreCorrection != null) { |
|
| 410 | + dbCorrectionForCompetitor.put(FieldNames.LEADERBOARD_CORRECTED_SCORE.name(), |
|
| 411 | + explicitScoreCorrection); |
|
| 412 | + } |
|
| 413 | + dbCorrectionForRace.add(dbCorrectionForCompetitor); |
|
| 414 | + } |
|
| 415 | + } |
|
| 416 | + if (!dbCorrectionForRace.isEmpty()) { |
|
| 417 | + // using the column name as the key for the score corrections requires re-writing the score corrections |
|
| 418 | + // of a meta-leaderboard if the name of one of its leaderboards changes |
|
| 419 | + dbScoreCorrections.put(MongoUtils.escapeDollarAndDot(raceColumn.getName()), dbCorrectionForRace); |
|
| 420 | + } |
|
| 421 | + } |
|
| 422 | + final TimePoint timePointOfLastCorrectionsValidity = scoreCorrection.getTimePointOfLastCorrectionsValidity(); |
|
| 423 | + if (timePointOfLastCorrectionsValidity != null) { |
|
| 424 | + dbScoreCorrections.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_TIMESTAMP.name(), timePointOfLastCorrectionsValidity.asMillis()); |
|
| 425 | + } |
|
| 426 | + if (scoreCorrection.getComment() != null) { |
|
| 427 | + dbScoreCorrections.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_COMMENT.name(), scoreCorrection.getComment()); |
|
| 428 | + } |
|
| 429 | + } |
|
| 430 | + |
|
| 431 | + @Override |
|
| 432 | + public void removeLeaderboard(String leaderboardName) { |
|
| 433 | + DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name()); |
|
| 434 | + BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), leaderboardName); |
|
| 435 | + leaderboardCollection.remove(query); |
|
| 436 | + } |
|
| 437 | + |
|
| 438 | + @Override |
|
| 439 | + public void renameLeaderboard(String oldName, String newName) { |
|
| 440 | + DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name()); |
|
| 441 | + BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), oldName); |
|
| 442 | + BasicDBObject renameUpdate = new BasicDBObject("$set", new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), newName)); |
|
| 443 | + leaderboardCollection.update(query, renameUpdate, /* upsert */ true, /* multi */ false, WriteConcern.SAFE); |
|
| 444 | + } |
|
| 445 | + |
|
| 446 | + @Override |
|
| 447 | + public void storeLeaderboardGroup(LeaderboardGroup leaderboardGroup) { |
|
| 448 | + DBCollection leaderboardGroupCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name()); |
|
| 449 | + DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name()); |
|
| 450 | + |
|
| 451 | + try { |
|
| 452 | + leaderboardGroupCollection.createIndex(new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), 1)); |
|
| 453 | + } catch (NullPointerException npe) { |
|
| 454 | + // sometimes, for reasons yet to be clarified, ensuring an index on the name field causes an NPE |
|
| 455 | + logger.log(Level.SEVERE, "storeLeaderboardGroup", npe); |
|
| 456 | + } |
|
| 457 | + BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), leaderboardGroup.getName()); |
|
| 458 | + BasicDBObject dbLeaderboardGroup = new BasicDBObject(); |
|
| 459 | + dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_UUID.name(), leaderboardGroup.getId()); |
|
| 460 | + dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_NAME.name(), leaderboardGroup.getName()); |
|
| 461 | + dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_DESCRIPTION.name(), leaderboardGroup.getDescription()); |
|
| 462 | + dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_DISPLAY_NAME.name(), leaderboardGroup.getDisplayName()); |
|
| 463 | + dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_DISPLAY_IN_REVERSE_ORDER.name(), leaderboardGroup.isDisplayGroupsInReverseOrder()); |
|
| 464 | + final Leaderboard overallLeaderboard = leaderboardGroup.getOverallLeaderboard(); |
|
| 465 | + if (overallLeaderboard != null) { |
|
| 466 | + BasicDBObject overallLeaderboardQuery = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), overallLeaderboard.getName()); |
|
| 467 | + DBObject dbOverallLeaderboard = leaderboardCollection.findOne(overallLeaderboardQuery); |
|
| 468 | + if (dbOverallLeaderboard == null) { |
|
| 469 | + storeLeaderboard(overallLeaderboard); |
|
| 470 | + dbOverallLeaderboard = leaderboardCollection.findOne(overallLeaderboardQuery); |
|
| 471 | + } |
|
| 472 | + ObjectId dbOverallLeaderboardId = (ObjectId) dbOverallLeaderboard.get("_id"); |
|
| 473 | + dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_OVERALL_LEADERBOARD.name(), dbOverallLeaderboardId); |
|
| 474 | + } |
|
| 475 | + BasicDBList dbLeaderboardIds = new BasicDBList(); |
|
| 476 | + for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) { |
|
| 477 | + BasicDBObject leaderboardQuery = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), leaderboard.getName()); |
|
| 478 | + DBObject dbLeaderboard = leaderboardCollection.findOne(leaderboardQuery); |
|
| 479 | + if (dbLeaderboard == null) { |
|
| 480 | + storeLeaderboard(leaderboard); |
|
| 481 | + dbLeaderboard = leaderboardCollection.findOne(leaderboardQuery); |
|
| 482 | + } |
|
| 483 | + ObjectId dbLeaderboardId = (ObjectId) dbLeaderboard.get("_id"); |
|
| 484 | + dbLeaderboardIds.add(dbLeaderboardId); |
|
| 485 | + } |
|
| 486 | + dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_LEADERBOARDS.name(), dbLeaderboardIds); |
|
| 487 | + leaderboardGroupCollection.update(query, dbLeaderboardGroup, true, false, WriteConcern.SAFE); |
|
| 488 | + } |
|
| 489 | + |
|
| 490 | + @Override |
|
| 491 | + public void removeLeaderboardGroup(String groupName) { |
|
| 492 | + DBCollection leaderboardGroupCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name()); |
|
| 493 | + BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), groupName); |
|
| 494 | + leaderboardGroupCollection.remove(query); |
|
| 495 | + } |
|
| 496 | + |
|
| 497 | + @Override |
|
| 498 | + public void renameLeaderboardGroup(String oldName, String newName) { |
|
| 499 | + DBCollection leaderboardGroupCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name()); |
|
| 500 | + BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), oldName); |
|
| 501 | + BasicDBObject update = new BasicDBObject("$set", new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), newName)); |
|
| 502 | + leaderboardGroupCollection.update(query, update, /* upsert */ true, /* multi */ false, WriteConcern.SAFE); |
|
| 503 | + } |
|
| 504 | + |
|
| 505 | + @Override |
|
| 506 | + public void storeServerConfiguration(SailingServerConfiguration serverConfiguration) { |
|
| 507 | + DBCollection serverCollection = database.getCollection(CollectionNames.SERVER_CONFIGURATION.name()); |
|
| 508 | + DBObject newServerConfig = new BasicDBObject(); |
|
| 509 | + newServerConfig.put(FieldNames.SERVER_IS_STANDALONE.name(), serverConfiguration.isStandaloneServer()); |
|
| 510 | + DBObject currentServerConfig = serverCollection.findOne(); |
|
| 511 | + if(currentServerConfig != null) { |
|
| 512 | + serverCollection.update(currentServerConfig, newServerConfig, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE); |
|
| 513 | + } else { |
|
| 514 | + serverCollection.save(newServerConfig); |
|
| 515 | + } |
|
| 516 | + } |
|
| 517 | + |
|
| 518 | + @Override |
|
| 519 | + public void storeSailingServer(RemoteSailingServerReference server) { |
|
| 520 | + DBCollection serverCollection = database.getCollection(CollectionNames.SAILING_SERVERS.name()); |
|
| 521 | + serverCollection.createIndex(new BasicDBObject(FieldNames.SERVER_NAME.name(), 1)); |
|
| 522 | + DBObject query = new BasicDBObject(); |
|
| 523 | + query.put(FieldNames.SERVER_NAME.name(), server.getName()); |
|
| 524 | + DBObject serverDBObject = new BasicDBObject(); |
|
| 525 | + serverDBObject.put(FieldNames.SERVER_NAME.name(), server.getName()); |
|
| 526 | + serverDBObject.put(FieldNames.SERVER_URL.name(), server.getURL().toExternalForm()); |
|
| 527 | + serverCollection.update(query, serverDBObject, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE); |
|
| 528 | + } |
|
| 529 | + |
|
| 530 | + @Override |
|
| 531 | + public void removeSailingServer(String name) { |
|
| 532 | + DBCollection serverCollection = database.getCollection(CollectionNames.SAILING_SERVERS.name()); |
|
| 533 | + BasicDBObject query = new BasicDBObject(FieldNames.SERVER_NAME.name(), name); |
|
| 534 | + serverCollection.remove(query); |
|
| 535 | + } |
|
| 536 | + |
|
| 537 | + /** |
|
| 538 | + * StoreEvent() uses some deprecated methods of event to keep backward compatibility. |
|
| 539 | + */ |
|
| 540 | + @SuppressWarnings("deprecation") |
|
| 541 | + @Override |
|
| 542 | + public void storeEvent(Event event) { |
|
| 543 | + DBCollection eventCollection = database.getCollection(CollectionNames.EVENTS.name()); |
|
| 544 | + eventCollection.createIndex(new BasicDBObject(FieldNames.EVENT_ID.name(), 1)); |
|
| 545 | + DBObject query = new BasicDBObject(); |
|
| 546 | + query.put(FieldNames.EVENT_ID.name(), event.getId()); |
|
| 547 | + DBObject eventDBObject = new BasicDBObject(); |
|
| 548 | + eventDBObject.put(FieldNames.EVENT_NAME.name(), event.getName()); |
|
| 549 | + eventDBObject.put(FieldNames.EVENT_DESCRIPTION.name(), event.getDescription()); |
|
| 550 | + eventDBObject.put(FieldNames.EVENT_ID.name(), event.getId()); |
|
| 551 | + eventDBObject.put(FieldNames.EVENT_LOGO_IMAGE_URL.name(), event.getLogoImageURL() != null ? event.getLogoImageURL().toString() : null); |
|
| 552 | + eventDBObject.put(FieldNames.EVENT_OFFICIAL_WEBSITE_URL.name(), event.getOfficialWebsiteURL() != null ? event.getOfficialWebsiteURL().toString() : null); |
|
| 553 | + eventDBObject.put(FieldNames.EVENT_SAILORS_INFO_WEBSITE_URL.name(), event.getSailorsInfoWebsiteURL() != null ? event.getSailorsInfoWebsiteURL().toString() : null); |
|
| 554 | + storeTimePoint(event.getStartDate(), eventDBObject, FieldNames.EVENT_START_DATE); |
|
| 555 | + storeTimePoint(event.getEndDate(), eventDBObject, FieldNames.EVENT_END_DATE); |
|
| 556 | + eventDBObject.put(FieldNames.EVENT_IS_PUBLIC.name(), event.isPublic()); |
|
| 557 | + DBObject venueDBObject = getVenueAsDBObject(event.getVenue()); |
|
| 558 | + eventDBObject.put(FieldNames.VENUE.name(), venueDBObject); |
|
| 559 | + BasicDBList imageURLs = new BasicDBList(); |
|
| 560 | + for (URL imageURL : event.getImageURLs()) { |
|
| 561 | + imageURLs.add(imageURL.toString()); |
|
| 562 | + } |
|
| 563 | + eventDBObject.put(FieldNames.EVENT_IMAGE_URLS.name(), imageURLs); |
|
| 564 | + BasicDBList videoURLs = new BasicDBList(); |
|
| 565 | + for (URL videoURL : event.getVideoURLs()) { |
|
| 566 | + videoURLs.add(videoURL.toString()); |
|
| 567 | + } |
|
| 568 | + eventDBObject.put(FieldNames.EVENT_VIDEO_URLS.name(), videoURLs); |
|
| 569 | + BasicDBList sponsorImageURLs = new BasicDBList(); |
|
| 570 | + for (URL sponsorImageURL : event.getSponsorImageURLs()) { |
|
| 571 | + sponsorImageURLs.add(sponsorImageURL.toString()); |
|
| 572 | + } |
|
| 573 | + eventDBObject.put(FieldNames.EVENT_SPONSOR_IMAGE_URLS.name(), sponsorImageURLs); |
|
| 574 | + BasicDBList images = new BasicDBList(); |
|
| 575 | + for (ImageDescriptor image : event.getImages()) { |
|
| 576 | + DBObject imageObject = createImageObject(image); |
|
| 577 | + images.add(imageObject); |
|
| 578 | + } |
|
| 579 | + eventDBObject.put(FieldNames.EVENT_IMAGES.name(), images); |
|
| 580 | + BasicDBList videos = new BasicDBList(); |
|
| 581 | + for (VideoDescriptor video: event.getVideos()) { |
|
| 582 | + DBObject videoObject = createVideoObject(video); |
|
| 583 | + videos.add(videoObject); |
|
| 584 | + } |
|
| 585 | + eventDBObject.put(FieldNames.EVENT_VIDEOS.name(), videos); |
|
| 586 | + eventCollection.update(query, eventDBObject, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE); |
|
| 587 | + // now store the links to the leaderboard groups |
|
| 588 | + DBCollection linksCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUP_LINKS_FOR_EVENTS.name()); |
|
| 589 | + linksCollection.createIndex(new BasicDBObject(FieldNames.EVENT_ID.name(), 1)); |
|
| 590 | + BasicDBList lgUUIDs = new BasicDBList(); |
|
| 591 | + for (LeaderboardGroup lg : event.getLeaderboardGroups()) { |
|
| 592 | + lgUUIDs.add(lg.getId()); |
|
| 593 | + } |
|
| 594 | + DBObject dbLinks = new BasicDBObject(); |
|
| 595 | + dbLinks.put(FieldNames.EVENT_ID.name(), event.getId()); |
|
| 596 | + dbLinks.put(FieldNames.LEADERBOARD_GROUP_UUID.name(), lgUUIDs); |
|
| 597 | + linksCollection.update(query, dbLinks, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE); |
|
| 598 | + } |
|
| 599 | + |
|
| 600 | + @Override |
|
| 601 | + public void renameEvent(Serializable id, String newName) { |
|
| 602 | + DBCollection eventCollection = database.getCollection(CollectionNames.EVENTS.name()); |
|
| 603 | + BasicDBObject query = new BasicDBObject(FieldNames.EVENT_ID.name(), id); |
|
| 604 | + BasicDBObject renameUpdate = new BasicDBObject("$set", new BasicDBObject(FieldNames.EVENT_NAME.name(), newName)); |
|
| 605 | + eventCollection.update(query, renameUpdate, /* upsert */ true, /* multi */ false, WriteConcern.SAFE); |
|
| 606 | + } |
|
| 607 | + |
|
| 608 | + @Override |
|
| 609 | + public void removeEvent(Serializable id) { |
|
| 610 | + DBCollection eventsCollection = database.getCollection(CollectionNames.EVENTS.name()); |
|
| 611 | + BasicDBObject query = new BasicDBObject(FieldNames.EVENT_ID.name(), id); |
|
| 612 | + eventsCollection.remove(query); |
|
| 613 | + } |
|
| 614 | + |
|
| 615 | + private DBObject getVenueAsDBObject(Venue venue) { |
|
| 616 | + DBObject result = new BasicDBObject(); |
|
| 617 | + result.put(FieldNames.VENUE_NAME.name(), venue.getName()); |
|
| 618 | + BasicDBList courseAreaList = new BasicDBList(); |
|
| 619 | + result.put(FieldNames.COURSE_AREAS.name(), courseAreaList); |
|
| 620 | + for (CourseArea courseArea : venue.getCourseAreas()) { |
|
| 621 | + DBObject dbCourseArea = new BasicDBObject(); |
|
| 622 | + courseAreaList.add(dbCourseArea); |
|
| 623 | + dbCourseArea.put(FieldNames.COURSE_AREA_NAME.name(), courseArea.getName()); |
|
| 624 | + dbCourseArea.put(FieldNames.COURSE_AREA_ID.name(), courseArea.getId()); |
|
| 625 | + } |
|
| 626 | + return result; |
|
| 627 | + } |
|
| 628 | + |
|
| 629 | + @Override |
|
| 630 | + public void storeRegatta(Regatta regatta) { |
|
| 631 | + DBCollection regattasCollection = database.getCollection(CollectionNames.REGATTAS.name()); |
|
| 632 | + regattasCollection.createIndex(new BasicDBObject(FieldNames.REGATTA_NAME.name(), 1)); |
|
| 633 | + regattasCollection.createIndex(new BasicDBObject(FieldNames.REGATTA_ID.name(), 1)); |
|
| 634 | + DBObject dbRegatta = new BasicDBObject(); |
|
| 635 | + DBObject query = new BasicDBObject(FieldNames.REGATTA_NAME.name(), regatta.getName()); |
|
| 636 | + dbRegatta.put(FieldNames.REGATTA_NAME.name(), regatta.getName()); |
|
| 637 | + dbRegatta.put(FieldNames.REGATTA_ID.name(), regatta.getId()); |
|
| 638 | + storeTimePoint(regatta.getStartDate(), dbRegatta, FieldNames.REGATTA_START_DATE); |
|
| 639 | + storeTimePoint(regatta.getEndDate(), dbRegatta, FieldNames.REGATTA_END_DATE); |
|
| 640 | + dbRegatta.put(FieldNames.SCORING_SCHEME_TYPE.name(), regatta.getScoringScheme().getType().name()); |
|
| 641 | + if (regatta.getBoatClass() != null) { |
|
| 642 | + dbRegatta.put(FieldNames.BOAT_CLASS_NAME.name(), regatta.getBoatClass().getName()); |
|
| 643 | + dbRegatta.put(FieldNames.BOAT_CLASS_TYPICALLY_STARTS_UPWIND.name(), regatta.getBoatClass().typicallyStartsUpwind()); |
|
| 644 | + } |
|
| 645 | + dbRegatta.put(FieldNames.REGATTA_SERIES.name(), storeSeries(regatta.getSeries())); |
|
| 646 | + |
|
| 647 | + if (regatta.getDefaultCourseArea() != null) { |
|
| 648 | + dbRegatta.put(FieldNames.COURSE_AREA_ID.name(), regatta.getDefaultCourseArea().getId().toString()); |
|
| 649 | + } else { |
|
| 650 | + dbRegatta.put(FieldNames.COURSE_AREA_ID.name(), null); |
|
| 651 | + } |
|
| 652 | + if (regatta.getRegattaConfiguration() != null) { |
|
| 653 | + JsonSerializer<RegattaConfiguration> serializer = RegattaConfigurationJsonSerializer.create(); |
|
| 654 | + JSONObject json = serializer.serialize(regatta.getRegattaConfiguration()); |
|
| 655 | + DBObject configurationObject = (DBObject) JSON.parse(json.toString()); |
|
| 656 | + dbRegatta.put(FieldNames.REGATTA_REGATTA_CONFIGURATION.name(), configurationObject); |
|
| 657 | + } |
|
| 658 | + dbRegatta.put(FieldNames.REGATTA_USE_START_TIME_INFERENCE.name(), regatta.useStartTimeInference()); |
|
| 659 | + dbRegatta.put(FieldNames.REGATTA_RANKING_METRIC.name(), storeRankingMetric(regatta)); |
|
| 660 | + regattasCollection.update(query, dbRegatta, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE); |
|
| 661 | + } |
|
| 662 | + |
|
| 663 | + private DBObject storeRankingMetric(Regatta regatta) { |
|
| 664 | + DBObject rankingMetricJson = new BasicDBObject(); |
|
| 665 | + final String rankingMetricTypeName = regatta.getRankingMetricType().name(); |
|
| 666 | + rankingMetricJson.put(FieldNames.REGATTA_RANKING_METRIC_TYPE.name(), rankingMetricTypeName); |
|
| 667 | + return rankingMetricJson; |
|
| 668 | + } |
|
| 669 | + |
|
| 670 | + @Override |
|
| 671 | + public void removeRegatta(Regatta regatta) { |
|
| 672 | + DBCollection regattasCollection = database.getCollection(CollectionNames.REGATTAS.name()); |
|
| 673 | + DBObject query = new BasicDBObject(FieldNames.REGATTA_NAME.name(), regatta.getName()); |
|
| 674 | + regattasCollection.remove(query); |
|
| 675 | + } |
|
| 676 | + |
|
| 677 | + private BasicDBList storeSeries(Iterable<? extends Series> series) { |
|
| 678 | + BasicDBList dbSeries = new BasicDBList(); |
|
| 679 | + for (Series s : series) { |
|
| 680 | + dbSeries.add(storeSeries(s)); |
|
| 681 | + } |
|
| 682 | + return dbSeries; |
|
| 683 | + } |
|
| 684 | + |
|
| 685 | + private DBObject storeSeries(Series s) { |
|
| 686 | + DBObject dbSeries = new BasicDBObject(); |
|
| 687 | + dbSeries.put(FieldNames.SERIES_NAME.name(), s.getName()); |
|
| 688 | + dbSeries.put(FieldNames.SERIES_IS_MEDAL.name(), s.isMedal()); |
|
| 689 | + dbSeries.put(FieldNames.SERIES_HAS_SPLIT_FLEET_CONTIGUOUS_SCORING.name(), s.hasSplitFleetContiguousScoring()); |
|
| 690 | + dbSeries.put(FieldNames.SERIES_STARTS_WITH_ZERO_SCORE.name(), s.isStartsWithZeroScore()); |
|
| 691 | + dbSeries.put(FieldNames.SERIES_STARTS_WITH_NON_DISCARDABLE_CARRY_FORWARD.name(), s.isFirstColumnIsNonDiscardableCarryForward()); |
|
| 692 | + BasicDBList dbFleets = new BasicDBList(); |
|
| 693 | + for (Fleet fleet : s.getFleets()) { |
|
| 694 | + dbFleets.add(storeFleet(fleet)); |
|
| 695 | + } |
|
| 696 | + dbSeries.put(FieldNames.SERIES_FLEETS.name(), dbFleets); |
|
| 697 | + BasicDBList dbRaceColumns = new BasicDBList(); |
|
| 698 | + for (RaceColumn raceColumn : s.getRaceColumns()) { |
|
| 699 | + dbRaceColumns.add(storeRaceColumn(raceColumn)); |
|
| 700 | + } |
|
| 701 | + dbSeries.put(FieldNames.SERIES_RACE_COLUMNS.name(), dbRaceColumns); |
|
| 702 | + if (s.getResultDiscardingRule() != null) { |
|
| 703 | + storeResultDiscardingRule(dbSeries, s.getResultDiscardingRule(), FieldNames.SERIES_DISCARDING_THRESHOLDS); |
|
| 704 | + } |
|
| 705 | + return dbSeries; |
|
| 706 | + } |
|
| 707 | + |
|
| 708 | + private DBObject storeFleet(Fleet fleet) { |
|
| 709 | + DBObject dbFleet = new BasicDBObject(FieldNames.FLEET_NAME.name(), fleet.getName()); |
|
| 710 | + if (fleet instanceof FleetImpl) { |
|
| 711 | + dbFleet.put(FieldNames.FLEET_ORDERING.name(), ((FleetImpl) fleet).getOrdering()); |
|
| 712 | + if(fleet.getColor() != null) { |
|
| 713 | + com.sap.sse.common.Util.Triple<Integer, Integer, Integer> colorAsRGB = fleet.getColor().getAsRGB(); |
|
| 714 | + // we save the color as a integer value representing the RGB values |
|
| 715 | + int colorAsInt = (256 * 256 * colorAsRGB.getC()) + colorAsRGB.getB() * 256 + colorAsRGB.getA(); |
|
| 716 | + dbFleet.put(FieldNames.FLEET_COLOR.name(), colorAsInt); |
|
| 717 | + } else { |
|
| 718 | + dbFleet.put(FieldNames.FLEET_COLOR.name(), null); |
|
| 719 | + } |
|
| 720 | + } |
|
| 721 | + return dbFleet; |
|
| 722 | + } |
|
| 723 | + |
|
| 724 | + @Override |
|
| 725 | + public void storeRegattaForRaceID(String raceIDAsString, Regatta regatta) { |
|
| 726 | + DBCollection regattaForRaceIDCollection = database.getCollection(CollectionNames.REGATTA_FOR_RACE_ID.name()); |
|
| 727 | + DBObject query = new BasicDBObject(FieldNames.RACE_ID_AS_STRING.name(), raceIDAsString); |
|
| 728 | + DBObject entry = new BasicDBObject(FieldNames.RACE_ID_AS_STRING.name(), raceIDAsString); |
|
| 729 | + entry.put(FieldNames.REGATTA_NAME.name(), regatta.getName()); |
|
| 730 | + regattaForRaceIDCollection.update(query, entry, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE); |
|
| 731 | + } |
|
| 732 | + |
|
| 733 | + @Override |
|
| 734 | + public void removeRegattaForRaceID(String raceIDAsString, Regatta regatta) { |
|
| 735 | + DBCollection regattaForRaceIDCollection = database.getCollection(CollectionNames.REGATTA_FOR_RACE_ID.name()); |
|
| 736 | + DBObject query = new BasicDBObject(FieldNames.RACE_ID_AS_STRING.name(), raceIDAsString); |
|
| 737 | + regattaForRaceIDCollection.remove(query); |
|
| 738 | + } |
|
| 739 | + |
|
| 740 | + public DBCollection getRaceLogCollection() { |
|
| 741 | + DBCollection result = database.getCollection(CollectionNames.RACE_LOGS.name()); |
|
| 742 | + result.createIndex(new BasicDBObject(FieldNames.RACE_LOG_IDENTIFIER.name(), null)); |
|
| 743 | + return result; |
|
| 744 | + } |
|
| 745 | + |
|
| 746 | + private void storeRaceLogEventAuthor(DBObject dbObject, AbstractLogEventAuthor author) { |
|
| 747 | + if (author != null) { |
|
| 748 | + dbObject.put(FieldNames.RACE_LOG_EVENT_AUTHOR_NAME.name(), author.getName()); |
|
| 749 | + dbObject.put(FieldNames.RACE_LOG_EVENT_AUTHOR_PRIORITY.name(), author.getPriority()); |
|
| 750 | + } |
|
| 751 | + } |
|
| 752 | + |
|
| 753 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogFlagEvent flagEvent) { |
|
| 754 | + BasicDBObject result = new BasicDBObject(); |
|
| 755 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 756 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogFlagEvent(flagEvent)); |
|
| 757 | + return result; |
|
| 758 | + } |
|
| 759 | + |
|
| 760 | + private void storeRaceLogIdentifier(RaceLogIdentifier raceLogIdentifier, DBObject result) { |
|
| 761 | + result.put(FieldNames.RACE_LOG_IDENTIFIER.name(), TripleSerializer.serialize(raceLogIdentifier.getIdentifier())); |
|
| 762 | + } |
|
| 763 | + |
|
| 764 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogStartTimeEvent startTimeEvent) { |
|
| 765 | + BasicDBObject result = new BasicDBObject(); |
|
| 766 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 767 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogStartTimeEvent(startTimeEvent)); |
|
| 768 | + return result; |
|
| 769 | + } |
|
| 770 | + |
|
| 771 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogPassChangeEvent passChangeEvent) { |
|
| 772 | + BasicDBObject result = new BasicDBObject(); |
|
| 773 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 774 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogPassChangeEvent(passChangeEvent)); |
|
| 775 | + return result; |
|
| 776 | + } |
|
| 777 | + |
|
| 778 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogRaceStatusEvent raceStatusEvent) { |
|
| 779 | + BasicDBObject result = new BasicDBObject(); |
|
| 780 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 781 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogRaceStatusEvent(raceStatusEvent)); |
|
| 782 | + return result; |
|
| 783 | + } |
|
| 784 | + |
|
| 785 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogCourseAreaChangedEvent courseAreaChangedEvent) { |
|
| 786 | + BasicDBObject result = new BasicDBObject(); |
|
| 787 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 788 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogCourseAreaChangedEvent(courseAreaChangedEvent)); |
|
| 789 | + return result; |
|
| 790 | + } |
|
| 791 | + |
|
| 792 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogCourseDesignChangedEvent courseDesignChangedEvent) { |
|
| 793 | + BasicDBObject result = new BasicDBObject(); |
|
| 794 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 795 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogCourseDesignChangedEvent(courseDesignChangedEvent)); |
|
| 796 | + return result; |
|
| 797 | + } |
|
| 798 | + |
|
| 799 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogFinishPositioningListChangedEvent finishPositioningListChangedEvent) { |
|
| 800 | + BasicDBObject result = new BasicDBObject(); |
|
| 801 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 802 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogFinishPositioningListChangedEvent(finishPositioningListChangedEvent)); |
|
| 803 | + return result; |
|
| 804 | + } |
|
| 805 | + |
|
| 806 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogFinishPositioningConfirmedEvent finishPositioningConfirmedEvent) { |
|
| 807 | + BasicDBObject result = new BasicDBObject(); |
|
| 808 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 809 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogFinishPositioningConfirmedEvent(finishPositioningConfirmedEvent)); |
|
| 810 | + return result; |
|
| 811 | + } |
|
| 812 | + |
|
| 813 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogPathfinderEvent pathfinderEvent) { |
|
| 814 | + BasicDBObject result = new BasicDBObject(); |
|
| 815 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 816 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogPathfinderEvent(pathfinderEvent)); |
|
| 817 | + return result; |
|
| 818 | + } |
|
| 819 | + |
|
| 820 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogGateLineOpeningTimeEvent gateLineOpeningTimeEvent) { |
|
| 821 | + BasicDBObject result = new BasicDBObject(); |
|
| 822 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 823 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogGateLineOpeningTimeEvent(gateLineOpeningTimeEvent)); |
|
| 824 | + return result; |
|
| 825 | + } |
|
| 826 | + |
|
| 827 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogStartProcedureChangedEvent event) { |
|
| 828 | + DBObject result = new BasicDBObject(); |
|
| 829 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 830 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogStartProcedureChangedEvent(event)); |
|
| 831 | + return result; |
|
| 832 | + } |
|
| 833 | + |
|
| 834 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogProtestStartTimeEvent event) { |
|
| 835 | + DBObject result = new BasicDBObject(); |
|
| 836 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 837 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogProtestStartTimeEvent(event)); |
|
| 838 | + return result; |
|
| 839 | + } |
|
| 840 | + |
|
| 841 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogWindFixEvent event) { |
|
| 842 | + DBObject result = new BasicDBObject(); |
|
| 843 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 844 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogWindFix(event)); |
|
| 845 | + return result; |
|
| 846 | + } |
|
| 847 | + |
|
| 848 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDeviceCompetitorMappingEvent event) { |
|
| 849 | + BasicDBObject result = new BasicDBObject(); |
|
| 850 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 851 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDeviceCompetitorMappingEvent(event)); |
|
| 852 | + return result; |
|
| 853 | + } |
|
| 854 | + |
|
| 855 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDeviceMarkMappingEvent event) { |
|
| 856 | + BasicDBObject result = new BasicDBObject(); |
|
| 857 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 858 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDeviceMarkMappingEvent(event)); |
|
| 859 | + return result; |
|
| 860 | + } |
|
| 861 | + |
|
| 862 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDenoteForTrackingEvent event) { |
|
| 863 | + BasicDBObject result = new BasicDBObject(); |
|
| 864 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 865 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDenoteForTrackingEvent(event)); |
|
| 866 | + return result; |
|
| 867 | + } |
|
| 868 | + |
|
| 869 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogStartTrackingEvent event) { |
|
| 870 | + BasicDBObject result = new BasicDBObject(); |
|
| 871 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 872 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogStartTrackingEvent(event)); |
|
| 873 | + return result; |
|
| 874 | + } |
|
| 875 | + |
|
| 876 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogRevokeEvent event) { |
|
| 877 | + BasicDBObject result = new BasicDBObject(); |
|
| 878 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 879 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogRevokeEvent(event)); |
|
| 880 | + return result; |
|
| 881 | + } |
|
| 882 | + |
|
| 883 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogRegisterCompetitorEvent event) { |
|
| 884 | + BasicDBObject result = new BasicDBObject(); |
|
| 885 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 886 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogRegisterCompetitorEvent(event)); |
|
| 887 | + return result; |
|
| 888 | + } |
|
| 889 | + |
|
| 890 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDefineMarkEvent event) { |
|
| 891 | + BasicDBObject result = new BasicDBObject(); |
|
| 892 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 893 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDefineMarkEvent(event)); |
|
| 894 | + return result; |
|
| 895 | + } |
|
| 896 | + |
|
| 897 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogCloseOpenEndedDeviceMappingEvent event) { |
|
| 898 | + BasicDBObject result = new BasicDBObject(); |
|
| 899 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 900 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogCloseOpenEndedDeviceMappingEvent(event)); |
|
| 901 | + return result; |
|
| 902 | + } |
|
| 903 | + |
|
| 904 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogEndOfTrackingEvent event) { |
|
| 905 | + BasicDBObject result = new BasicDBObject(); |
|
| 906 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 907 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogEndOfTrackingEvent(event)); |
|
| 908 | + return result; |
|
| 909 | + } |
|
| 910 | + |
|
| 911 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogStartOfTrackingEvent event) { |
|
| 912 | + BasicDBObject result = new BasicDBObject(); |
|
| 913 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 914 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogStartOfTrackingEvent(event)); |
|
| 915 | + return result; |
|
| 916 | + } |
|
| 917 | + |
|
| 918 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogAdditionalScoringInformationEvent event) { |
|
| 919 | + BasicDBObject result = new BasicDBObject(); |
|
| 920 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 921 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeAdditionalScoringInformation(event)); |
|
| 922 | + return result; |
|
| 923 | + } |
|
| 924 | + |
|
| 925 | + private Object storeAdditionalScoringInformation(RaceLogAdditionalScoringInformationEvent event) { |
|
| 926 | + DBObject result = new BasicDBObject(); |
|
| 927 | + storeRaceLogEventProperties(event, result); |
|
| 928 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogAdditionalScoringInformationEvent.class.getSimpleName()); |
|
| 929 | + result.put(FieldNames.RACE_LOG_ADDITIONAL_SCORING_INFORMATION_TYPE.name(), event.getType().name()); |
|
| 930 | + return result; |
|
| 931 | + } |
|
| 932 | + |
|
| 933 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogFixedMarkPassingEvent event) { |
|
| 934 | + BasicDBObject result = new BasicDBObject(); |
|
| 935 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 936 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogFixedMarkPassingEvent(event)); |
|
| 937 | + return result; |
|
| 938 | + } |
|
| 939 | + |
|
| 940 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogSuppressedMarkPassingsEvent event) { |
|
| 941 | + BasicDBObject result = new BasicDBObject(); |
|
| 942 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 943 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogSuppressedMarkPassingsEvent(event)); |
|
| 944 | + return result; |
|
| 945 | + } |
|
| 946 | + |
|
| 947 | + private Object storeRaceLogWindFix(RaceLogWindFixEvent event) { |
|
| 948 | + DBObject result = new BasicDBObject(); |
|
| 949 | + storeRaceLogEventProperties(event, result); |
|
| 950 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogWindFixEvent.class.getSimpleName()); |
|
| 951 | + result.put(FieldNames.WIND.name(), storeWind(event.getWindFix())); |
|
| 952 | + result.put(FieldNames.IS_MAGNETIC.name(), event.isMagnetic()); |
|
| 953 | + return result; |
|
| 954 | + } |
|
| 955 | + |
|
| 956 | + private Object storeRaceLogProtestStartTimeEvent(RaceLogProtestStartTimeEvent event) { |
|
| 957 | + DBObject result = new BasicDBObject(); |
|
| 958 | + storeRaceLogEventProperties(event, result); |
|
| 959 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogProtestStartTimeEvent.class.getSimpleName()); |
|
| 960 | + storeTimePoint(event.getProtestStartTime(), result, FieldNames.RACE_LOG_PROTEST_START_TIME); |
|
| 961 | + return result; |
|
| 962 | + } |
|
| 963 | + |
|
| 964 | + private Object storeRaceLogEndOfTrackingEvent(RaceLogEndOfTrackingEvent event) { |
|
| 965 | + DBObject result = new BasicDBObject(); |
|
| 966 | + storeRaceLogEventProperties(event, result); |
|
| 967 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogEndOfTrackingEvent.class.getSimpleName()); |
|
| 968 | + return result; |
|
| 969 | + } |
|
| 970 | + |
|
| 971 | + private Object storeRaceLogStartOfTrackingEvent(RaceLogStartOfTrackingEvent event) { |
|
| 972 | + DBObject result = new BasicDBObject(); |
|
| 973 | + storeRaceLogEventProperties(event, result); |
|
| 974 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogStartOfTrackingEvent.class.getSimpleName()); |
|
| 975 | + return result; |
|
| 976 | + } |
|
| 977 | + |
|
| 978 | + private Object storeRaceLogStartProcedureChangedEvent(RaceLogStartProcedureChangedEvent event) { |
|
| 979 | + DBObject result = new BasicDBObject(); |
|
| 980 | + storeRaceLogEventProperties(event, result); |
|
| 981 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogStartProcedureChangedEvent.class.getSimpleName()); |
|
| 982 | + result.put(FieldNames.RACE_LOG_START_PROCEDURE_TYPE.name(), event.getStartProcedureType().name()); |
|
| 983 | + return result; |
|
| 984 | + } |
|
| 985 | + |
|
| 986 | + private Object storeRaceLogPathfinderEvent(RaceLogPathfinderEvent pathfinderEvent) { |
|
| 987 | + DBObject result = new BasicDBObject(); |
|
| 988 | + storeRaceLogEventProperties(pathfinderEvent, result); |
|
| 989 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogPathfinderEvent.class.getSimpleName()); |
|
| 990 | + result.put(FieldNames.RACE_LOG_PATHFINDER_ID.name(), pathfinderEvent.getPathfinderId()); |
|
| 991 | + return result; |
|
| 992 | + } |
|
| 993 | + |
|
| 994 | + private void storeDeviceMappingEvent(DeviceMappingEvent<?, ?> event, DBObject result, FieldNames fromField, FieldNames toField) { |
|
| 995 | + try { |
|
| 996 | + result.put(FieldNames.DEVICE_ID.name(), storeDeviceId(deviceIdentifierServiceFinder, event.getDevice())); |
|
| 997 | + } catch (TransformationException | NoCorrespondingServiceRegisteredException e) { |
|
| 998 | + logger.log(Level.WARNING, "Could not store device identifier for mappng event", e); |
|
| 999 | + } |
|
| 1000 | + if (event.getFrom() != null) { |
|
| 1001 | + storeTimePoint(event.getFrom(), result, fromField); |
|
| 1002 | + } |
|
| 1003 | + if (event.getTo() != null) { |
|
| 1004 | + storeTimePoint(event.getTo(), result, toField); |
|
| 1005 | + } |
|
| 1006 | + } |
|
| 1007 | + |
|
| 1008 | + private Object storeRaceLogDeviceCompetitorMappingEvent(RaceLogDeviceCompetitorMappingEvent event) { |
|
| 1009 | + DBObject result = new BasicDBObject(); |
|
| 1010 | + storeRaceLogEventProperties(event, result); |
|
| 1011 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDeviceCompetitorMappingEvent.class.getSimpleName()); |
|
| 1012 | + storeDeviceMappingEvent(event, result, FieldNames.RACE_LOG_FROM, FieldNames.RACE_LOG_TO); |
|
| 1013 | + result.put(FieldNames.COMPETITOR_ID.name(), event.getMappedTo().getId()); |
|
| 1014 | + return result; |
|
| 1015 | + } |
|
| 1016 | + |
|
| 1017 | + private Object storeRaceLogDeviceMarkMappingEvent(RaceLogDeviceMarkMappingEvent event) { |
|
| 1018 | + DBObject result = new BasicDBObject(); |
|
| 1019 | + storeRaceLogEventProperties(event, result); |
|
| 1020 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDeviceMarkMappingEvent.class.getSimpleName()); |
|
| 1021 | + storeDeviceMappingEvent(event, result, FieldNames.RACE_LOG_FROM, FieldNames.RACE_LOG_TO); |
|
| 1022 | + result.put(FieldNames.MARK.name(), storeMark(event.getMappedTo())); |
|
| 1023 | + return result; |
|
| 1024 | + } |
|
| 1025 | + |
|
| 1026 | + private Object storeRaceLogDenoteForTrackingEvent(RaceLogDenoteForTrackingEvent event) { |
|
| 1027 | + DBObject result = new BasicDBObject(); |
|
| 1028 | + storeRaceLogEventProperties(event, result); |
|
| 1029 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDenoteForTrackingEvent.class.getSimpleName()); |
|
| 1030 | + result.put(FieldNames.RACE_NAME.name(), event.getRaceName()); |
|
| 1031 | + result.put(FieldNames.BOAT_CLASS_NAME.name(), event.getBoatClass().getName()); |
|
| 1032 | + result.put(FieldNames.RACE_ID.name(), event.getRaceId()); |
|
| 1033 | + return result; |
|
| 1034 | + } |
|
| 1035 | + |
|
| 1036 | + private Object storeRaceLogStartTrackingEvent(RaceLogStartTrackingEvent event) { |
|
| 1037 | + DBObject result = new BasicDBObject(); |
|
| 1038 | + storeRaceLogEventProperties(event, result); |
|
| 1039 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogStartTrackingEvent.class.getSimpleName()); |
|
| 1040 | + return result; |
|
| 1041 | + } |
|
| 1042 | + |
|
| 1043 | + private Object storeRaceLogRevokeEvent(RaceLogRevokeEvent event) { |
|
| 1044 | + DBObject result = new BasicDBObject(); |
|
| 1045 | + storeRaceLogEventProperties(event, result); |
|
| 1046 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogRevokeEvent.class.getSimpleName()); |
|
| 1047 | + result.put(FieldNames.RACE_LOG_REVOKED_EVENT_ID.name(), event.getRevokedEventId()); |
|
| 1048 | + result.put(FieldNames.RACE_LOG_REVOKED_EVENT_TYPE.name(), event.getRevokedEventType()); |
|
| 1049 | + result.put(FieldNames.RACE_LOG_REVOKED_EVENT_SHORT_INFO.name(), event.getRevokedEventShortInfo()); |
|
| 1050 | + result.put(FieldNames.RACE_LOG_REVOKED_REASON.name(), event.getReason()); |
|
| 1051 | + return result; |
|
| 1052 | + } |
|
| 1053 | + |
|
| 1054 | + private Object storeRaceLogRegisterCompetitorEvent(RaceLogRegisterCompetitorEvent event) { |
|
| 1055 | + DBObject result = new BasicDBObject(); |
|
| 1056 | + storeRaceLogEventProperties(event, result); |
|
| 1057 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogRegisterCompetitorEvent.class.getSimpleName()); |
|
| 1058 | + result.put(FieldNames.RACE_LOG_COMPETITOR_ID.name(), event.getCompetitor().getId()); |
|
| 1059 | + return result; |
|
| 1060 | + } |
|
| 1061 | + |
|
| 1062 | + private Object storeRaceLogDefineMarkEvent(RaceLogDefineMarkEvent event) { |
|
| 1063 | + DBObject result = new BasicDBObject(); |
|
| 1064 | + storeRaceLogEventProperties(event, result); |
|
| 1065 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDefineMarkEvent.class.getSimpleName()); |
|
| 1066 | + result.put(FieldNames.RACE_LOG_MARK.name(), storeMark(event.getMark())); |
|
| 1067 | + return result; |
|
| 1068 | + } |
|
| 1069 | + |
|
| 1070 | + private Object storeRaceLogCloseOpenEndedDeviceMappingEvent(RaceLogCloseOpenEndedDeviceMappingEvent event) { |
|
| 1071 | + DBObject result = new BasicDBObject(); |
|
| 1072 | + storeRaceLogEventProperties(event, result); |
|
| 1073 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName()); |
|
| 1074 | + result.put(FieldNames.RACE_LOG_DEVICE_MAPPING_EVENT_ID.name(), event.getDeviceMappingEventId()); |
|
| 1075 | + storeTimePoint(event.getClosingTimePoint(), result, FieldNames.RACE_LOG_CLOSING_TIMEPOINT); |
|
| 1076 | + return result; |
|
| 1077 | + } |
|
| 1078 | + |
|
| 1079 | + private Object storeRaceLogFixedMarkPassingEvent(RaceLogFixedMarkPassingEvent event) { |
|
| 1080 | + DBObject result = new BasicDBObject(); |
|
| 1081 | + storeRaceLogEventProperties(event, result); |
|
| 1082 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogFixedMarkPassingEvent.class.getSimpleName()); |
|
| 1083 | + result.put(FieldNames.INDEX_OF_PASSED_WAYPOINT.name(), event.getZeroBasedIndexOfPassedWaypoint()); |
|
| 1084 | + result.put(FieldNames.TIMEPOINT_OF_FIXED_MARKPASSING.name(), event.getTimePointOfFixedPassing().asMillis()); |
|
| 1085 | + return result; |
|
| 1086 | + } |
|
| 1087 | + |
|
| 1088 | + private Object storeRaceLogSuppressedMarkPassingsEvent(RaceLogSuppressedMarkPassingsEvent event) { |
|
| 1089 | + DBObject result = new BasicDBObject(); |
|
| 1090 | + storeRaceLogEventProperties(event, result); |
|
| 1091 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogSuppressedMarkPassingsEvent.class.getSimpleName()); |
|
| 1092 | + result.put(FieldNames.INDEX_OF_FIRST_SUPPRESSED_WAYPOINT.name(), event.getZeroBasedIndexOfFirstSuppressedWaypoint()); |
|
| 1093 | + return result; |
|
| 1094 | + } |
|
| 1095 | + |
|
| 1096 | + public DBObject storeRaceLogFlagEvent(RaceLogFlagEvent flagEvent) { |
|
| 1097 | + DBObject result = new BasicDBObject(); |
|
| 1098 | + storeRaceLogEventProperties(flagEvent, result); |
|
| 1099 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogFlagEvent.class.getSimpleName()); |
|
| 1100 | + result.put(FieldNames.RACE_LOG_EVENT_FLAG_UPPER.name(), flagEvent.getUpperFlag().name()); |
|
| 1101 | + result.put(FieldNames.RACE_LOG_EVENT_FLAG_LOWER.name(), flagEvent.getLowerFlag().name()); |
|
| 1102 | + result.put(FieldNames.RACE_LOG_EVENT_FLAG_DISPLAYED.name(), String.valueOf(flagEvent.isDisplayed())); |
|
| 1103 | + return result; |
|
| 1104 | + } |
|
| 1105 | + |
|
| 1106 | + private DBObject storeRaceLogStartTimeEvent(RaceLogStartTimeEvent startTimeEvent) { |
|
| 1107 | + DBObject result = new BasicDBObject(); |
|
| 1108 | + storeRaceLogEventProperties(startTimeEvent, result); |
|
| 1109 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogStartTimeEvent.class.getSimpleName()); |
|
| 1110 | + storeTimePoint(startTimeEvent.getStartTime(), result, FieldNames.RACE_LOG_EVENT_START_TIME); |
|
| 1111 | + result.put(FieldNames.RACE_LOG_EVENT_NEXT_STATUS.name(), startTimeEvent.getNextStatus().name()); |
|
| 1112 | + return result; |
|
| 1113 | + } |
|
| 1114 | + |
|
| 1115 | + private void storeRaceLogEventProperties(RaceLogEvent event, DBObject result) { |
|
| 1116 | + // for compatibility reasons we reuse the field name of Timed |
|
| 1117 | + storeTimePoint(event.getLogicalTimePoint(), result, FieldNames.TIME_AS_MILLIS); |
|
| 1118 | + storeTimePoint(event.getCreatedAt(), result, FieldNames.RACE_LOG_EVENT_CREATED_AT); |
|
| 1119 | + result.put(FieldNames.RACE_LOG_EVENT_ID.name(), event.getId()); |
|
| 1120 | + result.put(FieldNames.RACE_LOG_EVENT_PASS_ID.name(), event.getPassId()); |
|
| 1121 | + result.put(FieldNames.RACE_LOG_EVENT_INVOLVED_BOATS.name(), storeInvolvedBoatsForRaceLogEvent(event.getInvolvedBoats())); |
|
| 1122 | + storeRaceLogEventAuthor(result, event.getAuthor()); |
|
| 1123 | + } |
|
| 1124 | + |
|
| 1125 | + |
|
| 1126 | + private BasicDBList storeInvolvedBoatsForRaceLogEvent(List<Competitor> competitors) { |
|
| 1127 | + BasicDBList dbInvolvedCompetitorIds = new BasicDBList(); |
|
| 1128 | + for (Competitor competitor : competitors) { |
|
| 1129 | + dbInvolvedCompetitorIds.add(competitor.getId()); |
|
| 1130 | + } |
|
| 1131 | + return dbInvolvedCompetitorIds; |
|
| 1132 | + } |
|
| 1133 | + |
|
| 1134 | + private DBObject storeRaceLogPassChangeEvent(RaceLogPassChangeEvent passChangeEvent) { |
|
| 1135 | + DBObject result = new BasicDBObject(); |
|
| 1136 | + storeRaceLogEventProperties(passChangeEvent, result); |
|
| 1137 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogPassChangeEvent.class.getSimpleName()); |
|
| 1138 | + return result; |
|
| 1139 | + } |
|
| 1140 | + |
|
| 1141 | + private DBObject storeRaceLogDependentStartTimeEvent(RaceLogDependentStartTimeEvent dependentStartTimeEvent) { |
|
| 1142 | + DBObject result = new BasicDBObject(); |
|
| 1143 | + storeRaceLogEventProperties(dependentStartTimeEvent, result); |
|
| 1144 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDependentStartTimeEvent.class.getSimpleName()); |
|
| 1145 | + result.put(FieldNames.RACE_LOG_DEPDENDENT_ON_REGATTALIKE.name(), dependentStartTimeEvent.getDependentOnRaceIdentifier().getRegattaLikeParentName()); |
|
| 1146 | + result.put(FieldNames.RACE_LOG_DEPDENDENT_ON_RACECOLUMN.name(), dependentStartTimeEvent.getDependentOnRaceIdentifier().getRaceColumnName()); |
|
| 1147 | + result.put(FieldNames.RACE_LOG_DEPDENDENT_ON_FLEET.name(), dependentStartTimeEvent.getDependentOnRaceIdentifier().getFleetName()); |
|
| 1148 | + storeDuration(dependentStartTimeEvent.getStartTimeDifference(), result, FieldNames.RACE_LOG_START_TIME_DIFFERENCE_IN_MS); |
|
| 1149 | + result.put(FieldNames.RACE_LOG_EVENT_NEXT_STATUS.name(), dependentStartTimeEvent.getNextStatus().name()); |
|
| 1150 | + return result; |
|
| 1151 | + } |
|
| 1152 | + |
|
| 1153 | + private void storeDuration(Duration duration, DBObject result, FieldNames fieldName) { |
|
| 1154 | + if (duration != null) { |
|
| 1155 | + result.put(fieldName.name(), duration.asMillis()); |
|
| 1156 | + } |
|
| 1157 | + } |
|
| 1158 | + |
|
| 1159 | + private DBObject storeRaceLogRaceStatusEvent(RaceLogRaceStatusEvent raceStatusEvent) { |
|
| 1160 | + DBObject result = new BasicDBObject(); |
|
| 1161 | + storeRaceLogEventProperties(raceStatusEvent, result); |
|
| 1162 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogRaceStatusEvent.class.getSimpleName()); |
|
| 1163 | + result.put(FieldNames.RACE_LOG_EVENT_NEXT_STATUS.name(), raceStatusEvent.getNextStatus().name()); |
|
| 1164 | + return result; |
|
| 1165 | + } |
|
| 1166 | + |
|
| 1167 | + private DBObject storeRaceLogCourseAreaChangedEvent(RaceLogCourseAreaChangedEvent courseAreaChangedEvent) { |
|
| 1168 | + DBObject result = new BasicDBObject(); |
|
| 1169 | + storeRaceLogEventProperties(courseAreaChangedEvent, result); |
|
| 1170 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogCourseAreaChangedEvent.class.getSimpleName()); |
|
| 1171 | + result.put(FieldNames.COURSE_AREA_ID.name(), courseAreaChangedEvent.getCourseAreaId()); |
|
| 1172 | + return result; |
|
| 1173 | + } |
|
| 1174 | + |
|
| 1175 | + private DBObject storeRaceLogCourseDesignChangedEvent(RaceLogCourseDesignChangedEvent courseDesignChangedEvent) { |
|
| 1176 | + DBObject result = new BasicDBObject(); |
|
| 1177 | + storeRaceLogEventProperties(courseDesignChangedEvent, result); |
|
| 1178 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogCourseDesignChangedEvent.class.getSimpleName()); |
|
| 1179 | + result.put(FieldNames.RACE_LOG_COURSE_DESIGN_NAME.name(), courseDesignChangedEvent.getCourseDesign().getName()); |
|
| 1180 | + result.put(FieldNames.RACE_LOG_COURSE_DESIGN.name(), storeCourseBase(courseDesignChangedEvent.getCourseDesign())); |
|
| 1181 | + return result; |
|
| 1182 | + } |
|
| 1183 | + |
|
| 1184 | + private Object storeRaceLogFinishPositioningListChangedEvent(RaceLogFinishPositioningListChangedEvent finishPositioningListChangedEvent) { |
|
| 1185 | + DBObject result = new BasicDBObject(); |
|
| 1186 | + storeRaceLogEventProperties(finishPositioningListChangedEvent, result); |
|
| 1187 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogFinishPositioningListChangedEvent.class.getSimpleName()); |
|
| 1188 | + result.put(FieldNames.RACE_LOG_POSITIONED_COMPETITORS.name(), storePositionedCompetitors(finishPositioningListChangedEvent.getPositionedCompetitorsIDsNamesMaxPointsReasons())); |
|
| 1189 | + |
|
| 1190 | + return result; |
|
| 1191 | + } |
|
| 1192 | + |
|
| 1193 | + private Object storeRaceLogFinishPositioningConfirmedEvent(RaceLogFinishPositioningConfirmedEvent finishPositioningConfirmedEvent) { |
|
| 1194 | + DBObject result = new BasicDBObject(); |
|
| 1195 | + storeRaceLogEventProperties(finishPositioningConfirmedEvent, result); |
|
| 1196 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogFinishPositioningConfirmedEvent.class.getSimpleName()); |
|
| 1197 | + result.put(FieldNames.RACE_LOG_POSITIONED_COMPETITORS.name(), storePositionedCompetitors(finishPositioningConfirmedEvent.getPositionedCompetitorsIDsNamesMaxPointsReasons())); |
|
| 1198 | + |
|
| 1199 | + return result; |
|
| 1200 | + } |
|
| 1201 | + |
|
| 1202 | + private Object storeRaceLogGateLineOpeningTimeEvent(RaceLogGateLineOpeningTimeEvent gateLineOpeningTimeEvent){ |
|
| 1203 | + DBObject result = new BasicDBObject(); |
|
| 1204 | + storeRaceLogEventProperties(gateLineOpeningTimeEvent, result); |
|
| 1205 | + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogGateLineOpeningTimeEvent.class.getSimpleName()); |
|
| 1206 | + result.put(FieldNames.RACE_LOG_GATE_LINE_OPENING_TIME.name(), gateLineOpeningTimeEvent.getGateLineOpeningTimes().getGateLaunchStopTime()); |
|
| 1207 | + result.put(FieldNames.RACE_LOG_GOLF_DOWN_TIME.name(), gateLineOpeningTimeEvent.getGateLineOpeningTimes().getGolfDownTime()); |
|
| 1208 | + return result; |
|
| 1209 | + } |
|
| 1210 | + |
|
| 1211 | + private BasicDBList storePositionedCompetitors(List<com.sap.sse.common.Util.Triple<Serializable, String, MaxPointsReason>> positionedCompetitors) { |
|
| 1212 | + BasicDBList dbList = new BasicDBList(); |
|
| 1213 | + if (positionedCompetitors != null) { |
|
| 1214 | + for (com.sap.sse.common.Util.Triple<Serializable, String, MaxPointsReason> competitorPair : positionedCompetitors) { |
|
| 1215 | + dbList.add(storePositionedCompetitor(competitorPair)); |
|
| 1216 | + } |
|
| 1217 | + } |
|
| 1218 | + return dbList; |
|
| 1219 | + } |
|
| 1220 | + |
|
| 1221 | + private DBObject storePositionedCompetitor(com.sap.sse.common.Util.Triple<Serializable, String, MaxPointsReason> competitorTriple) { |
|
| 1222 | + DBObject result = new BasicDBObject(); |
|
| 1223 | + result.put(FieldNames.COMPETITOR_ID.name(), competitorTriple.getA()); |
|
| 1224 | + result.put(FieldNames.COMPETITOR_DISPLAY_NAME.name(), competitorTriple.getB()); |
|
| 1225 | + result.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON.name(), competitorTriple.getC().name()); |
|
| 1226 | + |
|
| 1227 | + return result; |
|
| 1228 | + } |
|
| 1229 | + |
|
| 1230 | + private BasicDBList storeCourseBase(CourseBase courseData) { |
|
| 1231 | + BasicDBList dbList = new BasicDBList(); |
|
| 1232 | + |
|
| 1233 | + for (Waypoint waypoint : courseData.getWaypoints()) { |
|
| 1234 | + dbList.add(storeWaypoint(waypoint)); |
|
| 1235 | + } |
|
| 1236 | + return dbList; |
|
| 1237 | + } |
|
| 1238 | + |
|
| 1239 | + private DBObject storeWaypoint(Waypoint waypoint) { |
|
| 1240 | + DBObject result = new BasicDBObject(); |
|
| 1241 | + result.put(FieldNames.WAYPOINT_PASSINGINSTRUCTIONS.name(), getPassingInstructions(waypoint.getPassingInstructions())); |
|
| 1242 | + result.put(FieldNames.CONTROLPOINT.name(), storeControlPoint(waypoint.getControlPoint())); |
|
| 1243 | + return result; |
|
| 1244 | + } |
|
| 1245 | + |
|
| 1246 | + private DBObject storeControlPoint(ControlPoint controlPoint) { |
|
| 1247 | + DBObject result = new BasicDBObject(); |
|
| 1248 | + if (controlPoint instanceof Mark) { |
|
| 1249 | + result.put(FieldNames.CONTROLPOINT_CLASS.name(), Mark.class.getSimpleName()); |
|
| 1250 | + result.put(FieldNames.CONTROLPOINT_VALUE.name(), storeMark((Mark) controlPoint)); |
|
| 1251 | + } else if (controlPoint instanceof ControlPointWithTwoMarks) { |
|
| 1252 | + result.put(FieldNames.CONTROLPOINT_CLASS.name(), ControlPointWithTwoMarks.class.getSimpleName()); |
|
| 1253 | + result.put(FieldNames.CONTROLPOINT_VALUE.name(), storeControlPointWithTwoMarks((ControlPointWithTwoMarks) controlPoint)); |
|
| 1254 | + } |
|
| 1255 | + return result; |
|
| 1256 | + } |
|
| 1257 | + |
|
| 1258 | + private DBObject storeControlPointWithTwoMarks(ControlPointWithTwoMarks cpwtm) { |
|
| 1259 | + DBObject result = new BasicDBObject(); |
|
| 1260 | + result.put(FieldNames.CONTROLPOINTWITHTWOMARKS_ID.name(), cpwtm.getId()); |
|
| 1261 | + result.put(FieldNames.CONTROLPOINTWITHTWOMARKS_NAME.name(), cpwtm.getName()); |
|
| 1262 | + result.put(FieldNames.CONTROLPOINTWITHTWOMARKS_LEFT.name(), storeMark(cpwtm.getLeft())); |
|
| 1263 | + result.put(FieldNames.CONTROLPOINTWITHTWOMARKS_RIGHT.name(), storeMark(cpwtm.getRight())); |
|
| 1264 | + return result; |
|
| 1265 | + } |
|
| 1266 | + |
|
| 1267 | + private DBObject storeMark(Mark mark) { |
|
| 1268 | + DBObject result = new BasicDBObject(); |
|
| 1269 | + result.put(FieldNames.MARK_ID.name(), mark.getId()); |
|
| 1270 | + result.put(FieldNames.MARK_COLOR.name(), mark.getColor()); |
|
| 1271 | + result.put(FieldNames.MARK_NAME.name(), mark.getName()); |
|
| 1272 | + result.put(FieldNames.MARK_PATTERN.name(), mark.getPattern()); |
|
| 1273 | + result.put(FieldNames.MARK_SHAPE.name(), mark.getShape()); |
|
| 1274 | + result.put(FieldNames.MARK_TYPE.name(), mark.getType() == null ? null : mark.getType().name()); |
|
| 1275 | + return result; |
|
| 1276 | + } |
|
| 1277 | + |
|
| 1278 | + private String getPassingInstructions(PassingInstruction passingInstructions) { |
|
| 1279 | + final String passing; |
|
| 1280 | + if (passingInstructions != null) { |
|
| 1281 | + passing = passingInstructions.name(); |
|
| 1282 | + } else { |
|
| 1283 | + passing = null; |
|
| 1284 | + } |
|
| 1285 | + return passing; |
|
| 1286 | + } |
|
| 1287 | + @Override |
|
| 1288 | + public void storeCompetitor(Competitor competitor) { |
|
| 1289 | + DBCollection collection = database.getCollection(CollectionNames.COMPETITORS.name()); |
|
| 1290 | + JSONObject json = competitorSerializer.serialize(competitor); |
|
| 1291 | + DBObject query = (DBObject) JSON.parse(CompetitorJsonSerializer.getCompetitorIdQuery(competitor).toString()); |
|
| 1292 | + DBObject entry = (DBObject) JSON.parse(json.toString()); |
|
| 1293 | + collection.update(query, entry, /* upsrt */true, /* multi */false, WriteConcern.SAFE); |
|
| 1294 | + } |
|
| 1295 | + |
|
| 1296 | + @Override |
|
| 1297 | + public void removeAllCompetitors() { |
|
| 1298 | + logger.info("Removing all persistent competitor info"); |
|
| 1299 | + DBCollection collection = database.getCollection(CollectionNames.COMPETITORS.name()); |
|
| 1300 | + collection.drop(); |
|
| 1301 | + } |
|
| 1302 | + |
|
| 1303 | + @Override |
|
| 1304 | + public void removeCompetitor(Competitor competitor) { |
|
| 1305 | + logger.info("Removing persistent competitor info for competitor "+competitor.getName()+" with ID "+competitor.getId()); |
|
| 1306 | + DBCollection collection = database.getCollection(CollectionNames.COMPETITORS.name()); |
|
| 1307 | + DBObject query = (DBObject) JSON.parse(CompetitorJsonSerializer.getCompetitorIdQuery(competitor).toString()); |
|
| 1308 | + collection.remove(query, WriteConcern.SAFE); |
|
| 1309 | + } |
|
| 1310 | + |
|
| 1311 | + @Override |
|
| 1312 | + public void storeDeviceConfiguration(DeviceConfigurationMatcher matcher, DeviceConfiguration configuration) { |
|
| 1313 | + DBCollection configurationsCollections = database.getCollection(CollectionNames.CONFIGURATIONS.name()); |
|
| 1314 | + |
|
| 1315 | + DBObject query = new BasicDBObject(); |
|
| 1316 | + query.put(FieldNames.CONFIGURATION_MATCHER_ID.name(), matcher.getMatcherIdentifier()); |
|
| 1317 | + |
|
| 1318 | + DBObject entryObject = new BasicDBObject(); |
|
| 1319 | + entryObject.put(FieldNames.CONFIGURATION_MATCHER_ID.name(), matcher.getMatcherIdentifier()); |
|
| 1320 | + entryObject.put(FieldNames.CONFIGURATION_MATCHER.name(), createDeviceConfigurationMatcherObject(matcher)); |
|
| 1321 | + entryObject.put(FieldNames.CONFIGURATION_CONFIG.name(), createDeviceConfigurationObject(configuration)); |
|
| 1322 | + |
|
| 1323 | + configurationsCollections.update(query, entryObject, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE); |
|
| 1324 | + } |
|
| 1325 | + |
|
| 1326 | + private DBObject createDeviceConfigurationMatcherObject(DeviceConfigurationMatcher matcher) { |
|
| 1327 | + DBObject matcherObject = new BasicDBObject(); |
|
| 1328 | + matcherObject.put(FieldNames.CONFIGURATION_MATCHER_TYPE.name(), matcher.getMatcherType().name()); |
|
| 1329 | + if (matcher instanceof DeviceConfigurationMatcherSingle) { |
|
| 1330 | + BasicDBList client = new BasicDBList(); |
|
| 1331 | + client.add(((DeviceConfigurationMatcherSingle)matcher).getClientIdentifier()); |
|
| 1332 | + matcherObject.put(FieldNames.CONFIGURATION_MATCHER_CLIENTS.name(), client); |
|
| 1333 | + } else if (matcher instanceof DeviceConfigurationMatcherMulti) { |
|
| 1334 | + BasicDBList clients = new BasicDBList(); |
|
| 1335 | + Util.addAll(((DeviceConfigurationMatcherMulti)matcher).getClientIdentifiers(), clients); |
|
| 1336 | + matcherObject.put(FieldNames.CONFIGURATION_MATCHER_CLIENTS.name(), clients); |
|
| 1337 | + } |
|
| 1338 | + return matcherObject; |
|
| 1339 | + } |
|
| 1340 | + |
|
| 1341 | + private DBObject createDeviceConfigurationObject(DeviceConfiguration configuration) { |
|
| 1342 | + JsonSerializer<DeviceConfiguration> serializer = DeviceConfigurationJsonSerializer.create(); |
|
| 1343 | + JSONObject json = serializer.serialize(configuration); |
|
| 1344 | + DBObject entry = (DBObject) JSON.parse(json.toString()); |
|
| 1345 | + return entry; |
|
| 1346 | + } |
|
| 1347 | + |
|
| 1348 | + @Override |
|
| 1349 | + public void removeDeviceConfiguration(DeviceConfigurationMatcher matcher) { |
|
| 1350 | + DBCollection configurationsCollections = database.getCollection(CollectionNames.CONFIGURATIONS.name()); |
|
| 1351 | + DBObject query = new BasicDBObject(); |
|
| 1352 | + query.put(FieldNames.CONFIGURATION_MATCHER_ID.name(), matcher.getMatcherIdentifier()); |
|
| 1353 | + configurationsCollections.remove(query); |
|
| 1354 | + } |
|
| 1355 | + |
|
| 1356 | + public static DBObject storeDeviceId( |
|
| 1357 | + TypeBasedServiceFinder<DeviceIdentifierMongoHandler> deviceIdentifierServiceFinder, DeviceIdentifier device) |
|
| 1358 | + throws TransformationException, NoCorrespondingServiceRegisteredException { |
|
| 1359 | + String type = device.getIdentifierType(); |
|
| 1360 | + DeviceIdentifierMongoHandler handler = deviceIdentifierServiceFinder.findService(type); |
|
| 1361 | + com.sap.sse.common.Util.Pair<String, ? extends Object> pair = handler.serialize(device); |
|
| 1362 | + type = pair.getA(); |
|
| 1363 | + Object deviceTypeSpecificId = pair.getB(); |
|
| 1364 | + return new BasicDBObjectBuilder() |
|
| 1365 | + .add(FieldNames.DEVICE_TYPE.name(), type) |
|
| 1366 | + .add(FieldNames.DEVICE_TYPE_SPECIFIC_ID.name(), deviceTypeSpecificId) |
|
| 1367 | + .add(FieldNames.DEVICE_STRING_REPRESENTATION.name(), device.getStringRepresentation()).get(); |
|
| 1368 | + } |
|
| 1369 | + |
|
| 1370 | + void storeRaceLogEventEvent(DBObject eventEntry) { |
|
| 1371 | + getRaceLogCollection().insert(eventEntry); |
|
| 1372 | + } |
|
| 1373 | + |
|
| 1374 | + @Override |
|
| 1375 | + public void removeRaceLog(RaceLogIdentifier identifier) { |
|
| 1376 | + DBObject query = new BasicDBObject(); |
|
| 1377 | + storeRaceLogIdentifier(identifier, query); |
|
| 1378 | + getRaceLogCollection().remove(query); |
|
| 1379 | + } |
|
| 1380 | + |
|
| 1381 | + @Override |
|
| 1382 | + public void removeRegattaLog(RegattaLikeIdentifier identifier) { |
|
| 1383 | + DBObject query = new BasicDBObject(); |
|
| 1384 | + addRegattaLikeIdentifier(identifier, query); |
|
| 1385 | + getRegattaLogCollection().remove(query); |
|
| 1386 | + } |
|
| 1387 | + |
|
| 1388 | + @Override |
|
| 1389 | + public void storeResultUrl(String resultProviderName, URL url) { |
|
| 1390 | + DBCollection resultUrlsCollection = database.getCollection(CollectionNames.RESULT_URLS.name()); |
|
| 1391 | + DBObject query = new BasicDBObject(FieldNames.RESULT_PROVIDERNAME.name(), resultProviderName); |
|
| 1392 | + DBObject entry = new BasicDBObject(FieldNames.RESULT_PROVIDERNAME.name(), resultProviderName); |
|
| 1393 | + entry.put(FieldNames.RESULT_URL.name(), url.toString()); |
|
| 1394 | + resultUrlsCollection.update(query, entry, /* upsrt */true, /* multi */false, WriteConcern.SAFE); |
|
| 1395 | + } |
|
| 1396 | + |
|
| 1397 | + @Override |
|
| 1398 | + public void removeResultUrl(String resultProviderName, URL url) { |
|
| 1399 | + DBCollection resultUrlsCollection = database.getCollection(CollectionNames.RESULT_URLS.name()); |
|
| 1400 | + DBObject query = new BasicDBObjectBuilder().add(FieldNames.RESULT_PROVIDERNAME.name(), resultProviderName) |
|
| 1401 | + .add(FieldNames.RESULT_URL.name(), url.toString()).get(); |
|
| 1402 | + resultUrlsCollection.remove(query); |
|
| 1403 | + } |
|
| 1404 | + |
|
| 1405 | + public DBCollection getRegattaLogCollection() { |
|
| 1406 | + DBCollection result = database.getCollection(CollectionNames.REGATTA_LOGS.name()); |
|
| 1407 | + DBObject index = new BasicDBObject(FieldNames.REGATTA_LOG_IDENTIFIER_TYPE.name(), null); |
|
| 1408 | + index.put(FieldNames.REGATTA_LOG_IDENTIFIER_NAME.name(), null); |
|
| 1409 | + result.createIndex(index); |
|
| 1410 | + return result; |
|
| 1411 | + } |
|
| 1412 | + |
|
| 1413 | + private DBObject createBasicRegattaLogEventDBObject(RegattaLogEvent event) { |
|
| 1414 | + DBObject result = new BasicDBObject(); |
|
| 1415 | + storeTimed(event, result); |
|
| 1416 | + storeTimePoint(event.getCreatedAt(), result, FieldNames.REGATTA_LOG_EVENT_CREATED_AT); |
|
| 1417 | + result.put(FieldNames.REGATTA_LOG_EVENT_ID.name(), event.getId()); |
|
| 1418 | + result.put(FieldNames.REGATTA_LOG_EVENT_AUTHOR_NAME.name(), event.getAuthor().getName()); |
|
| 1419 | + result.put(FieldNames.REGATTA_LOG_EVENT_AUTHOR_PRIORITY.name(), event.getAuthor().getPriority()); |
|
| 1420 | + return result; |
|
| 1421 | + } |
|
| 1422 | + |
|
| 1423 | + private void addRegattaLikeIdentifier(RegattaLikeIdentifier regattaLikeId, DBObject toObject) { |
|
| 1424 | + toObject.put(FieldNames.REGATTA_LOG_IDENTIFIER_TYPE.name(), regattaLikeId.getIdentifierType()); |
|
| 1425 | + toObject.put(FieldNames.REGATTA_LOG_IDENTIFIER_NAME.name(), regattaLikeId.getName()); |
|
| 1426 | + } |
|
| 1427 | + |
|
| 1428 | + private void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, DBObject innerObject) { |
|
| 1429 | + DBObject result = new BasicDBObject(FieldNames.REGATTA_LOG_EVENT.name(), innerObject); |
|
| 1430 | + addRegattaLikeIdentifier(regattaLikeId, result); |
|
| 1431 | + getRegattaLogCollection().insert(result); |
|
| 1432 | + } |
|
| 1433 | + |
|
| 1434 | + public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogDeviceCompetitorMappingEvent event) { |
|
| 1435 | + DBObject result = createBasicRegattaLogEventDBObject(event); |
|
| 1436 | + result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogDeviceCompetitorMappingEvent.class.getSimpleName()); |
|
| 1437 | + storeDeviceMappingEvent(event, result, FieldNames.REGATTA_LOG_FROM, FieldNames.REGATTA_LOG_TO); |
|
| 1438 | + result.put(FieldNames.COMPETITOR_ID.name(), event.getMappedTo().getId()); |
|
| 1439 | + storeRegattaLogEvent(regattaLikeId, result); |
|
| 1440 | + } |
|
| 1441 | + |
|
| 1442 | + public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogDeviceMarkMappingEvent event) { |
|
| 1443 | + DBObject result = createBasicRegattaLogEventDBObject(event); |
|
| 1444 | + result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogDeviceMarkMappingEvent.class.getSimpleName()); |
|
| 1445 | + storeDeviceMappingEvent(event, result, FieldNames.REGATTA_LOG_FROM, FieldNames.REGATTA_LOG_TO); |
|
| 1446 | + result.put(FieldNames.MARK.name(), storeMark(event.getMappedTo())); |
|
| 1447 | + storeRegattaLogEvent(regattaLikeId, result); |
|
| 1448 | + } |
|
| 1449 | + |
|
| 1450 | + public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogRevokeEvent event) { |
|
| 1451 | + DBObject result = createBasicRegattaLogEventDBObject(event); |
|
| 1452 | + result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogRevokeEvent.class.getSimpleName()); |
|
| 1453 | + result.put(FieldNames.REGATTA_LOG_REVOKED_EVENT_ID.name(), event.getRevokedEventId()); |
|
| 1454 | + result.put(FieldNames.REGATTA_LOG_REVOKED_EVENT_TYPE.name(), event.getRevokedEventType()); |
|
| 1455 | + result.put(FieldNames.REGATTA_LOG_REVOKED_EVENT_SHORT_INFO.name(), event.getRevokedEventShortInfo()); |
|
| 1456 | + result.put(FieldNames.REGATTA_LOG_REVOKED_REASON.name(), event.getReason()); |
|
| 1457 | + storeRegattaLogEvent(regattaLikeId, result); |
|
| 1458 | + } |
|
| 1459 | + |
|
| 1460 | + public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogRegisterCompetitorEvent event) { |
|
| 1461 | + DBObject result = createBasicRegattaLogEventDBObject(event); |
|
| 1462 | + result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogRegisterCompetitorEvent.class.getSimpleName()); |
|
| 1463 | + result.put(FieldNames.REGATTA_LOG_COMPETITOR_ID.name(), event.getCompetitor().getId()); |
|
| 1464 | + storeRegattaLogEvent(regattaLikeId, result); |
|
| 1465 | + } |
|
| 1466 | + |
|
| 1467 | + public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogCloseOpenEndedDeviceMappingEvent event) { |
|
| 1468 | + DBObject result = createBasicRegattaLogEventDBObject(event); |
|
| 1469 | + result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName()); |
|
| 1470 | + result.put(FieldNames.REGATTA_LOG_DEVICE_MAPPING_EVENT_ID.name(), event.getDeviceMappingEventId()); |
|
| 1471 | + storeTimePoint(event.getClosingTimePoint(), result, FieldNames.REGATTA_LOG_CLOSING_TIMEPOINT); |
|
| 1472 | + storeRegattaLogEvent(regattaLikeId, result); |
|
| 1473 | + } |
|
| 1474 | + |
|
| 1475 | + public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogSetCompetitorTimeOnTimeFactorEvent event) { |
|
| 1476 | + DBObject result = createBasicRegattaLogEventDBObject(event); |
|
| 1477 | + result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName()); |
|
| 1478 | + result.put(FieldNames.REGATTA_LOG_COMPETITOR_ID.name(), event.getCompetitor().getId()); |
|
| 1479 | + result.put(FieldNames.REGATTA_LOG_TIME_ON_TIME_FACTOR.name(), event.getTimeOnTimeFactor()); |
|
| 1480 | + } |
|
| 1481 | + |
|
| 1482 | + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDependentStartTimeEvent event) { |
|
| 1483 | + BasicDBObject result = new BasicDBObject(); |
|
| 1484 | + storeRaceLogIdentifier(raceLogIdentifier, result); |
|
| 1485 | + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDependentStartTimeEvent(event)); |
|
| 1486 | + return result; |
|
| 1487 | + } |
|
| 1488 | + |
|
| 1489 | + public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogSetCompetitorTimeOnDistanceAllowancePerNauticalMileEvent event) { |
|
| 1490 | + DBObject result = createBasicRegattaLogEventDBObject(event); |
|
| 1491 | + result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName()); |
|
| 1492 | + result.put(FieldNames.REGATTA_LOG_COMPETITOR_ID.name(), event.getCompetitor().getId()); |
|
| 1493 | + result.put(FieldNames.REGATTA_LOG_TIME_ON_DISTANCE_SECONDS_ALLOWANCE_PER_NAUTICAL_MILE.name(), event.getTimeOnDistanceAllowancePerNauticalMile().asSeconds()); |
|
| 1494 | + storeRegattaLogEvent(regattaLikeId, result); |
|
| 1495 | + } |
|
| 1496 | + |
|
| 1497 | + private DBObject createImageObject(ImageDescriptor image) { |
|
| 1498 | + DBObject result = new BasicDBObject(); |
|
| 1499 | + result.put(FieldNames.IMAGE_URL.name(), image.getURL().toString()); |
|
| 1500 | + result.put(FieldNames.IMAGE_LOCALE.name(), image.getLocale() != null ? image.getLocale().toLanguageTag() : null); |
|
| 1501 | + result.put(FieldNames.IMAGE_TITLE.name(), image.getTitle()); |
|
| 1502 | + result.put(FieldNames.IMAGE_SUBTITLE.name(), image.getSubtitle()); |
|
| 1503 | + result.put(FieldNames.IMAGE_COPYRIGHT.name(), image.getCopyright()); |
|
| 1504 | + result.put(FieldNames.IMAGE_WIDTH_IN_PX.name(), image.getWidthInPx()); |
|
| 1505 | + result.put(FieldNames.IMAGE_HEIGHT_IN_PX.name(), image.getHeightInPx()); |
|
| 1506 | + storeTimePoint(image.getCreatedAtDate(), result, FieldNames.IMAGE_CREATEDATDATE); |
|
| 1507 | + BasicDBList tags = new BasicDBList(); |
|
| 1508 | + for (String tag : image.getTags()) { |
|
| 1509 | + tags.add(tag); |
|
| 1510 | + } |
|
| 1511 | + result.put(FieldNames.IMAGE_TAGS.name(), tags); |
|
| 1512 | + return result; |
|
| 1513 | + } |
|
| 1514 | + |
|
| 1515 | + private DBObject createVideoObject(VideoDescriptor video) { |
|
| 1516 | + DBObject result = new BasicDBObject(); |
|
| 1517 | + result.put(FieldNames.VIDEO_URL.name(), video.getURL().toString()); |
|
| 1518 | + result.put(FieldNames.VIDEO_LOCALE.name(), video.getLocale() != null ? video.getLocale().toLanguageTag() : null); |
|
| 1519 | + result.put(FieldNames.VIDEO_THUMBNAIL_URL.name(), video.getThumbnailURL() != null ? video.getThumbnailURL().toString() : null); |
|
| 1520 | + result.put(FieldNames.VIDEO_TITLE.name(), video.getTitle()); |
|
| 1521 | + result.put(FieldNames.VIDEO_SUBTITLE.name(), video.getSubtitle()); |
|
| 1522 | + result.put(FieldNames.VIDEO_MIMETYPE.name(), video.getMimeType() != null ? video.getMimeType().name() : null); |
|
| 1523 | + result.put(FieldNames.VIDEO_COPYRIGHT.name(), video.getCopyright()); |
|
| 1524 | + result.put(FieldNames.VIDEO_LENGTH_IN_SECONDS.name(), video.getLengthInSeconds()); |
|
| 1525 | + storeTimePoint(video.getCreatedAtDate(), result, FieldNames.VIDEO_CREATEDATDATE); |
|
| 1526 | + BasicDBList tags = new BasicDBList(); |
|
| 1527 | + for (String tag : video.getTags()) { |
|
| 1528 | + tags.add(tag); |
|
| 1529 | + } |
|
| 1530 | + result.put(FieldNames.VIDEO_TAGS.name(), tags); |
|
| 1531 | + return result; |
|
| 1532 | + } |
|
| 1533 | +} |
java/com.sap.sailing.domain.shared.android/pom.xml
| ... | ... | @@ -1,39 +1,39 @@ |
| 1 | -<?xml version="1.0" encoding="UTF-8"?>
|
|
| 2 | -<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
|
| 3 | - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
| 4 | - <modelVersion>4.0.0</modelVersion>
|
|
| 5 | - <parent>
|
|
| 6 | - <artifactId>root</artifactId>
|
|
| 7 | - <groupId>com.sap.sailing</groupId>
|
|
| 8 | - <version>1.0.0-SNAPSHOT</version>
|
|
| 9 | - </parent>
|
|
| 10 | - <artifactId>com.sap.sailing.domain.shared.android</artifactId>
|
|
| 11 | - <packaging>eclipse-plugin</packaging>
|
|
| 12 | - <build>
|
|
| 13 | - <plugins>
|
|
| 14 | - <plugin>
|
|
| 15 | - <groupId>org.eclipse.tycho</groupId>
|
|
| 16 | - <artifactId>tycho-compiler-plugin</artifactId>
|
|
| 17 | - <version>${tycho-version}</version>
|
|
| 18 | - <configuration>
|
|
| 19 | - <source>1.7</source>
|
|
| 20 | - <target>1.7</target>
|
|
| 21 | - </configuration>
|
|
| 22 | - </plugin>
|
|
| 23 | - <plugin>
|
|
| 24 | - <groupId>org.eclipse.tycho</groupId>
|
|
| 25 | - <artifactId>tycho-source-plugin</artifactId>
|
|
| 26 | - <version>${tycho-version}</version>
|
|
| 27 | - <executions>
|
|
| 28 | - <execution>
|
|
| 29 | - <id>plugin-source</id>
|
|
| 30 | - <phase>generate-sources</phase>
|
|
| 31 | - <goals>
|
|
| 32 | - <goal>plugin-source</goal>
|
|
| 33 | - </goals>
|
|
| 34 | - </execution>
|
|
| 35 | - </executions>
|
|
| 36 | - </plugin>
|
|
| 37 | - </plugins>
|
|
| 38 | - </build>
|
|
| 39 | -</project>
|
|
| 1 | +<?xml version="1.0" encoding="UTF-8"?> |
|
| 2 | +<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" |
|
| 3 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
|
| 4 | + <modelVersion>4.0.0</modelVersion> |
|
| 5 | + <parent> |
|
| 6 | + <artifactId>root</artifactId> |
|
| 7 | + <groupId>com.sap.sailing</groupId> |
|
| 8 | + <version>1.0.0-SNAPSHOT</version> |
|
| 9 | + </parent> |
|
| 10 | + <artifactId>com.sap.sailing.domain.shared.android</artifactId> |
|
| 11 | + <packaging>eclipse-plugin</packaging> |
|
| 12 | + <build> |
|
| 13 | + <plugins> |
|
| 14 | + <plugin> |
|
| 15 | + <groupId>org.eclipse.tycho</groupId> |
|
| 16 | + <artifactId>tycho-compiler-plugin</artifactId> |
|
| 17 | + <version>${tycho-version}</version> |
|
| 18 | + <configuration> |
|
| 19 | + <source>1.7</source> |
|
| 20 | + <target>1.7</target> |
|
| 21 | + </configuration> |
|
| 22 | + </plugin> |
|
| 23 | + <plugin> |
|
| 24 | + <groupId>org.eclipse.tycho</groupId> |
|
| 25 | + <artifactId>tycho-source-plugin</artifactId> |
|
| 26 | + <version>${tycho-version}</version> |
|
| 27 | + <executions> |
|
| 28 | + <execution> |
|
| 29 | + <id>plugin-source</id> |
|
| 30 | + <phase>generate-sources</phase> |
|
| 31 | + <goals> |
|
| 32 | + <goal>plugin-source</goal> |
|
| 33 | + </goals> |
|
| 34 | + </execution> |
|
| 35 | + </executions> |
|
| 36 | + </plugin> |
|
| 37 | + </plugins> |
|
| 38 | + </build> |
|
| 39 | +</project> |
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/EventBase.java
| ... | ... | @@ -1,98 +1,98 @@ |
| 1 | -package com.sap.sailing.domain.base;
|
|
| 2 | -
|
|
| 3 | -import java.net.URL;
|
|
| 4 | -import java.util.Map;
|
|
| 5 | -
|
|
| 6 | -import com.sap.sailing.domain.common.Renamable;
|
|
| 7 | -import com.sap.sse.common.Named;
|
|
| 8 | -import com.sap.sse.common.TimePoint;
|
|
| 9 | -import com.sap.sse.common.WithID;
|
|
| 10 | -import com.sap.sse.common.media.ImageSize;
|
|
| 11 | -import com.sap.sse.common.media.WithMedia;
|
|
| 12 | -
|
|
| 13 | -/**
|
|
| 14 | - * Base interface for an Event consisting of all static information, which might be shared
|
|
| 15 | - * by the server and an Android application.
|
|
| 16 | - */
|
|
| 17 | -public interface EventBase extends Named, WithDescription, Renamable, WithID, WithMedia {
|
|
| 18 | -
|
|
| 19 | - void setDescription(String description);
|
|
| 20 | -
|
|
| 21 | - /**
|
|
| 22 | - * @return a non-<code>null</code> venue for this event
|
|
| 23 | - */
|
|
| 24 | - Venue getVenue();
|
|
| 25 | -
|
|
| 26 | - /**
|
|
| 27 | - * @return the start date of the event
|
|
| 28 | - */
|
|
| 29 | - TimePoint getStartDate();
|
|
| 30 | -
|
|
| 31 | - void setStartDate(TimePoint startDate);
|
|
| 32 | -
|
|
| 33 | - /**
|
|
| 34 | - * @return the end date of the event
|
|
| 35 | - */
|
|
| 36 | - TimePoint getEndDate();
|
|
| 37 | -
|
|
| 38 | - void setEndDate(TimePoint startDate);
|
|
| 39 | -
|
|
| 40 | - boolean isPublic();
|
|
| 41 | -
|
|
| 42 | - void setPublic(boolean isPublic);
|
|
| 43 | -
|
|
| 44 | - /**
|
|
| 45 | - * @deprecated
|
|
| 46 | - * Returns a non-<code>null</code> live but unmodifiable collection of URLs pointing to image resources that can be
|
|
| 47 | - * used to represent the event, e.g., on a web page.
|
|
| 48 | - *
|
|
| 49 | - * @return a non-<code>null</code> value which may be empty
|
|
| 50 | - */
|
|
| 51 | - Iterable<URL> getImageURLs();
|
|
| 52 | -
|
|
| 53 | - /**
|
|
| 54 | - * @deprecated
|
|
| 55 | - * An event may have zero or more sponsors, each of which usually want to see their logo on the web page.
|
|
| 56 | - *
|
|
| 57 | - * @return the sponsors' logos; always non-<code>null</code> but possibly empty
|
|
| 58 | - */
|
|
| 59 | - Iterable<URL> getSponsorImageURLs();
|
|
| 60 | -
|
|
| 61 | - /**
|
|
| 62 | - * @deprecated
|
|
| 63 | - * An optional logo image; may return <code>null</code>.
|
|
| 64 | - */
|
|
| 65 | - URL getLogoImageURL();
|
|
| 66 | -
|
|
| 67 | - /**
|
|
| 68 | - * @deprecated
|
|
| 69 | - * Returns a non-<code>null</code> live but unmodifiable collection of URLs pointing to video resources that can be
|
|
| 70 | - * used to represent the event, e.g., on a web page.
|
|
| 71 | - *
|
|
| 72 | - * @return a non-<code>null</code> value which may be empty
|
|
| 73 | - */
|
|
| 74 | - Iterable<URL> getVideoURLs();
|
|
| 75 | -
|
|
| 76 | - /**
|
|
| 77 | - * @return the URL of the event's official web site, or <code>null</code> if such a site does not exist or its URL
|
|
| 78 | - * is not known
|
|
| 79 | - */
|
|
| 80 | - URL getOfficialWebsiteURL();
|
|
| 81 | -
|
|
| 82 | - void setOfficialWebsiteURL(URL officialWebsiteURL);
|
|
| 83 | -
|
|
| 84 | - /**
|
|
| 85 | - * @return the URL of an external web site containing sailor related information like protests, official results, etc.
|
|
| 86 | - * or <code>null</code> if such a site does not exist or its URL is not known.
|
|
| 87 | - */
|
|
| 88 | - URL getSailorsInfoWebsiteURL();
|
|
| 89 | -
|
|
| 90 | - void setSailorsInfoWebsiteURL(URL sailorsInfoWebsiteURL);
|
|
| 91 | -
|
|
| 92 | - Iterable<? extends LeaderboardGroupBase> getLeaderboardGroups();
|
|
| 93 | -
|
|
| 94 | - /**
|
|
| 95 | - * Sets and converts all event images and videos from the old URL based format to the new richer format
|
|
| 96 | - * */
|
|
| 97 | - boolean setMediaURLs(Iterable<URL> imageURLs, Iterable<URL> sponsorImageURLs, Iterable<URL> videoURLs, URL logoImageURL, Map<URL, ImageSize> imageSizes);
|
|
| 98 | -}
|
|
| 1 | +package com.sap.sailing.domain.base; |
|
| 2 | + |
|
| 3 | +import java.net.URL; |
|
| 4 | +import java.util.Map; |
|
| 5 | + |
|
| 6 | +import com.sap.sailing.domain.common.Renamable; |
|
| 7 | +import com.sap.sse.common.Named; |
|
| 8 | +import com.sap.sse.common.TimePoint; |
|
| 9 | +import com.sap.sse.common.WithID; |
|
| 10 | +import com.sap.sse.common.media.ImageSize; |
|
| 11 | +import com.sap.sse.shared.media.WithMedia; |
|
| 12 | + |
|
| 13 | +/** |
|
| 14 | + * Base interface for an Event consisting of all static information, which might be shared |
|
| 15 | + * by the server and an Android application. |
|
| 16 | + */ |
|
| 17 | +public interface EventBase extends Named, WithDescription, Renamable, WithID, WithMedia { |
|
| 18 | + |
|
| 19 | + void setDescription(String description); |
|
| 20 | + |
|
| 21 | + /** |
|
| 22 | + * @return a non-<code>null</code> venue for this event |
|
| 23 | + */ |
|
| 24 | + Venue getVenue(); |
|
| 25 | + |
|
| 26 | + /** |
|
| 27 | + * @return the start date of the event |
|
| 28 | + */ |
|
| 29 | + TimePoint getStartDate(); |
|
| 30 | + |
|
| 31 | + void setStartDate(TimePoint startDate); |
|
| 32 | + |
|
| 33 | + /** |
|
| 34 | + * @return the end date of the event |
|
| 35 | + */ |
|
| 36 | + TimePoint getEndDate(); |
|
| 37 | + |
|
| 38 | + void setEndDate(TimePoint startDate); |
|
| 39 | + |
|
| 40 | + boolean isPublic(); |
|
| 41 | + |
|
| 42 | + void setPublic(boolean isPublic); |
|
| 43 | + |
|
| 44 | + /** |
|
| 45 | + * @deprecated |
|
| 46 | + * Returns a non-<code>null</code> live but unmodifiable collection of URLs pointing to image resources that can be |
|
| 47 | + * used to represent the event, e.g., on a web page. |
|
| 48 | + * |
|
| 49 | + * @return a non-<code>null</code> value which may be empty |
|
| 50 | + */ |
|
| 51 | + Iterable<URL> getImageURLs(); |
|
| 52 | + |
|
| 53 | + /** |
|
| 54 | + * @deprecated |
|
| 55 | + * An event may have zero or more sponsors, each of which usually want to see their logo on the web page. |
|
| 56 | + * |
|
| 57 | + * @return the sponsors' logos; always non-<code>null</code> but possibly empty |
|
| 58 | + */ |
|
| 59 | + Iterable<URL> getSponsorImageURLs(); |
|
| 60 | + |
|
| 61 | + /** |
|
| 62 | + * @deprecated |
|
| 63 | + * An optional logo image; may return <code>null</code>. |
|
| 64 | + */ |
|
| 65 | + URL getLogoImageURL(); |
|
| 66 | + |
|
| 67 | + /** |
|
| 68 | + * @deprecated |
|
| 69 | + * Returns a non-<code>null</code> live but unmodifiable collection of URLs pointing to video resources that can be |
|
| 70 | + * used to represent the event, e.g., on a web page. |
|
| 71 | + * |
|
| 72 | + * @return a non-<code>null</code> value which may be empty |
|
| 73 | + */ |
|
| 74 | + Iterable<URL> getVideoURLs(); |
|
| 75 | + |
|
| 76 | + /** |
|
| 77 | + * @return the URL of the event's official web site, or <code>null</code> if such a site does not exist or its URL |
|
| 78 | + * is not known |
|
| 79 | + */ |
|
| 80 | + URL getOfficialWebsiteURL(); |
|
| 81 | + |
|
| 82 | + void setOfficialWebsiteURL(URL officialWebsiteURL); |
|
| 83 | + |
|
| 84 | + /** |
|
| 85 | + * @return the URL of an external web site containing sailor related information like protests, official results, etc. |
|
| 86 | + * or <code>null</code> if such a site does not exist or its URL is not known. |
|
| 87 | + */ |
|
| 88 | + URL getSailorsInfoWebsiteURL(); |
|
| 89 | + |
|
| 90 | + void setSailorsInfoWebsiteURL(URL sailorsInfoWebsiteURL); |
|
| 91 | + |
|
| 92 | + Iterable<? extends LeaderboardGroupBase> getLeaderboardGroups(); |
|
| 93 | + |
|
| 94 | + /** |
|
| 95 | + * Sets and converts all event images and videos from the old URL based format to the new richer format |
|
| 96 | + * */ |
|
| 97 | + boolean setMediaURLs(Iterable<URL> imageURLs, Iterable<URL> sponsorImageURLs, Iterable<URL> videoURLs, URL logoImageURL, Map<URL, ImageSize> imageSizes); |
|
| 98 | +} |
java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/EventBaseImpl.java
| ... | ... | @@ -15,15 +15,15 @@ import com.sap.sailing.domain.base.Venue; |
| 15 | 15 | import com.sap.sse.common.TimePoint; |
| 16 | 16 | import com.sap.sse.common.Util; |
| 17 | 17 | import com.sap.sse.common.Util.Pair; |
| 18 | -import com.sap.sse.common.media.ImageDescriptor; |
|
| 19 | -import com.sap.sse.common.media.ImageDescriptorImpl; |
|
| 20 | 18 | import com.sap.sse.common.media.ImageSize; |
| 21 | -import com.sap.sse.common.media.MediaDescriptor; |
|
| 22 | 19 | import com.sap.sse.common.media.MediaTagConstants; |
| 23 | -import com.sap.sse.common.media.MediaUtils; |
|
| 24 | 20 | import com.sap.sse.common.media.MimeType; |
| 25 | -import com.sap.sse.common.media.VideoDescriptor; |
|
| 26 | -import com.sap.sse.common.media.VideoDescriptorImpl; |
|
| 21 | +import com.sap.sse.shared.media.ImageDescriptor; |
|
| 22 | +import com.sap.sse.shared.media.MediaDescriptor; |
|
| 23 | +import com.sap.sse.shared.media.MediaUtils; |
|
| 24 | +import com.sap.sse.shared.media.VideoDescriptor; |
|
| 25 | +import com.sap.sse.shared.media.impl.ImageDescriptorImpl; |
|
| 26 | +import com.sap.sse.shared.media.impl.VideoDescriptorImpl; |
|
| 27 | 27 | |
| 28 | 28 | public abstract class EventBaseImpl implements EventBase { |
| 29 | 29 | private static final long serialVersionUID = -5749964088848611074L; |
java/com.sap.sailing.domain.test/resources/IncrementalLeaderboardDTO.ser
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/MultipleClassesInRegattaTest.java
| ... | ... | @@ -60,7 +60,7 @@ public class MultipleClassesInRegattaTest { |
| 60 | 60 | httpAndHost
|
| 61 | 61 | + "/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=cce678c8-97e6-11e0-9aed-406186cbf87c"),
|
| 62 | 62 | new URI(liveURI), new URI(storedURI), new URI(courseDesignUpdateURI),
|
| 63 | - /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* simulateWithStartTimeNow */ false, /* ignoreTracTracMarkPassings*/
|
|
| 63 | + /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* offsetToStartTimeOfSimulatedRace */ null, /* ignoreTracTracMarkPassings*/
|
|
| 64 | 64 | false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, EmptyWindStore.INSTANCE,
|
| 65 | 65 | EmptyGPSFixStore.INSTANCE, tracTracUsername, tracTracPassword,
|
| 66 | 66 | TracTracConnectionConstants.ONLINE_STATUS, TracTracConnectionConstants.ONLINE_VISIBILITY,
|
| ... | ... | @@ -71,7 +71,7 @@ public class MultipleClassesInRegattaTest { |
| 71 | 71 | httpAndHost
|
| 72 | 72 | + "/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=11290bd6-97e7-11e0-9aed-406186cbf87c"),
|
| 73 | 73 | new URI(liveURI), new URI(storedURI), new URI(courseDesignUpdateURI),
|
| 74 | - /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* simulateWithStartTimeNow */ false, /* ignoreTracTracMarkPassings*/
|
|
| 74 | + /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* offsetToStartTimeOfSimulatedRace */ null, /* ignoreTracTracMarkPassings*/
|
|
| 75 | 75 | false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, EmptyWindStore.INSTANCE,
|
| 76 | 76 | EmptyGPSFixStore.INSTANCE, tracTracUsername, tracTracPassword,
|
| 77 | 77 | TracTracConnectionConstants.ONLINE_STATUS, TracTracConnectionConstants.ONLINE_VISIBILITY,
|
| ... | ... | @@ -82,7 +82,7 @@ public class MultipleClassesInRegattaTest { |
| 82 | 82 | httpAndHost
|
| 83 | 83 | + "/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=39635b24-97e7-11e0-9aed-406186cbf87c"),
|
| 84 | 84 | new URI(liveURI), new URI(storedURI), new URI(courseDesignUpdateURI),
|
| 85 | - /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* simulateWithStartTimeNow */ false, /* ignoreTracTracMarkPassings*/
|
|
| 85 | + /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* offsetToStartTimeOfSimulatedRace */ null, /* ignoreTracTracMarkPassings*/
|
|
| 86 | 86 | false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, EmptyWindStore.INSTANCE,
|
| 87 | 87 | EmptyGPSFixStore.INSTANCE, tracTracUsername, tracTracPassword,
|
| 88 | 88 | TracTracConnectionConstants.ONLINE_STATUS, TracTracConnectionConstants.ONLINE_VISIBILITY,
|
| ... | ... | @@ -93,7 +93,7 @@ public class MultipleClassesInRegattaTest { |
| 93 | 93 | httpAndHost
|
| 94 | 94 | + "/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=04498426-7dfd-11e0-8236-406186cbf87c"),
|
| 95 | 95 | new URI(liveURI), new URI(storedURI), new URI(courseDesignUpdateURI),
|
| 96 | - /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* simulateWithStartTimeNow */ false, /* ignoreTracTracMarkPassings*/
|
|
| 96 | + /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* offsetToStartTimeOfSimulatedRace */ null, /* ignoreTracTracMarkPassings*/
|
|
| 97 | 97 | false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, EmptyWindStore.INSTANCE,
|
| 98 | 98 | EmptyGPSFixStore.INSTANCE, tracTracUsername, tracTracPassword,
|
| 99 | 99 | TracTracConnectionConstants.ONLINE_STATUS, TracTracConnectionConstants.ONLINE_VISIBILITY,
|
java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/UnicodeCharactersInCompetitorNamesTest.java
| ... | ... | @@ -67,7 +67,7 @@ public class UnicodeCharactersInCompetitorNamesTest { |
| 67 | 67 | + TracTracConnectionConstants.PORT_STORED), new URI(
|
| 68 | 68 | "http://tracms.traclive.dk/update_course"),
|
| 69 | 69 | /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l,
|
| 70 | - /* simulateWithStartTimeNow */ false, /* ignoreTracTracMarkPassings*/
|
|
| 70 | + /* offsetToStartTimeOfSimulatedRace */ null, /* ignoreTracTracMarkPassings*/
|
|
| 71 | 71 | false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE,
|
| 72 | 72 | EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, "tracTest", "tracTest", "", "",
|
| 73 | 73 | new DummyTrackedRegattaRegistry(), mock(RaceLogResolver.class));
|
java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/DomainFactory.java
| ... | ... | @@ -44,6 +44,7 @@ import com.sap.sailing.domain.tracking.impl.EmptyWindStore; |
| 44 | 44 | import com.sap.sailing.domain.tractracadapter.impl.DomainFactoryImpl;
|
| 45 | 45 | import com.sap.sailing.domain.tractracadapter.impl.RaceCourseReceiver;
|
| 46 | 46 | import com.sap.sailing.domain.tractracadapter.impl.Simulator;
|
| 47 | +import com.sap.sse.common.Duration; |
|
| 47 | 48 | import com.sap.sse.common.TimePoint;
|
| 48 | 49 | import com.sap.sse.common.Util;
|
| 49 | 50 | import com.tractrac.model.lib.api.data.IPosition;
|
| ... | ... | @@ -95,7 +96,6 @@ public interface DomainFactory { |
| 95 | 96 | * Fetch a race definition previously created by a call to {@link #getOrCreateRaceDefinitionAndTrackedRace}. If no such
|
| 96 | 97 | * race definition was created so far, the call blocks until such a definition is provided by a call to
|
| 97 | 98 | * {@link #getOrCreateRaceDefinitionAndTrackedRace}.
|
| 98 | - * @param raceId TODO
|
|
| 99 | 99 | */
|
| 100 | 100 | RaceDefinition getAndWaitForRaceDefinition(UUID raceId);
|
| 101 | 101 | |
| ... | ... | @@ -105,7 +105,6 @@ public interface DomainFactory { |
| 105 | 105 | * A new {@link com.sap.sailing.domain.base.Regatta} is created if no event by
|
| 106 | 106 | * an equal name with a boat class with an equal name as the <code>event</code>'s
|
| 107 | 107 | * boat class exists yet.
|
| 108 | - * @param trackedRegattaRegistry TODO
|
|
| 109 | 108 | */
|
| 110 | 109 | com.sap.sailing.domain.base.Regatta getOrCreateDefaultRegatta(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
|
| 111 | 110 | IRace race, TrackedRegattaRegistry trackedRegattaRegistry);
|
| ... | ... | @@ -134,27 +133,30 @@ public interface DomainFactory { |
| 134 | 133 | * if <code>null</code>, all stored data until the "end of time" will be loaded that the event has
|
| 135 | 134 | * to provide, particularly for the mark positions which are stored per event, not per race; otherwise,
|
| 136 | 135 | * particularly the mark position loading will be constrained to this end time.
|
| 136 | + * @param offsetToStartTimeOfSimulatedRace
|
|
| 137 | + * if non-<code>null</code>, the {@link Simulator} will be used with this duration as start offset
|
|
| 137 | 138 | * @param windStore
|
| 138 | 139 | * Provides the capability to obtain the {@link WindTrack}s for the different wind sources. A trivial
|
| 139 | 140 | * implementation is {@link EmptyWindStore} which simply provides new, empty tracks. This is always
|
| 140 | 141 | * available but loses track of the wind, e.g., during server restarts.
|
| 141 | - * @param raceLogResolver TODO
|
|
| 142 | 142 | */
|
| 143 | 143 | TracTracRaceTracker createRaceTracker(URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI,
|
| 144 | 144 | TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis,
|
| 145 | - boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
|
|
| 145 | + Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
|
|
| 146 | 146 | WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername, String tracTracPassword,
|
| 147 | 147 | String raceStatus, String raceVisibility, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver)
|
| 148 | 148 | throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException, SubscriberInitializationException;
|
| 149 | 149 | |
| 150 | 150 | /**
|
| 151 | - * Same as {@link #createRaceTracker(URL, URI, URI, URI, TimePoint, TimePoint, WindStore, TrackedRegattaRegistry)}, only that
|
|
| 152 | - * a predefined {@link Regatta} is used to hold the resulting races.
|
|
| 153 | - * @param raceLogResolver TODO
|
|
| 151 | + * Same as {@link #createRaceTracker(URL, URI, URI, URI, TimePoint, TimePoint, WindStore, TrackedRegattaRegistry)},
|
|
| 152 | + * only that a predefined {@link Regatta} is used to hold the resulting races.
|
|
| 153 | + *
|
|
| 154 | + * @param offsetToStartTimeOfSimulatedRace
|
|
| 155 | + * if non-<code>null</code>, the {@link Simulator} will be used with this duration as start offset
|
|
| 154 | 156 | */
|
| 155 | 157 | RaceTracker createRaceTracker(Regatta regatta, URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI,
|
| 156 | 158 | TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis,
|
| 157 | - boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
|
|
| 159 | + Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
|
|
| 158 | 160 | WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername, String tracTracPassword,
|
| 159 | 161 | String raceStatus, String raceVisibility, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver)
|
| 160 | 162 | throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException,
|
| ... | ... | @@ -262,9 +264,13 @@ public interface DomainFactory { |
| 262 | 264 | |
| 263 | 265 | Util.Pair<Iterable<com.sap.sailing.domain.base.Competitor>, BoatClass> getCompetitorsAndDominantBoatClass(IRace race);
|
| 264 | 266 | |
| 267 | + /**
|
|
| 268 | + * @param offsetToStartTimeOfSimulatedRace
|
|
| 269 | + * if non-<code>null</code>, the {@link Simulator} will be used with this duration as start offset
|
|
| 270 | + */
|
|
| 265 | 271 | RaceTrackingConnectivityParameters createTrackingConnectivityParameters(URL paramURL, URI liveURI, URI storedURI,
|
| 266 | 272 | URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis,
|
| 267 | - boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
|
|
| 273 | + Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
|
|
| 268 | 274 | String tracTracUsername, String tracTracPassword, String raceStatus, String raceVisibility);
|
| 269 | 275 | /**
|
| 270 | 276 | * Removes all knowledge about <code>tractracRace</code> which includes removing it from the race cache, from the
|
java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracAdapter.java
| ... | ... | @@ -17,6 +17,8 @@ import com.sap.sailing.domain.regattalog.RegattaLogStore; |
| 17 | 17 | import com.sap.sailing.domain.tracking.RaceHandle; |
| 18 | 18 | import com.sap.sailing.domain.tracking.TrackerManager; |
| 19 | 19 | import com.sap.sailing.domain.tracking.WindStore; |
| 20 | +import com.sap.sailing.domain.tractracadapter.impl.Simulator; |
|
| 21 | +import com.sap.sse.common.Duration; |
|
| 20 | 22 | import com.sap.sse.common.TimePoint; |
| 21 | 23 | import com.sap.sse.common.Util; |
| 22 | 24 | |
| ... | ... | @@ -65,12 +67,13 @@ public interface TracTracAdapter { |
| 65 | 67 | * created, with a single default series and a single default fleet. If a valid {@link RegattaIdentifier} |
| 66 | 68 | * is specified, a regatta lookup is performed with that identifier; if the regatta is found, it is used |
| 67 | 69 | * to add the races to. Otherwise, a default regatta as described above will be created and used. |
| 68 | - * @param raceStatus |
|
| 70 | + * @param offsetToStartTimeOfSimulatedRace |
|
| 71 | + * if non-<code>null</code>, the {@link Simulator} will be used with this duration as start offset |
|
| 69 | 72 | */ |
| 70 | 73 | RaceHandle addTracTracRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo, URL paramURL, |
| 71 | 74 | URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint trackingStartTime, |
| 72 | 75 | TimePoint trackingEndTime, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, |
| 73 | - long timeoutForReceivingRaceDefinitionInMilliseconds, boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, |
|
| 76 | + long timeoutForReceivingRaceDefinitionInMilliseconds, Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, |
|
| 74 | 77 | String tracTracUsername, String tracTracPassword, String raceStatus, String raceVisibility) |
| 75 | 78 | throws MalformedURLException, FileNotFoundException, URISyntaxException, Exception; |
| 76 | 79 |
java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java
| ... | ... | @@ -74,6 +74,7 @@ import com.sap.sailing.domain.tractracadapter.ReceiverType; |
| 74 | 74 | import com.sap.sailing.domain.tractracadapter.TracTracConfiguration;
|
| 75 | 75 | import com.sap.sailing.domain.tractracadapter.TracTracControlPoint;
|
| 76 | 76 | import com.sap.sailing.domain.tractracadapter.TracTracRaceTracker;
|
| 77 | +import com.sap.sse.common.Duration;
|
|
| 77 | 78 | import com.sap.sse.common.TimePoint;
|
| 78 | 79 | import com.sap.sse.common.Util;
|
| 79 | 80 | import com.sap.sse.common.Util.Pair;
|
| ... | ... | @@ -652,27 +653,27 @@ public class DomainFactoryImpl implements DomainFactory { |
| 652 | 653 | @Override
|
| 653 | 654 | public TracTracRaceTracker createRaceTracker(URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI,
|
| 654 | 655 | TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis,
|
| 655 | - boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore,
|
|
| 656 | + Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore,
|
|
| 656 | 657 | RegattaLogStore regattaLogStore, WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername,
|
| 657 | 658 | String tracTracPassword, String raceStatus, String raceVisibility,
|
| 658 | 659 | TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver) throws MalformedURLException, FileNotFoundException,
|
| 659 | 660 | URISyntaxException, CreateModelException, SubscriberInitializationException {
|
| 660 | 661 | return new TracTracRaceTrackerImpl(this, paramURL, liveURI, storedURI, courseDesignUpdateURI, startOfTracking,
|
| 661 | - endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, windStore,
|
|
| 662 | + endOfTracking, delayToLiveInMillis, offsetToStartTimeOfSimulatedRace, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, windStore,
|
|
| 662 | 663 | gpsFixStore, tracTracUsername, tracTracPassword, raceStatus, raceVisibility, trackedRegattaRegistry, raceLogResolver);
|
| 663 | 664 | }
|
| 664 | 665 | |
| 665 | 666 | @Override
|
| 666 | 667 | public RaceTracker createRaceTracker(Regatta regatta, URL paramURL, URI liveURI, URI storedURI,
|
| 667 | 668 | URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis,
|
| 668 | - boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore,
|
|
| 669 | + Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore,
|
|
| 669 | 670 | RegattaLogStore regattaLogStore, WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername,
|
| 670 | 671 | String tracTracPassword, String raceStatus, String raceVisibility,
|
| 671 | 672 | TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver)
|
| 672 | 673 | throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException,
|
| 673 | 674 | SubscriberInitializationException {
|
| 674 | 675 | return new TracTracRaceTrackerImpl(regatta, this, paramURL, liveURI, storedURI, courseDesignUpdateURI,
|
| 675 | - startOfTracking, endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore,
|
|
| 676 | + startOfTracking, endOfTracking, delayToLiveInMillis, offsetToStartTimeOfSimulatedRace, useInternalMarkPassingAlgorithm, raceLogStore,
|
|
| 676 | 677 | regattaLogStore, windStore, gpsFixStore, tracTracUsername, tracTracPassword, raceStatus,
|
| 677 | 678 | raceVisibility, trackedRegattaRegistry, raceLogResolver);
|
| 678 | 679 | }
|
| ... | ... | @@ -690,11 +691,11 @@ public class DomainFactoryImpl implements DomainFactory { |
| 690 | 691 | @Override
|
| 691 | 692 | public RaceTrackingConnectivityParameters createTrackingConnectivityParameters(URL paramURL, URI liveURI,
|
| 692 | 693 | URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking,
|
| 693 | - long delayToLiveInMillis, boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore,
|
|
| 694 | + long delayToLiveInMillis, Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore,
|
|
| 694 | 695 | RegattaLogStore regattaLogStore, String tracTracUsername, String tracTracPassword, String raceStatus,
|
| 695 | 696 | String raceVisibility) {
|
| 696 | 697 | return new RaceTrackingConnectivityParametersImpl(paramURL, liveURI, storedURI, courseDesignUpdateURI,
|
| 697 | - startOfTracking, endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore,
|
|
| 698 | + startOfTracking, endOfTracking, delayToLiveInMillis, offsetToStartTimeOfSimulatedRace, useInternalMarkPassingAlgorithm, raceLogStore,
|
|
| 698 | 699 | regattaLogStore, this, tracTracUsername, tracTracPassword, raceStatus, raceVisibility);
|
| 699 | 700 | }
|
| 700 | 701 |
java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceTrackingConnectivityParametersImpl.java
| ... | ... | @@ -16,6 +16,7 @@ import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters; |
| 16 | 16 | import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
|
| 17 | 17 | import com.sap.sailing.domain.tracking.WindStore;
|
| 18 | 18 | import com.sap.sailing.domain.tractracadapter.DomainFactory;
|
| 19 | +import com.sap.sse.common.Duration;
|
|
| 19 | 20 | import com.sap.sse.common.TimePoint;
|
| 20 | 21 | import com.tractrac.model.lib.api.event.CreateModelException;
|
| 21 | 22 | import com.tractrac.subscription.lib.api.SubscriberInitializationException;
|
| ... | ... | @@ -31,7 +32,7 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne |
| 31 | 32 | private final RegattaLogStore regattaLogStore;
|
| 32 | 33 | private final DomainFactory domainFactory;
|
| 33 | 34 | private final long delayToLiveInMillis;
|
| 34 | - private final boolean simulateWithStartTimeNow;
|
|
| 35 | + private final Duration offsetToStartTimeOfSimulatedRace;
|
|
| 35 | 36 | private final String tracTracUsername;
|
| 36 | 37 | private final String tracTracPassword;
|
| 37 | 38 | private final String raceStatus;
|
| ... | ... | @@ -40,7 +41,7 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne |
| 40 | 41 | |
| 41 | 42 | public RaceTrackingConnectivityParametersImpl(URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI,
|
| 42 | 43 | TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis,
|
| 43 | - boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
|
|
| 44 | + Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
|
|
| 44 | 45 | DomainFactory domainFactory, String tracTracUsername, String tracTracPassword, String raceStatus,
|
| 45 | 46 | String raceVisibility) {
|
| 46 | 47 | super();
|
| ... | ... | @@ -52,7 +53,7 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne |
| 52 | 53 | this.endOfTracking = endOfTracking;
|
| 53 | 54 | this.delayToLiveInMillis = delayToLiveInMillis;
|
| 54 | 55 | this.domainFactory = domainFactory;
|
| 55 | - this.simulateWithStartTimeNow = simulateWithStartTimeNow;
|
|
| 56 | + this.offsetToStartTimeOfSimulatedRace = offsetToStartTimeOfSimulatedRace;
|
|
| 56 | 57 | this.raceLogStore = raceLogStore;
|
| 57 | 58 | this.regattaLogStore = regattaLogStore;
|
| 58 | 59 | this.tracTracUsername = tracTracUsername;
|
| ... | ... | @@ -67,7 +68,7 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne |
| 67 | 68 | GPSFixStore gpsFixStore, RaceLogResolver raceLogResolver) throws MalformedURLException, FileNotFoundException, URISyntaxException,
|
| 68 | 69 | CreateModelException, SubscriberInitializationException {
|
| 69 | 70 | RaceTracker tracker = domainFactory.createRaceTracker(paramURL, liveURI, storedURI, courseDesignUpdateURI,
|
| 70 | - startOfTracking, endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore,
|
|
| 71 | + startOfTracking, endOfTracking, delayToLiveInMillis, offsetToStartTimeOfSimulatedRace, useInternalMarkPassingAlgorithm, raceLogStore,
|
|
| 71 | 72 | regattaLogStore, windStore, gpsFixStore, tracTracUsername, tracTracPassword, raceStatus,
|
| 72 | 73 | raceVisibility, trackedRegattaRegistry, raceLogResolver);
|
| 73 | 74 | return tracker;
|
| ... | ... | @@ -77,7 +78,7 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne |
| 77 | 78 | public RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry,
|
| 78 | 79 | WindStore windStore, GPSFixStore gpsFixStore, RaceLogResolver raceLogResolver) throws Exception {
|
| 79 | 80 | RaceTracker tracker = domainFactory.createRaceTracker(regatta, paramURL, liveURI, storedURI,
|
| 80 | - courseDesignUpdateURI, startOfTracking, endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, useInternalMarkPassingAlgorithm,
|
|
| 81 | + courseDesignUpdateURI, startOfTracking, endOfTracking, delayToLiveInMillis, offsetToStartTimeOfSimulatedRace, useInternalMarkPassingAlgorithm,
|
|
| 81 | 82 | raceLogStore, regattaLogStore, windStore, gpsFixStore, tracTracUsername, tracTracPassword, raceStatus,
|
| 82 | 83 | raceVisibility, trackedRegattaRegistry, raceLogResolver);
|
| 83 | 84 | return tracker;
|
java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RawPositionReceiver.java
| ... | ... | @@ -4,7 +4,6 @@ import java.util.logging.Logger; |
| 4 | 4 | |
| 5 | 5 | import com.sap.sailing.domain.base.Competitor;
|
| 6 | 6 | import com.sap.sailing.domain.common.tracking.GPSFixMoving;
|
| 7 | -import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl;
|
|
| 8 | 7 | import com.sap.sailing.domain.tracking.DynamicTrackedRace;
|
| 9 | 8 | import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
|
| 10 | 9 | import com.sap.sailing.domain.tractracadapter.DomainFactory;
|
| ... | ... | @@ -57,13 +56,13 @@ public class RawPositionReceiver extends AbstractReceiverWithQueue<IRaceCompetit |
| 57 | 56 | IRace race = event.getA().getRace();
|
| 58 | 57 | DynamicTrackedRace trackedRace = getTrackedRace(race);
|
| 59 | 58 | if (trackedRace != null) {
|
| 60 | - GPSFixMoving fix = getDomainFactory().createGPSFixMoving(event.getB());
|
|
| 59 | + final GPSFixMoving fix = getDomainFactory().createGPSFixMoving(event.getB());
|
|
| 60 | + Competitor competitor = getDomainFactory().getOrCreateCompetitor(event.getA().getCompetitor());
|
|
| 61 | 61 | if (getSimulator() != null) {
|
| 62 | - fix = new GPSFixMovingImpl(fix.getPosition(), getSimulator().delay(
|
|
| 63 | - fix.getTimePoint()), fix.getSpeed());
|
|
| 62 | + getSimulator().scheduleCompetitorPosition(competitor, fix);
|
|
| 63 | + } else {
|
|
| 64 | + trackedRace.recordFix(competitor, fix);
|
|
| 64 | 65 | }
|
| 65 | - Competitor competitor = getDomainFactory().getOrCreateCompetitor(event.getA().getCompetitor());
|
|
| 66 | - trackedRace.recordFix(competitor, fix);
|
|
| 67 | 66 | } else {
|
| 68 | 67 | logger.warning("Couldn't find tracked race for race " + race.getName()
|
| 69 | 68 | + ". Dropping raw position event " + event);
|
java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/Simulator.java
| ... | ... | @@ -24,7 +24,9 @@ import com.sap.sailing.domain.tracking.WindStore; |
| 24 | 24 | import com.sap.sailing.domain.tracking.WindTrack;
|
| 25 | 25 | import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
|
| 26 | 26 | import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
|
| 27 | +import com.sap.sse.common.Duration;
|
|
| 27 | 28 | import com.sap.sse.common.TimePoint;
|
| 29 | +import com.sap.sse.common.impl.MillisecondsDurationImpl;
|
|
| 28 | 30 | import com.sap.sse.common.impl.MillisecondsTimePoint;
|
| 29 | 31 | |
| 30 | 32 | public class Simulator {
|
| ... | ... | @@ -33,13 +35,15 @@ public class Simulator { |
| 33 | 35 | private DynamicTrackedRace trackedRace;
|
| 34 | 36 | private final WindStore windStore;
|
| 35 | 37 | private boolean stopped;
|
| 36 | - private long advanceInMillis = -1;
|
|
| 38 | + private Duration advanceInMillis = Duration.NULL.minus(1);
|
|
| 37 | 39 | private Timer timer = new Timer("Timer for TracTrac Simulator");
|
| 40 | + private final Duration offsetToStart;
|
|
| 38 | 41 | |
| 39 | - public Simulator(WindStore windStore) {
|
|
| 42 | + public Simulator(WindStore windStore, Duration offsetToStart) {
|
|
| 40 | 43 | super();
|
| 41 | 44 | assert windStore != null;
|
| 42 | 45 | this.windStore = windStore;
|
| 46 | + this.offsetToStart = offsetToStart;
|
|
| 43 | 47 | }
|
| 44 | 48 | |
| 45 | 49 | /**
|
| ... | ... | @@ -69,7 +73,7 @@ public class Simulator { |
| 69 | 73 | /**
|
| 70 | 74 | * This is what everybody is waiting for :-). Notifies all waiters.
|
| 71 | 75 | */
|
| 72 | - public synchronized void setAdvanceInMillis(long advanceInMillis) {
|
|
| 76 | + public synchronized void setAdvanceInMillis(Duration advanceInMillis) {
|
|
| 73 | 77 | this.advanceInMillis = advanceInMillis;
|
| 74 | 78 | notifyAll();
|
| 75 | 79 | }
|
| ... | ... | @@ -114,7 +118,7 @@ public class Simulator { |
| 114 | 118 | * Waits until {@link #advanceInMillis} is set to something not equal to -1 which is its initial value. Unblocked by
|
| 115 | 119 | * {@link #setAdvanceInMillis(long)}.
|
| 116 | 120 | */
|
| 117 | - private synchronized long getAdvanceInMillis() {
|
|
| 121 | + private synchronized Duration getAdvance() {
|
|
| 118 | 122 | while (!isAdvanceInMillisSet()) {
|
| 119 | 123 | try {
|
| 120 | 124 | wait(2000); // wait for two seconds, then re-evaluate whether there is a start time
|
| ... | ... | @@ -125,6 +129,16 @@ public class Simulator { |
| 125 | 129 | }
|
| 126 | 130 | return advanceInMillis;
|
| 127 | 131 | }
|
| 132 | +
|
|
| 133 | + private Duration getOffsetToStart() {
|
|
| 134 | + final Duration result;
|
|
| 135 | + if (offsetToStart != null) {
|
|
| 136 | + result = offsetToStart;
|
|
| 137 | + } else {
|
|
| 138 | + result = Duration.NULL;
|
|
| 139 | + }
|
|
| 140 | + return result;
|
|
| 141 | + }
|
|
| 128 | 142 | |
| 129 | 143 | /**
|
| 130 | 144 | * Transforms <code>timed</code>'s time point according to this simulator's delay and waits roughly until this time
|
| ... | ... | @@ -157,12 +171,12 @@ public class Simulator { |
| 157 | 171 | * Like {@link #delay}, only that it doesn't wait until <code>timePoint</code> is reached in wall time.
|
| 158 | 172 | */
|
| 159 | 173 | public TimePoint advance(TimePoint timePoint) {
|
| 160 | - return new MillisecondsTimePoint(timePoint.asMillis()+getAdvanceInMillis());
|
|
| 174 | + return timePoint.plus(getAdvance());
|
|
| 161 | 175 | }
|
| 162 | 176 | |
| 163 | 177 | /**
|
| 164 | 178 | * If {@link #advanceInMillis} is already set to a non-negative value, it is left alone, and
|
| 165 | - * {@link #delay(TimePoint)} is called. Otherwise, <code>time</code> is taken to be the original start time of the
|
|
| 179 | + * {@link #advance(TimePoint)} is called. Otherwise, <code>time</code> is taken to be the original start time of the
|
|
| 166 | 180 | * race which is then used to compute {@link #advanceInMillis} such that
|
| 167 | 181 | * <code>time.asMillis() + advanceInMillis == System.currentTimeMillis()</code>.
|
| 168 | 182 | */
|
| ... | ... | @@ -183,13 +197,13 @@ public class Simulator { |
| 183 | 197 | if (isAdvanceInMillisSet()) {
|
| 184 | 198 | return advance(time);
|
| 185 | 199 | } else {
|
| 186 | - setAdvanceInMillis(System.currentTimeMillis() - time.asMillis());
|
|
| 200 | + setAdvanceInMillis(new MillisecondsDurationImpl(MillisecondsTimePoint.now().minus(time.asMillis()).plus(getOffsetToStart()).asMillis()));
|
|
| 187 | 201 | return advance(time);
|
| 188 | 202 | }
|
| 189 | 203 | }
|
| 190 | 204 | |
| 191 | 205 | private boolean isAdvanceInMillisSet() {
|
| 192 | - return advanceInMillis != -1;
|
|
| 206 | + return !advanceInMillis.equals(Duration.NULL.minus(1));
|
|
| 193 | 207 | }
|
| 194 | 208 | |
| 195 | 209 | /**
|
| ... | ... | @@ -238,21 +252,35 @@ public class Simulator { |
| 238 | 252 | // deliver an empty list now
|
| 239 | 253 | trackedRace.updateMarkPassings(competitor, markPassings);
|
| 240 | 254 | }
|
| 241 | -
|
|
| 255 | + }
|
|
| 256 | +
|
|
| 257 | + public void scheduleCompetitorPosition(final Competitor competitor, GPSFixMoving competitorFix) {
|
|
| 258 | + final RecordGPSFix<Competitor> recorder = (c, f)->trackedRace.recordFix(c, f);
|
|
| 259 | + scheduleFixRecording(competitor, competitorFix, recorder);
|
|
| 260 | + }
|
|
| 261 | +
|
|
| 262 | + @FunctionalInterface
|
|
| 263 | + private static interface RecordGPSFix<T> {
|
|
| 264 | + void recordFix(T objectForFix, GPSFixMoving fix);
|
|
| 242 | 265 | }
|
| 243 | 266 | |
| 244 | 267 | public void scheduleMarkPosition(final Mark mark, GPSFixMoving markFix) {
|
| 245 | - final TimePoint transformedTimepoint = advance(markFix.getTimePoint());
|
|
| 246 | - final GPSFixMoving transformedMarkFix = new GPSFixMovingImpl(markFix.getPosition(), transformedTimepoint, markFix.getSpeed());
|
|
| 268 | + final RecordGPSFix<Mark> recorder = (m, f)->trackedRace.recordFix(mark, f);
|
|
| 269 | + scheduleFixRecording(mark, markFix, recorder);
|
|
| 270 | + }
|
|
| 271 | +
|
|
| 272 | + private <T> void scheduleFixRecording(final T object, GPSFixMoving fix, final RecordGPSFix<T> recorder) {
|
|
| 273 | + final TimePoint transformedTimepoint = advance(fix.getTimePoint());
|
|
| 274 | + final GPSFixMoving transformedMarkFix = new GPSFixMovingImpl(fix.getPosition(), transformedTimepoint, fix.getSpeed());
|
|
| 247 | 275 | long waitTime = getWaitTimeInMillisUntil(transformedMarkFix.getTimePoint());
|
| 248 | 276 | if (waitTime <= 0) {
|
| 249 | - trackedRace.recordFix(mark, transformedMarkFix);
|
|
| 277 | + recorder.recordFix(object, transformedMarkFix);
|
|
| 250 | 278 | } else {
|
| 251 | 279 | timer.schedule(new TimerTask() {
|
| 252 | 280 | @Override
|
| 253 | 281 | public void run() {
|
| 254 | 282 | try {
|
| 255 | - trackedRace.recordFix(mark, transformedMarkFix);
|
|
| 283 | + recorder.recordFix(object, transformedMarkFix);
|
|
| 256 | 284 | } catch (Exception e) {
|
| 257 | 285 | logger.throwing(Simulator.class.getName(), "scheduleMarkPosition", e);
|
| 258 | 286 | }
|
java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracAdapterImpl.java
| ... | ... | @@ -21,6 +21,7 @@ import com.sap.sailing.domain.tractracadapter.JSONService; |
| 21 | 21 | import com.sap.sailing.domain.tractracadapter.RaceRecord; |
| 22 | 22 | import com.sap.sailing.domain.tractracadapter.TracTracAdapter; |
| 23 | 23 | import com.sap.sailing.domain.tractracadapter.TracTracConfiguration; |
| 24 | +import com.sap.sse.common.Duration; |
|
| 24 | 25 | import com.sap.sse.common.TimePoint; |
| 25 | 26 | import com.sap.sse.common.Util; |
| 26 | 27 | |
| ... | ... | @@ -56,7 +57,7 @@ public class TracTracAdapterImpl implements TracTracAdapter { |
| 56 | 57 | getTracTracDomainFactory().createTrackingConnectivityParameters(paramURL, liveURI, storedURI, |
| 57 | 58 | courseDesignUpdateURI, |
| 58 | 59 | /* startOfTracking */null, |
| 59 | - /* endOfTracking */null, delayToLiveInMillis, /* simulateWithStartTimeNow */false, /* ignoreTracTracMarkPassings */ false, |
|
| 60 | + /* endOfTracking */null, delayToLiveInMillis, /* offsetToStartTimeOfSimulatedRace */null, /* ignoreTracTracMarkPassings */ false, |
|
| 60 | 61 | raceLogStore, regattaLogStore, tracTracUsername, tracTracPassword, raceStatus, raceVisibility), |
| 61 | 62 | timeoutInMilliseconds); |
| 62 | 63 | } |
| ... | ... | @@ -65,13 +66,13 @@ public class TracTracAdapterImpl implements TracTracAdapter { |
| 65 | 66 | public RaceHandle addTracTracRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo, URL paramURL, |
| 66 | 67 | URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, |
| 67 | 68 | RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, long timeoutInMilliseconds, |
| 68 | - boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, String tracTracUsername, String tracTracPassword, String raceStatus, |
|
| 69 | + Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, String tracTracUsername, String tracTracPassword, String raceStatus, |
|
| 69 | 70 | String raceVisibility) throws Exception { |
| 70 | 71 | return trackerManager.addRace( |
| 71 | 72 | regattaToAddTo, |
| 72 | 73 | getTracTracDomainFactory().createTrackingConnectivityParameters(paramURL, liveURI, storedURI, |
| 73 | 74 | courseDesignUpdateURI, startOfTracking, endOfTracking, delayToLiveInMillis, |
| 74 | - simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, tracTracUsername, tracTracPassword, |
|
| 75 | + offsetToStartTimeOfSimulatedRace, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, tracTracUsername, tracTracPassword, |
|
| 75 | 76 | raceStatus, raceVisibility), timeoutInMilliseconds); |
| 76 | 77 | } |
| 77 | 78 |
java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/TracTracRaceTrackerImpl.java
| ... | ... | @@ -49,6 +49,7 @@ import com.sap.sailing.domain.tractracadapter.DomainFactory; |
| 49 | 49 | import com.sap.sailing.domain.tractracadapter.Receiver;
|
| 50 | 50 | import com.sap.sailing.domain.tractracadapter.TracTracConnectionConstants;
|
| 51 | 51 | import com.sap.sailing.domain.tractracadapter.TracTracRaceTracker;
|
| 52 | +import com.sap.sse.common.Duration;
|
|
| 52 | 53 | import com.sap.sse.common.TimePoint;
|
| 53 | 54 | import com.sap.sse.common.Util;
|
| 54 | 55 | import com.tractrac.model.lib.api.ModelLocator;
|
| ... | ... | @@ -253,26 +254,26 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements |
| 253 | 254 | */
|
| 254 | 255 | protected TracTracRaceTrackerImpl(DomainFactory domainFactory, URL paramURL, URI liveURI, URI storedURI,
|
| 255 | 256 | URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis,
|
| 256 | - boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
|
|
| 257 | + Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
|
|
| 257 | 258 | WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername, String tracTracPassword,
|
| 258 | 259 | String raceStatus, String raceVisibility, TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver)
|
| 259 | 260 | throws URISyntaxException, MalformedURLException, FileNotFoundException, CreateModelException,
|
| 260 | 261 | SubscriberInitializationException {
|
| 261 | 262 | this(ModelLocator.getEventFactory().createRace(new URI(paramURL.toString())), domainFactory, paramURL, liveURI,
|
| 262 | 263 | storedURI, courseDesignUpdateURI, startOfTracking, endOfTracking, delayToLiveInMillis,
|
| 263 | - simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, windStore, gpsFixStore, tracTracUsername,
|
|
| 264 | + offsetToStartTimeOfSimulatedRace, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, windStore, gpsFixStore, tracTracUsername,
|
|
| 264 | 265 | tracTracPassword, raceStatus, raceVisibility, trackedRegattaRegistry, raceLogResolver);
|
| 265 | 266 | }
|
| 266 | 267 | |
| 267 | 268 | private TracTracRaceTrackerImpl(IRace tractracRace, DomainFactory domainFactory, URL paramURL, URI liveURI,
|
| 268 | 269 | URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking,
|
| 269 | - long delayToLiveInMillis, boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore,
|
|
| 270 | + long delayToLiveInMillis, Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore,
|
|
| 270 | 271 | RegattaLogStore regattaLogStore, WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername,
|
| 271 | 272 | String tracTracPassword, String raceStatus, String raceVisibility,
|
| 272 | 273 | TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver) throws URISyntaxException, MalformedURLException,
|
| 273 | 274 | FileNotFoundException, SubscriberInitializationException {
|
| 274 | 275 | this(tractracRace, null, domainFactory, paramURL, liveURI, storedURI, courseDesignUpdateURI, startOfTracking,
|
| 275 | - endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, windStore,
|
|
| 276 | + endOfTracking, delayToLiveInMillis, offsetToStartTimeOfSimulatedRace, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, windStore,
|
|
| 276 | 277 | gpsFixStore, tracTracUsername, tracTracPassword, raceStatus, raceVisibility, trackedRegattaRegistry, raceLogResolver);
|
| 277 | 278 | }
|
| 278 | 279 | |
| ... | ... | @@ -284,14 +285,14 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements |
| 284 | 285 | */
|
| 285 | 286 | protected TracTracRaceTrackerImpl(Regatta regatta, DomainFactory domainFactory, URL paramURL, URI liveURI,
|
| 286 | 287 | URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking,
|
| 287 | - long delayToLiveInMillis, boolean simulateWithStartTimeNow, boolean ignoreTracTracMarkPassings, RaceLogStore raceLogStore,
|
|
| 288 | + long delayToLiveInMillis, Duration offsetToStartTimeOfSimulatedRace, boolean ignoreTracTracMarkPassings, RaceLogStore raceLogStore,
|
|
| 288 | 289 | RegattaLogStore regattaLogStore, WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername,
|
| 289 | 290 | String tracTracPassword, String raceStatus, String raceVisibility,
|
| 290 | 291 | TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver) throws URISyntaxException,
|
| 291 | 292 | MalformedURLException, FileNotFoundException, CreateModelException, SubscriberInitializationException {
|
| 292 | 293 | this(ModelLocator.getEventFactory().createRace(new URI(paramURL.toString())), regatta, domainFactory, paramURL,
|
| 293 | 294 | liveURI, storedURI, courseDesignUpdateURI, startOfTracking, endOfTracking, delayToLiveInMillis,
|
| 294 | - simulateWithStartTimeNow, ignoreTracTracMarkPassings, raceLogStore, regattaLogStore, windStore, gpsFixStore, tracTracUsername,
|
|
| 295 | + offsetToStartTimeOfSimulatedRace, ignoreTracTracMarkPassings, raceLogStore, regattaLogStore, windStore, gpsFixStore, tracTracUsername,
|
|
| 295 | 296 | tracTracPassword, raceStatus, raceVisibility, trackedRegattaRegistry, raceLogResolver);
|
| 296 | 297 | }
|
| 297 | 298 | |
| ... | ... | @@ -300,15 +301,15 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements |
| 300 | 301 | * @param regatta
|
| 301 | 302 | * if <code>null</code>, then <code>domainFactory.getOrCreateRegatta(tractracEvent)</code> will be used
|
| 302 | 303 | * to obtain a default regatta
|
| 303 | - * @param simulateWithStartTimeNow
|
|
| 304 | - * if <code>true</code>, the connector will adjust the time stamps of all events received such that the
|
|
| 304 | + * @param offsetToStartTimeOfSimulatedRace
|
|
| 305 | + * if not <code>null</code>, the connector will adjust the time stamps of all events received such that the
|
|
| 305 | 306 | * first mark passing for the first waypoint will be set to "now." It will delay the forwarding of all
|
| 306 | - * events received such that they seem to be sent in "real-time." So, more or less the time points
|
|
| 307 | + * events received such that they seem to be sent in "real-time" + <code>offsetToStartTimeOfSimulatedRace</code> So, more or less the time points
|
|
| 307 | 308 | * attached to the events sent to the receivers will again approximate the wall time.
|
| 308 | 309 | */
|
| 309 | 310 | private TracTracRaceTrackerImpl(IRace tractracRace, final Regatta regatta, DomainFactory domainFactory,
|
| 310 | 311 | URL paramURL, URI liveURI, URI storedURI, URI tracTracUpdateURI, TimePoint startOfTracking,
|
| 311 | - TimePoint endOfTracking, long delayToLiveInMillis, boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm,
|
|
| 312 | + TimePoint endOfTracking, long delayToLiveInMillis, Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm,
|
|
| 312 | 313 | RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, GPSFixStore gpsFixStore,
|
| 313 | 314 | String tracTracUsername, String tracTracPassword, String raceStatus, String raceVisibility,
|
| 314 | 315 | TrackedRegattaRegistry trackedRegattaRegistry, RaceLogResolver raceLogResolver) throws URISyntaxException, MalformedURLException,
|
| ... | ... | @@ -323,8 +324,8 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements |
| 323 | 324 | this.gpsFixStore = gpsFixStore;
|
| 324 | 325 | this.domainFactory = domainFactory;
|
| 325 | 326 | this.lastProgressPerID = new HashMap<>();
|
| 326 | - if (simulateWithStartTimeNow) {
|
|
| 327 | - simulator = new Simulator(windStore);
|
|
| 327 | + if (offsetToStartTimeOfSimulatedRace != null) {
|
|
| 328 | + simulator = new Simulator(windStore, offsetToStartTimeOfSimulatedRace);
|
|
| 328 | 329 | // don't write the transformed wind fixes into the DB again... see also bug 1974
|
| 329 | 330 | this.windStore = EmptyWindStore.INSTANCE;
|
| 330 | 331 | } else {
|
java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Course.java
| ... | ... | @@ -43,4 +43,6 @@ public interface Course extends CourseBase { |
| 43 | 43 | void update(Iterable<Util.Pair<ControlPoint, PassingInstruction>> newControlPoints, DomainFactory baseDomainFactory) throws PatchFailedException;
|
| 44 | 44 | |
| 45 | 45 | int getNumberOfWaypoints();
|
| 46 | +
|
|
| 47 | + Leg getLeg(int zeroBasedIndexOfWaypoint);
|
|
| 46 | 48 | }
|
java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/CourseImpl.java
| ... | ... | @@ -247,6 +247,11 @@ public class CourseImpl extends NamedImpl implements Course { |
| 247 | 247 | }
|
| 248 | 248 | |
| 249 | 249 | @Override
|
| 250 | + public Leg getLeg(int zeroBasedIndexOfWaypoint) {
|
|
| 251 | + return legs.get(zeroBasedIndexOfWaypoint);
|
|
| 252 | + }
|
|
| 253 | +
|
|
| 254 | + @Override
|
|
| 250 | 255 | public String toString() {
|
| 251 | 256 | lockForRead();
|
| 252 | 257 | try {
|
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java
| ... | ... | @@ -1,1907 +1,1937 @@ |
| 1 | -package com.sap.sailing.domain.leaderboard.impl;
|
|
| 2 | -
|
|
| 3 | -import java.io.IOException;
|
|
| 4 | -import java.io.ObjectInputStream;
|
|
| 5 | -import java.util.ArrayList;
|
|
| 6 | -import java.util.Collection;
|
|
| 7 | -import java.util.Collections;
|
|
| 8 | -import java.util.Comparator;
|
|
| 9 | -import java.util.Date;
|
|
| 10 | -import java.util.HashMap;
|
|
| 11 | -import java.util.HashSet;
|
|
| 12 | -import java.util.Iterator;
|
|
| 13 | -import java.util.LinkedHashMap;
|
|
| 14 | -import java.util.List;
|
|
| 15 | -import java.util.Map;
|
|
| 16 | -import java.util.NavigableSet;
|
|
| 17 | -import java.util.NoSuchElementException;
|
|
| 18 | -import java.util.Set;
|
|
| 19 | -import java.util.UUID;
|
|
| 20 | -import java.util.concurrent.Callable;
|
|
| 21 | -import java.util.concurrent.ExecutionException;
|
|
| 22 | -import java.util.concurrent.Executor;
|
|
| 23 | -import java.util.concurrent.Executors;
|
|
| 24 | -import java.util.concurrent.Future;
|
|
| 25 | -import java.util.concurrent.FutureTask;
|
|
| 26 | -import java.util.concurrent.LinkedBlockingQueue;
|
|
| 27 | -import java.util.concurrent.RunnableFuture;
|
|
| 28 | -import java.util.concurrent.ThreadPoolExecutor;
|
|
| 29 | -import java.util.concurrent.TimeUnit;
|
|
| 30 | -import java.util.logging.Level;
|
|
| 31 | -import java.util.logging.Logger;
|
|
| 32 | -
|
|
| 33 | -import com.sap.sailing.domain.abstractlog.race.InvalidatesLeaderboardCache;
|
|
| 34 | -import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
|
|
| 35 | -import com.sap.sailing.domain.base.Competitor;
|
|
| 36 | -import com.sap.sailing.domain.base.Course;
|
|
| 37 | -import com.sap.sailing.domain.base.DomainFactory;
|
|
| 38 | -import com.sap.sailing.domain.base.Fleet;
|
|
| 39 | -import com.sap.sailing.domain.base.Leg;
|
|
| 40 | -import com.sap.sailing.domain.base.RaceColumn;
|
|
| 41 | -import com.sap.sailing.domain.base.RaceColumnInSeries;
|
|
| 42 | -import com.sap.sailing.domain.base.RaceColumnListener;
|
|
| 43 | -import com.sap.sailing.domain.base.Waypoint;
|
|
| 44 | -import com.sap.sailing.domain.common.Distance;
|
|
| 45 | -import com.sap.sailing.domain.common.LegType;
|
|
| 46 | -import com.sap.sailing.domain.common.ManeuverType;
|
|
| 47 | -import com.sap.sailing.domain.common.MaxPointsReason;
|
|
| 48 | -import com.sap.sailing.domain.common.NoWindError;
|
|
| 49 | -import com.sap.sailing.domain.common.NoWindException;
|
|
| 50 | -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
|
| 51 | -import com.sap.sailing.domain.common.RegattaNameAndRaceName;
|
|
| 52 | -import com.sap.sailing.domain.common.Speed;
|
|
| 53 | -import com.sap.sailing.domain.common.SpeedWithBearing;
|
|
| 54 | -import com.sap.sailing.domain.common.dto.CompetitorDTO;
|
|
| 55 | -import com.sap.sailing.domain.common.dto.FleetDTO;
|
|
| 56 | -import com.sap.sailing.domain.common.dto.LeaderboardDTO;
|
|
| 57 | -import com.sap.sailing.domain.common.dto.LeaderboardEntryDTO;
|
|
| 58 | -import com.sap.sailing.domain.common.dto.LeaderboardRowDTO;
|
|
| 59 | -import com.sap.sailing.domain.common.dto.LegEntryDTO;
|
|
| 60 | -import com.sap.sailing.domain.common.dto.RaceDTO;
|
|
| 61 | -import com.sap.sailing.domain.common.tracking.GPSFixMoving;
|
|
| 62 | -import com.sap.sailing.domain.leaderboard.Leaderboard;
|
|
| 63 | -import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher;
|
|
| 64 | -import com.sap.sailing.domain.leaderboard.ResultDiscardingRule;
|
|
| 65 | -import com.sap.sailing.domain.leaderboard.ScoreCorrection.Result;
|
|
| 66 | -import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
|
|
| 67 | -import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
|
|
| 68 | -import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCache;
|
|
| 69 | -import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache;
|
|
| 70 | -import com.sap.sailing.domain.leaderboard.caching.LiveLeaderboardUpdater;
|
|
| 71 | -import com.sap.sailing.domain.racelog.RaceLogIdentifier;
|
|
| 72 | -import com.sap.sailing.domain.ranking.RankingMetric.CompetitorRankingInfo;
|
|
| 73 | -import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo;
|
|
| 74 | -import com.sap.sailing.domain.tracking.GPSFixTrack;
|
|
| 75 | -import com.sap.sailing.domain.tracking.Maneuver;
|
|
| 76 | -import com.sap.sailing.domain.tracking.MarkPassing;
|
|
| 77 | -import com.sap.sailing.domain.tracking.MarkPassingManeuver;
|
|
| 78 | -import com.sap.sailing.domain.tracking.RaceChangeListener;
|
|
| 79 | -import com.sap.sailing.domain.tracking.TrackedLeg;
|
|
| 80 | -import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
|
|
| 81 | -import com.sap.sailing.domain.tracking.TrackedRace;
|
|
| 82 | -import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
|
|
| 83 | -import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache;
|
|
| 84 | -import com.sap.sailing.domain.tracking.WindPositionMode;
|
|
| 85 | -import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
|
|
| 86 | -import com.sap.sailing.util.impl.RaceColumnListeners;
|
|
| 87 | -import com.sap.sse.common.Duration;
|
|
| 88 | -import com.sap.sse.common.TimePoint;
|
|
| 89 | -import com.sap.sse.common.Util;
|
|
| 90 | -import com.sap.sse.common.impl.MillisecondsTimePoint;
|
|
| 91 | -import com.sap.sse.concurrent.LockUtil;
|
|
| 92 | -import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
|
|
| 93 | -import com.sap.sse.util.impl.ThreadFactoryWithPriority;
|
|
| 94 | -
|
|
| 95 | -/**
|
|
| 96 | - * Base implementation for various types of leaderboards. The {@link RaceColumnListener} implementation forwards events
|
|
| 97 | - * received to all {@link RaceColumnListener} subscribed with this leaderboard. To which objects this leaderboard subscribes
|
|
| 98 | - * as {@link RaceColumnListener} is left to the concrete subclasses to implement, but the race columns seem like useful
|
|
| 99 | - * candidates.
|
|
| 100 | - *
|
|
| 101 | - * @author Axel Uhl (d043530)
|
|
| 102 | - *
|
|
| 103 | - */
|
|
| 104 | -public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, RaceColumnListener {
|
|
| 105 | - private static final long serialVersionUID = 330156778603279333L;
|
|
| 106 | -
|
|
| 107 | - private static final Logger logger = Logger.getLogger(AbstractSimpleLeaderboardImpl.class.getName());
|
|
| 108 | -
|
|
| 109 | - static final Double DOUBLE_0 = new Double(0);
|
|
| 110 | -
|
|
| 111 | - private final SettableScoreCorrection scoreCorrection;
|
|
| 112 | -
|
|
| 113 | - private ThresholdBasedResultDiscardingRule crossLeaderboardResultDiscardingRule;
|
|
| 114 | -
|
|
| 115 | - /**
|
|
| 116 | - * The optional display name mappings for competitors. This allows a user to override the tracking-provided
|
|
| 117 | - * competitor names for display in a leaderboard.
|
|
| 118 | - */
|
|
| 119 | - private final Map<Competitor, String> displayNames;
|
|
| 120 | -
|
|
| 121 | - /** the display name of the leaderboard */
|
|
| 122 | - private String displayName;
|
|
| 123 | -
|
|
| 124 | - /**
|
|
| 125 | - * Backs the {@link #getCarriedPoints(Competitor)} API with data. Can be used to prime this leaderboard
|
|
| 126 | - * with aggregated results of races not tracked / displayed by this leaderboard in detail. The points
|
|
| 127 | - * provided by this map are considered by {@link #getTotalPoints(Competitor, TimePoint)}.
|
|
| 128 | - */
|
|
| 129 | - private final Map<Competitor, Double> carriedPoints;
|
|
| 130 | -
|
|
| 131 | - private final RaceColumnListeners raceColumnListeners;
|
|
| 132 | -
|
|
| 133 | - /**
|
|
| 134 | - * A set that manages the difference between {@link #getCompetitors()} and {@link #getAllCompetitors()}. Access
|
|
| 135 | - * is controlled by the {@link #suppressedCompetitorsLock} lock.
|
|
| 136 | - */
|
|
| 137 | - private final Set<Competitor> suppressedCompetitors;
|
|
| 138 | - private final NamedReentrantReadWriteLock suppressedCompetitorsLock;
|
|
| 139 | -
|
|
| 140 | - private transient Map<com.sap.sse.common.Util.Pair<TrackedRace, Competitor>, RunnableFuture<RaceDetails>> raceDetailsAtEndOfTrackingCache;
|
|
| 141 | -
|
|
| 142 | - /**
|
|
| 143 | - * This executor needs to be a different one than {@link #executor} because the tasks run by {@link #executor}
|
|
| 144 | - * can depend on the results of the tasks run by {@link #raceDetailsExecutor}, and an {@link Executor} doesn't
|
|
| 145 | - * move a task that is blocked by waiting for another {@link FutureTask} to the side but blocks permanently,
|
|
| 146 | - * ending in a deadlock (one that cannot easily be detected by the Eclipse debugger either).
|
|
| 147 | - */
|
|
| 148 | - private transient Executor raceDetailsExecutor;
|
|
| 149 | -
|
|
| 150 | - /**
|
|
| 151 | - * Used to remove all these listeners from their tracked races when this servlet is {@link #destroy() destroyed}.
|
|
| 152 | - */
|
|
| 153 | - private transient Set<CacheInvalidationListener> cacheInvalidationListeners;
|
|
| 154 | -
|
|
| 155 | - private transient ThreadPoolExecutor executor;
|
|
| 156 | -
|
|
| 157 | - private transient LiveLeaderboardUpdater liveLeaderboardUpdater;
|
|
| 158 | -
|
|
| 159 | - private transient LeaderboardDTOCache leaderboardDTOCache;
|
|
| 160 | -
|
|
| 161 | - /**
|
|
| 162 | - * A leaderboard entry representing a snapshot of a cell at a given time point for a single race/competitor.
|
|
| 163 | - *
|
|
| 164 | - * @author Axel Uhl (d043530)
|
|
| 165 | - *
|
|
| 166 | - */
|
|
| 167 | - class EntryImpl implements Entry {
|
|
| 168 | - private final Callable<Integer> trackedRankProvider;
|
|
| 169 | - private final Double netPoints;
|
|
| 170 | - private final Callable<Double> netPointsUncorrectedProvider;
|
|
| 171 | - private final boolean isNetPointsCorrected;
|
|
| 172 | - private final Double totalPoints;
|
|
| 173 | - private final MaxPointsReason maxPointsReason;
|
|
| 174 | - private final boolean discarded;
|
|
| 175 | - private final Fleet fleet;
|
|
| 176 | -
|
|
| 177 | - private EntryImpl(Callable<Integer> trackedRankProvider, Double netPoints,
|
|
| 178 | - Callable<Double> netPointsUncorrectedProvider, boolean isNetPointsCorrected,
|
|
| 179 | - Double totalPoints, MaxPointsReason maxPointsReason, boolean discarded, Fleet fleet) {
|
|
| 180 | - super();
|
|
| 181 | - this.trackedRankProvider = trackedRankProvider;
|
|
| 182 | - this.netPoints = netPoints;
|
|
| 183 | - this.netPointsUncorrectedProvider = netPointsUncorrectedProvider;
|
|
| 184 | - this.isNetPointsCorrected = isNetPointsCorrected;
|
|
| 185 | - this.totalPoints = totalPoints;
|
|
| 186 | - this.maxPointsReason = maxPointsReason;
|
|
| 187 | - this.discarded = discarded;
|
|
| 188 | - this.fleet = fleet;
|
|
| 189 | - }
|
|
| 190 | - @Override
|
|
| 191 | - public int getTrackedRank() {
|
|
| 192 | - try {
|
|
| 193 | - return trackedRankProvider.call();
|
|
| 194 | - } catch (Exception e) {
|
|
| 195 | - throw new RuntimeException(e);
|
|
| 196 | - }
|
|
| 197 | - }
|
|
| 198 | - @Override
|
|
| 199 | - public Double getNetPoints() {
|
|
| 200 | - return netPoints;
|
|
| 201 | - }
|
|
| 202 | - @Override
|
|
| 203 | - public boolean isNetPointsCorrected() {
|
|
| 204 | - return isNetPointsCorrected;
|
|
| 205 | - }
|
|
| 206 | - @Override
|
|
| 207 | - public Double getTotalPoints() {
|
|
| 208 | - return totalPoints;
|
|
| 209 | - }
|
|
| 210 | - @Override
|
|
| 211 | - public MaxPointsReason getMaxPointsReason() {
|
|
| 212 | - return maxPointsReason;
|
|
| 213 | - }
|
|
| 214 | - @Override
|
|
| 215 | - public boolean isDiscarded() {
|
|
| 216 | - return discarded;
|
|
| 217 | - }
|
|
| 218 | - @Override
|
|
| 219 | - public Fleet getFleet() {
|
|
| 220 | - return fleet;
|
|
| 221 | - }
|
|
| 222 | - @Override
|
|
| 223 | - public Double getNetPointsUncorrected() {
|
|
| 224 | - try {
|
|
| 225 | - return netPointsUncorrectedProvider.call();
|
|
| 226 | - } catch (Exception e) {
|
|
| 227 | - throw new RuntimeException(e);
|
|
| 228 | - }
|
|
| 229 | - }
|
|
| 230 | - }
|
|
| 231 | -
|
|
| 232 | - /**
|
|
| 233 | - * Computing the competitors can be a bit expensive, particularly if the fleet is large and there may be suppressed
|
|
| 234 | - * competitors, and the leaderboard may be a meta-leaderboard that refers to other leaderboards which each have
|
|
| 235 | - * several tracked races attached from where the competitors need to be retrieved. Ideally, the competitors list
|
|
| 236 | - * would be cached, but that is again difficult because we would have to monitor all changes in all dependent
|
|
| 237 | - * leaderboards and columns and tracked races properly.
|
|
| 238 | - * <p>
|
|
| 239 | - *
|
|
| 240 | - * As it turns out, one of the most frequent uses of the {@link AbstractSimpleLeaderboardImpl#getCompetitors}
|
|
| 241 | - * competitors list is to determine their number which in turn is only required for high-point scoring systems and
|
|
| 242 | - * for computing the default score for penalties. Again, the most frequently used low-point family of scoring schemes
|
|
| 243 | - * does not require this number. Yet, the scoring scheme requires an argument for polymorphic use by those that
|
|
| 244 | - * need it. Instead of computing it for each call, this interface lets us defer the actual calculation until the
|
|
| 245 | - * point when it's really needed. Once asked, this object will cache the result. Therefore, a new one should be
|
|
| 246 | - * constructed each time the number shall be computed.
|
|
| 247 | - *
|
|
| 248 | - * @author Axel Uhl (D043530)
|
|
| 249 | - *
|
|
| 250 | - */
|
|
| 251 | - public class NumberOfCompetitorsFetcherImpl implements NumberOfCompetitorsInLeaderboardFetcher {
|
|
| 252 | - private int numberOfCompetitors = -1;
|
|
| 253 | -
|
|
| 254 | - @Override
|
|
| 255 | - public int getNumberOfCompetitorsInLeaderboard() {
|
|
| 256 | - if (numberOfCompetitors == -1) {
|
|
| 257 | - numberOfCompetitors = Util.size(getCompetitors());
|
|
| 258 | - }
|
|
| 259 | - return numberOfCompetitors;
|
|
| 260 | - }
|
|
| 261 | - }
|
|
| 262 | -
|
|
| 263 | - /**
|
|
| 264 | - * Handles the invalidation of the {@link SailingServiceImpl#raceDetailsAtEndOfTrackingCache} entries if the tracked
|
|
| 265 | - * race changes in any way. In particular, for {@link #statusChanged}, when the status changes away from LOADING,
|
|
| 266 | - * calculations may start or resume, making it necessary to clear the cache.
|
|
| 267 | - *
|
|
| 268 | - * @author Axel Uhl (D043530)
|
|
| 269 | - *
|
|
| 270 | - */
|
|
| 271 | - private class CacheInvalidationListener extends AbstractRaceChangeListener {
|
|
| 272 | - private final TrackedRace trackedRace;
|
|
| 273 | - private final Competitor competitor;
|
|
| 274 | -
|
|
| 275 | - public CacheInvalidationListener(TrackedRace trackedRace, Competitor competitor) {
|
|
| 276 | - this.trackedRace = trackedRace;
|
|
| 277 | - this.competitor = competitor;
|
|
| 278 | - }
|
|
| 279 | -
|
|
| 280 | - public TrackedRace getTrackedRace() {
|
|
| 281 | - return trackedRace;
|
|
| 282 | - }
|
|
| 283 | -
|
|
| 284 | - public void removeFromTrackedRace() {
|
|
| 285 | - trackedRace.removeListener(this);
|
|
| 286 | - }
|
|
| 287 | -
|
|
| 288 | - private void invalidateCacheAndRemoveThisListenerFromTrackedRace() {
|
|
| 289 | - synchronized (raceDetailsAtEndOfTrackingCache) {
|
|
| 290 | - raceDetailsAtEndOfTrackingCache.remove(new com.sap.sse.common.Util.Pair<TrackedRace, Competitor>(trackedRace, competitor));
|
|
| 291 | - removeFromTrackedRace();
|
|
| 292 | - }
|
|
| 293 | - }
|
|
| 294 | -
|
|
| 295 | - @Override
|
|
| 296 | - protected void defaultAction() {
|
|
| 297 | - invalidateCacheAndRemoveThisListenerFromTrackedRace();
|
|
| 298 | - }
|
|
| 299 | - }
|
|
| 300 | -
|
|
| 301 | - private static class UUIDGenerator implements LeaderboardDTO.UUIDGenerator {
|
|
| 302 | - @Override
|
|
| 303 | - public String generateRandomUUID() {
|
|
| 304 | - return UUID.randomUUID().toString();
|
|
| 305 | - }
|
|
| 306 | - }
|
|
| 307 | -
|
|
| 308 | - public AbstractSimpleLeaderboardImpl(ThresholdBasedResultDiscardingRule resultDiscardingRule) {
|
|
| 309 | - this.carriedPoints = new HashMap<Competitor, Double>();
|
|
| 310 | - this.scoreCorrection = createScoreCorrection();
|
|
| 311 | - this.displayNames = new HashMap<Competitor, String>();
|
|
| 312 | - this.crossLeaderboardResultDiscardingRule = resultDiscardingRule;
|
|
| 313 | - this.suppressedCompetitors = new HashSet<Competitor>();
|
|
| 314 | - this.suppressedCompetitorsLock = new NamedReentrantReadWriteLock("suppressedCompetitorsLock", /* fair */ false);
|
|
| 315 | - this.raceColumnListeners = new RaceColumnListeners();
|
|
| 316 | - this.raceDetailsAtEndOfTrackingCache = new HashMap<com.sap.sse.common.Util.Pair<TrackedRace, Competitor>, RunnableFuture<RaceDetails>>();
|
|
| 317 | - initTransientFields();
|
|
| 318 | - }
|
|
| 319 | -
|
|
| 320 | - /**
|
|
| 321 | - * Produces the score correction object to use in this leaderboard. Used by the constructor. Subclasses may override
|
|
| 322 | - * this method to create a more specific type of score correction. This implementation produces an object of type
|
|
| 323 | - * {@link ScoreCorrectionImpl}.
|
|
| 324 | - */
|
|
| 325 | - protected SettableScoreCorrection createScoreCorrection() {
|
|
| 326 | - return new ScoreCorrectionImpl(this);
|
|
| 327 | - }
|
|
| 328 | -
|
|
| 329 | - private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
|
|
| 330 | - ois.defaultReadObject();
|
|
| 331 | - initTransientFields();
|
|
| 332 | - }
|
|
| 333 | -
|
|
| 334 | - private void initTransientFields() {
|
|
| 335 | - this.raceDetailsAtEndOfTrackingCache = new HashMap<com.sap.sse.common.Util.Pair<TrackedRace,Competitor>, RunnableFuture<RaceDetails>>();
|
|
| 336 | - this.raceDetailsExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
|
| 337 | - this.cacheInvalidationListeners = new HashSet<CacheInvalidationListener>();
|
|
| 338 | - // When many updates are triggered in a short period of time by a single thread, ensure that the single thread
|
|
| 339 | - // providing the updates is not outperformed by all the re-calculations happening here. Leave at least one
|
|
| 340 | - // core to other things, but by using at least three threads ensure that no simplistic deadlocks may occur.
|
|
| 341 | - final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors(), 3);
|
|
| 342 | - executor = new ThreadPoolExecutor(/* corePoolSize */ THREAD_POOL_SIZE,
|
|
| 343 | - /* maximumPoolSize */ THREAD_POOL_SIZE,
|
|
| 344 | - /* keepAliveTime */ 60, TimeUnit.SECONDS,
|
|
| 345 | - /* workQueue */ new LinkedBlockingQueue<Runnable>(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY-1, /* daemon */ true));
|
|
| 346 | - }
|
|
| 347 | -
|
|
| 348 | - @Override
|
|
| 349 | - public void destroy() {
|
|
| 350 | - for (CacheInvalidationListener cacheInvalidationListener : cacheInvalidationListeners) {
|
|
| 351 | - cacheInvalidationListener.removeFromTrackedRace();
|
|
| 352 | - }
|
|
| 353 | - }
|
|
| 354 | -
|
|
| 355 | - @Override
|
|
| 356 | - public SettableScoreCorrection getScoreCorrection() {
|
|
| 357 | - return scoreCorrection;
|
|
| 358 | - }
|
|
| 359 | -
|
|
| 360 | - @Override
|
|
| 361 | - public String getDisplayName(Competitor competitor) {
|
|
| 362 | - return displayNames.get(competitor);
|
|
| 363 | - }
|
|
| 364 | -
|
|
| 365 | - @Override
|
|
| 366 | - public String getDisplayName() {
|
|
| 367 | - return displayName;
|
|
| 368 | - }
|
|
| 369 | -
|
|
| 370 | - @Override
|
|
| 371 | - public void setDisplayName(String displayName) {
|
|
| 372 | - this.displayName = displayName;
|
|
| 373 | - }
|
|
| 374 | -
|
|
| 375 | - @Override
|
|
| 376 | - public ResultDiscardingRule getResultDiscardingRule() {
|
|
| 377 | - return crossLeaderboardResultDiscardingRule;
|
|
| 378 | - }
|
|
| 379 | -
|
|
| 380 | - @Override
|
|
| 381 | - public void setCarriedPoints(Competitor competitor, double carriedPoints) {
|
|
| 382 | - Double oldCarriedPoints = this.carriedPoints.put(competitor, carriedPoints);
|
|
| 383 | - if (!Util.equalsWithNull(oldCarriedPoints, carriedPoints)) {
|
|
| 384 | - getScoreCorrection().notifyListenersAboutCarriedPointsChange(competitor, oldCarriedPoints, carriedPoints);
|
|
| 385 | - }
|
|
| 386 | - }
|
|
| 387 | -
|
|
| 388 | - @Override
|
|
| 389 | - public double getCarriedPoints(Competitor competitor) {
|
|
| 390 | - Double result = carriedPoints.get(competitor);
|
|
| 391 | - return result == null ? 0 : result;
|
|
| 392 | - }
|
|
| 393 | -
|
|
| 394 | - @Override
|
|
| 395 | - public Map<Competitor, Double> getCompetitorsForWhichThereAreCarriedPoints() {
|
|
| 396 | - return Collections.unmodifiableMap(carriedPoints);
|
|
| 397 | - }
|
|
| 398 | -
|
|
| 399 | - @Override
|
|
| 400 | - public void unsetCarriedPoints(Competitor competitor) {
|
|
| 401 | - Double oldCarriedPoints = carriedPoints.remove(competitor);
|
|
| 402 | - if (oldCarriedPoints != null) {
|
|
| 403 | - getScoreCorrection().notifyListenersAboutCarriedPointsChange(competitor, oldCarriedPoints, null);
|
|
| 404 | - }
|
|
| 405 | - }
|
|
| 406 | -
|
|
| 407 | - @Override
|
|
| 408 | - public boolean hasCarriedPoints() {
|
|
| 409 | - return !carriedPoints.isEmpty();
|
|
| 410 | - }
|
|
| 411 | -
|
|
| 412 | - @Override
|
|
| 413 | - public boolean hasCarriedPoints(Competitor competitor) {
|
|
| 414 | - return carriedPoints.containsKey(competitor);
|
|
| 415 | - }
|
|
| 416 | -
|
|
| 417 | - @Override
|
|
| 418 | - public void setDisplayName(Competitor competitor, String displayName) {
|
|
| 419 | - String oldDisplayName = displayNames.get(competitor);
|
|
| 420 | - displayNames.put(competitor, displayName);
|
|
| 421 | - if (!Util.equalsWithNull(oldDisplayName, displayName)) {
|
|
| 422 | - getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName);
|
|
| 423 | - }
|
|
| 424 | - }
|
|
| 425 | -
|
|
| 426 | - @Override
|
|
| 427 | - public void setCrossLeaderboardResultDiscardingRule(ThresholdBasedResultDiscardingRule discardingRule) {
|
|
| 428 | - ResultDiscardingRule oldDiscardingRule = getResultDiscardingRule();
|
|
| 429 | - this.crossLeaderboardResultDiscardingRule = discardingRule;
|
|
| 430 | - getRaceColumnListeners().notifyListenersAboutResultDiscardingRuleChanged(oldDiscardingRule, discardingRule);
|
|
| 431 | - }
|
|
| 432 | -
|
|
| 433 | - @Override
|
|
| 434 | - public Double getNetPoints(final Competitor competitor, final RaceColumn raceColumn, final TimePoint timePoint) {
|
|
| 435 | - return getScoreCorrection().getCorrectedScore(
|
|
| 436 | - new Callable<Integer>() {
|
|
| 437 | - public Integer call() throws NoWindException {
|
|
| 438 | - return getTrackedRank(competitor, raceColumn, timePoint);
|
|
| 439 | - }
|
|
| 440 | - }, competitor,
|
|
| 441 | - raceColumn, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()).getCorrectedScore();
|
|
| 442 | - }
|
|
| 443 | -
|
|
| 444 | - @Override
|
|
| 445 | - public MaxPointsReason getMaxPointsReason(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) {
|
|
| 446 | - return getScoreCorrection().getMaxPointsReason(competitor, raceColumn, timePoint);
|
|
| 447 | - }
|
|
| 448 | -
|
|
| 449 | - @Override
|
|
| 450 | - public boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) {
|
|
| 451 | - return isDiscarded(competitor, raceColumn, getRaceColumns(), timePoint);
|
|
| 452 | - }
|
|
| 453 | -
|
|
| 454 | - private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn,
|
|
| 455 | - Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) {
|
|
| 456 | - final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule()
|
|
| 457 | - .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint);
|
|
| 458 | - return isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns);
|
|
| 459 | - }
|
|
| 460 | -
|
|
| 461 | - /**
|
|
| 462 | - * Same as {@link #isDiscarded(Competitor, RaceColumn, TimePoint)}, only that the set of discarded race columns can
|
|
| 463 | - * be specified which is useful when total points are to be computed for more than one column for the same
|
|
| 464 | - * competitor because then the calculation of discards (which requires looking at all columns) only needs to be done
|
|
| 465 | - * once and not again for each column (which would lead to quadratic effort).
|
|
| 466 | - *
|
|
| 467 | - * @param discardedRaceColumns
|
|
| 468 | - * expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}.
|
|
| 469 | - * {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint)
|
|
| 470 | - * getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}.
|
|
| 471 | - */
|
|
| 472 | - private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint,
|
|
| 473 | - final Set<RaceColumn> discardedRaceColumns) {
|
|
| 474 | - return !raceColumn.isMedalRace()
|
|
| 475 | - && getMaxPointsReason(competitor, raceColumn, timePoint).isDiscardable()
|
|
| 476 | - && discardedRaceColumns.contains(raceColumn);
|
|
| 477 | - }
|
|
| 478 | -
|
|
| 479 | - @Override
|
|
| 480 | - public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) throws NoWindException {
|
|
| 481 | - return getTotalPoints(competitor, raceColumn, getRaceColumns(), timePoint);
|
|
| 482 | - }
|
|
| 483 | -
|
|
| 484 | - @Override
|
|
| 485 | - public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn,
|
|
| 486 | - Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) throws NoWindException {
|
|
| 487 | - final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule()
|
|
| 488 | - .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint);
|
|
| 489 | - return getTotalPoints(competitor, raceColumn, timePoint, discardedRaceColumns);
|
|
| 490 | - }
|
|
| 491 | -
|
|
| 492 | - /**
|
|
| 493 | - * Same as {@link #getTotalPoints(Competitor, RaceColumn, Iterable, TimePoint)}, only that the set of discarded race columns can
|
|
| 494 | - * be specified which is useful when total points are to be computed for more than one column for the same
|
|
| 495 | - * competitor because then the calculation of discards (which requires looking at all columns) only needs to be done
|
|
| 496 | - * once and not again for each column (which would lead to quadratic effort).
|
|
| 497 | - *
|
|
| 498 | - * @param discardedRaceColumns
|
|
| 499 | - * expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}.
|
|
| 500 | - * {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint)
|
|
| 501 | - * getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}.
|
|
| 502 | - */
|
|
| 503 | - @Override
|
|
| 504 | - public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, Set<RaceColumn> discardedRaceColumns) {
|
|
| 505 | - Double result;
|
|
| 506 | - if (isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns)) {
|
|
| 507 | - result = 0.0;
|
|
| 508 | - } else {
|
|
| 509 | - final Double netPoints = getNetPoints(competitor, raceColumn, timePoint);
|
|
| 510 | - if (netPoints == null) {
|
|
| 511 | - result = null;
|
|
| 512 | - } else {
|
|
| 513 | - result = raceColumn.getFactor() * netPoints;
|
|
| 514 | - }
|
|
| 515 | - }
|
|
| 516 | - return result;
|
|
| 517 | - }
|
|
| 518 | -
|
|
| 519 | - @Override
|
|
| 520 | - public Double getTotalPoints(Competitor competitor, TimePoint timePoint) {
|
|
| 521 | - return getTotalPoints(competitor, getRaceColumns(), timePoint);
|
|
| 522 | - }
|
|
| 523 | -
|
|
| 524 | - @Override
|
|
| 525 | - public Double getTotalPoints(Competitor competitor, final Iterable<RaceColumn> raceColumnsToConsider,
|
|
| 526 | - TimePoint timePoint) {
|
|
| 527 | - // when a column with isStartsWithZeroScore() is found, only reset score if the competitor scored in any race from there on
|
|
| 528 | - boolean needToResetScoreUponNextNonEmptyEntry = false;
|
|
| 529 | - double result = getCarriedPoints(competitor);
|
|
| 530 | - final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule()
|
|
| 531 | - .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint);
|
|
| 532 | - for (RaceColumn raceColumn : raceColumnsToConsider) {
|
|
| 533 | - if (raceColumn.isStartsWithZeroScore()) {
|
|
| 534 | - needToResetScoreUponNextNonEmptyEntry = true;
|
|
| 535 | - }
|
|
| 536 | - if (getScoringScheme().isValidInTotalScore(this, raceColumn, competitor, timePoint)) {
|
|
| 537 | - final Double totalPoints = getTotalPoints(competitor, raceColumn, timePoint, discardedRaceColumns);
|
|
| 538 | - if (totalPoints != null) {
|
|
| 539 | - if (needToResetScoreUponNextNonEmptyEntry) {
|
|
| 540 | - result = 0;
|
|
| 541 | - needToResetScoreUponNextNonEmptyEntry = false;
|
|
| 542 | - }
|
|
| 543 | - result += totalPoints;
|
|
| 544 | - }
|
|
| 545 | - }
|
|
| 546 | - }
|
|
| 547 | - return result;
|
|
| 548 | - }
|
|
| 549 | -
|
|
| 550 | - /**
|
|
| 551 | - * All competitors with non-<code>null</code> net points are added to the result which is then sorted by net points in ascending
|
|
| 552 | - * order. The fleet, if ordered, is the primary ordering criterion, followed by the net points.
|
|
| 553 | - */
|
|
| 554 | - @Override
|
|
| 555 | - public List<Competitor> getCompetitorsFromBestToWorst(final RaceColumn raceColumn, TimePoint timePoint) throws NoWindException {
|
|
| 556 | - final Map<Competitor, com.sap.sse.common.Util.Pair<Double, Fleet>> netPointsAndFleet = new HashMap<Competitor, com.sap.sse.common.Util.Pair<Double, Fleet>>();
|
|
| 557 | - for (Competitor competitor : getCompetitors()) {
|
|
| 558 | - Double netPoints = getNetPoints(competitor, raceColumn, timePoint);
|
|
| 559 | - if (netPoints != null) {
|
|
| 560 | - netPointsAndFleet.put(competitor, new com.sap.sse.common.Util.Pair<Double, Fleet>(netPoints, raceColumn.getFleetOfCompetitor(competitor)));
|
|
| 561 | - }
|
|
| 562 | - }
|
|
| 563 | - List<Competitor> result = new ArrayList<Competitor>(netPointsAndFleet.keySet());
|
|
| 564 | - Collections.sort(result, new Comparator<Competitor>() {
|
|
| 565 | - @Override
|
|
| 566 | - public int compare(Competitor o1, Competitor o2) {
|
|
| 567 | - int comparisonResult;
|
|
| 568 | - if (o1 == o2) {
|
|
| 569 | - comparisonResult = 0;
|
|
| 570 | - } else {
|
|
| 571 | - if (raceColumn.hasSplitFleets() && !raceColumn.hasSplitFleetContiguousScoring()) {
|
|
| 572 | - // only check fleets if there are more than one and the column is not to be contiguously scored even in case
|
|
| 573 | - // of split fleets
|
|
| 574 | - final Fleet o1Fleet = netPointsAndFleet.get(o1).getB();
|
|
| 575 | - final Fleet o2Fleet = netPointsAndFleet.get(o2).getB();
|
|
| 576 | - if (o1Fleet == null) {
|
|
| 577 | - if (o2Fleet == null) {
|
|
| 578 | - comparisonResult = 0;
|
|
| 579 | - } else {
|
|
| 580 | - comparisonResult = 1; // o1 ranks "worse" because it doesn't have a fleet set while o2
|
|
| 581 | - // has
|
|
| 582 | - }
|
|
| 583 | - } else {
|
|
| 584 | - if (o2Fleet == null) {
|
|
| 585 | - comparisonResult = -1; // o1 ranks "better" because it has a fleet set while o2 hasn't
|
|
| 586 | - } else {
|
|
| 587 | - comparisonResult = o1Fleet.compareTo(o2Fleet);
|
|
| 588 | - }
|
|
| 589 | - }
|
|
| 590 | - } else {
|
|
| 591 | - // either there are no split fleets or the split isn't relevant for scoring as for ordered fleets
|
|
| 592 | - // the scoring runs contiguously from top to bottom
|
|
| 593 | - comparisonResult = 0;
|
|
| 594 | - }
|
|
| 595 | - if (comparisonResult == 0) {
|
|
| 596 | - comparisonResult = getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare(
|
|
| 597 | - netPointsAndFleet.get(o1).getA(), netPointsAndFleet.get(o2).getA());
|
|
| 598 | - }
|
|
| 599 | - }
|
|
| 600 | - return comparisonResult;
|
|
| 601 | - }
|
|
| 602 | - });
|
|
| 603 | - return result;
|
|
| 604 | - }
|
|
| 605 | -
|
|
| 606 | - @Override
|
|
| 607 | - public List<Competitor> getCompetitorsFromBestToWorst(TimePoint timePoint) {
|
|
| 608 | - return getCompetitorsFromBestToWorst(getRaceColumns(), timePoint);
|
|
| 609 | - }
|
|
| 610 | -
|
|
| 611 | - private List<Competitor> getCompetitorsFromBestToWorst(Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) {
|
|
| 612 | - List<Competitor> result = new ArrayList<Competitor>();
|
|
| 613 | - for (Competitor competitor : getCompetitors()) {
|
|
| 614 | - result.add(competitor);
|
|
| 615 | - }
|
|
| 616 | - Collections.sort(result, getTotalRankComparator(raceColumnsToConsider, timePoint));
|
|
| 617 | - return result;
|
|
| 618 | - }
|
|
| 619 | -
|
|
| 620 | - @Override
|
|
| 621 | - public int getTotalRankOfCompetitor(Competitor competitor, TimePoint timePoint) throws NoWindException {
|
|
| 622 | - List<Competitor> competitorsFromBestToWorst = getCompetitorsFromBestToWorst(timePoint);
|
|
| 623 | - return competitorsFromBestToWorst.indexOf(competitor) + 1;
|
|
| 624 | - }
|
|
| 625 | -
|
|
| 626 | - protected Comparator<? super Competitor> getTotalRankComparator(Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) {
|
|
| 627 | - return new LeaderboardTotalRankComparator(this, timePoint, getScoringScheme(), /* nullScoresAreBetter */ false, raceColumnsToConsider);
|
|
| 628 | - }
|
|
| 629 | -
|
|
| 630 | - @Override
|
|
| 631 | - public RaceColumn getRaceColumnByName(String columnName) {
|
|
| 632 | - RaceColumn result = null;
|
|
| 633 | - for (RaceColumn r : getRaceColumns()) {
|
|
| 634 | - if (r.getName().equals(columnName)) {
|
|
| 635 | - result = r;
|
|
| 636 | - break;
|
|
| 637 | - }
|
|
| 638 | - }
|
|
| 639 | - return result;
|
|
| 640 | - }
|
|
| 641 | -
|
|
| 642 | - @Override
|
|
| 643 | - public Competitor getCompetitorByName(String competitorName) {
|
|
| 644 | - for (Competitor competitor : getAllCompetitors()) {
|
|
| 645 | - if (competitor.getName().equals(competitorName)) {
|
|
| 646 | - return competitor;
|
|
| 647 | - }
|
|
| 648 | - }
|
|
| 649 | - return null;
|
|
| 650 | - }
|
|
| 651 | -
|
|
| 652 | - @Override
|
|
| 653 | - public boolean countRaceForComparisonWithDiscardingThresholds(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) {
|
|
| 654 | - TrackedRace trackedRaceForCompetitorInColumn;
|
|
| 655 | - return getScoringScheme().isValidInTotalScore(this, raceColumn, competitor, timePoint) &&
|
|
| 656 | - (getScoreCorrection().isScoreCorrected(competitor, raceColumn, timePoint) ||
|
|
| 657 | - ((trackedRaceForCompetitorInColumn=raceColumn.getTrackedRace(competitor)) != null &&
|
|
| 658 | - trackedRaceForCompetitorInColumn.hasStarted(timePoint) &&
|
|
| 659 | - trackedRaceForCompetitorInColumn.getRank(competitor, timePoint) != 0));
|
|
| 660 | - }
|
|
| 661 | -
|
|
| 662 | - @Override
|
|
| 663 | - public void addRaceColumnListener(RaceColumnListener listener) {
|
|
| 664 | - getRaceColumnListeners().addRaceColumnListener(listener);
|
|
| 665 | - }
|
|
| 666 | -
|
|
| 667 | - @Override
|
|
| 668 | - public void removeRaceColumnListener(RaceColumnListener listener) {
|
|
| 669 | - getRaceColumnListeners().removeRaceColumnListener(listener);
|
|
| 670 | - }
|
|
| 671 | -
|
|
| 672 | - @Override
|
|
| 673 | - public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint) throws NoWindException {
|
|
| 674 | - final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint);
|
|
| 675 | - return getEntry(competitor, race, timePoint, discardedRaceColumns);
|
|
| 676 | - }
|
|
| 677 | -
|
|
| 678 | - @Override
|
|
| 679 | - public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint,
|
|
| 680 | - Set<RaceColumn> discardedRaceColumns) throws NoWindException {
|
|
| 681 | - Callable<Integer> trackedRankProvider = new Callable<Integer>() {
|
|
| 682 | - public Integer call() throws NoWindException {
|
|
| 683 | - return getTrackedRank(competitor, race, timePoint);
|
|
| 684 | - }
|
|
| 685 | - };
|
|
| 686 | - final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, race,
|
|
| 687 | - timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme());
|
|
| 688 | - boolean discarded = isDiscarded(competitor, race, timePoint, discardedRaceColumns);
|
|
| 689 | - final Double correctedScore = correctedResults.getCorrectedScore();
|
|
| 690 | - return new EntryImpl(trackedRankProvider, correctedScore, new Callable<Double>() {
|
|
| 691 | - @Override
|
|
| 692 | - public Double call() {
|
|
| 693 | - return correctedResults.getUncorrectedScore();
|
|
| 694 | - }
|
|
| 695 | - }, correctedResults.isCorrected(), discarded ? DOUBLE_0 : correctedScore == null ? null
|
|
| 696 | - : Double.valueOf(correctedScore * race.getFactor()), correctedResults.getMaxPointsReason(), discarded,
|
|
| 697 | - race.getFleetOfCompetitor(competitor));
|
|
| 698 | - }
|
|
| 699 | -
|
|
| 700 | - @Override
|
|
| 701 | - public Map<RaceColumn, List<Competitor>> getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(TimePoint timePoint) throws NoWindException {
|
|
| 702 | - Map<RaceColumn, List<Competitor>> result = new LinkedHashMap<>();
|
|
| 703 | - List<RaceColumn> raceColumnsToConsider = new ArrayList<>();
|
|
| 704 | - for (RaceColumn raceColumn : getRaceColumns()) {
|
|
| 705 | - raceColumnsToConsider.add(raceColumn);
|
|
| 706 | - result.put(raceColumn, getCompetitorsFromBestToWorst(raceColumnsToConsider, timePoint));
|
|
| 707 | - }
|
|
| 708 | - return result;
|
|
| 709 | - }
|
|
| 710 | -
|
|
| 711 | - @Override
|
|
| 712 | - public Map<RaceColumn, Map<Competitor, Double>> getTotalPointsSumAfterRaceColumn(final TimePoint timePoint)
|
|
| 713 | - throws NoWindException {
|
|
| 714 | - final Map<RaceColumn, Map<Competitor, Double>> result = new LinkedHashMap<>();
|
|
| 715 | - List<RaceColumn> raceColumnsToConsider = new ArrayList<>();
|
|
| 716 | - Map<RaceColumn, Future<Map<Competitor, Double>>> futures = new HashMap<>();
|
|
| 717 | - for (final RaceColumn raceColumn : getRaceColumns()) {
|
|
| 718 | - raceColumnsToConsider.add(raceColumn);
|
|
| 719 | - final Iterable<RaceColumn> finalRaceColumnsToConsider = new ArrayList<>(raceColumnsToConsider);
|
|
| 720 | - futures.put(raceColumn, executor.submit(new Callable<Map<Competitor, Double>>() {
|
|
| 721 | - @Override
|
|
| 722 | - public Map<Competitor, Double> call() {
|
|
| 723 | - Map<Competitor, Double> totalPointsSumPerCompetitorInColumn = new HashMap<>();
|
|
| 724 | - for (Competitor competitor : getCompetitors()) {
|
|
| 725 | - totalPointsSumPerCompetitorInColumn.put(competitor, getTotalPoints(competitor, finalRaceColumnsToConsider, timePoint));
|
|
| 726 | - }
|
|
| 727 | - synchronized (result) {
|
|
| 728 | - return totalPointsSumPerCompetitorInColumn;
|
|
| 729 | - }
|
|
| 730 | - }
|
|
| 731 | - }));
|
|
| 732 | - }
|
|
| 733 | - for (RaceColumn raceColumn : getRaceColumns()) {
|
|
| 734 | - try {
|
|
| 735 | - result.put(raceColumn, futures.get(raceColumn).get());
|
|
| 736 | - } catch (InterruptedException | ExecutionException e) {
|
|
| 737 | - if (e.getCause() instanceof NoWindError) {
|
|
| 738 | - throw ((NoWindError) e.getCause()).getCause();
|
|
| 739 | - } else {
|
|
| 740 | - throw new RuntimeException(e); // no caught exceptions occur in the futures executed
|
|
| 741 | - }
|
|
| 742 | - }
|
|
| 743 | - }
|
|
| 744 | - return result;
|
|
| 745 | - }
|
|
| 746 | -
|
|
| 747 | - @Override
|
|
| 748 | - public Map<com.sap.sse.common.Util.Pair<Competitor, RaceColumn>, Entry> getContent(final TimePoint timePoint) throws NoWindException {
|
|
| 749 | - Map<com.sap.sse.common.Util.Pair<Competitor, RaceColumn>, Entry> result = new HashMap<com.sap.sse.common.Util.Pair<Competitor, RaceColumn>, Entry>();
|
|
| 750 | - Map<Competitor, Set<RaceColumn>> discardedRaces = new HashMap<Competitor, Set<RaceColumn>>();
|
|
| 751 | - for (final RaceColumn raceColumn : getRaceColumns()) {
|
|
| 752 | - for (final Competitor competitor : getCompetitors()) {
|
|
| 753 | - Callable<Integer> trackedRankProvider = new Callable<Integer>() {
|
|
| 754 | - @Override
|
|
| 755 | - public Integer call() throws Exception {
|
|
| 756 | - return getTrackedRank(competitor, raceColumn, timePoint);
|
|
| 757 | - }
|
|
| 758 | - };
|
|
| 759 | - final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, raceColumn,
|
|
| 760 | - timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme());
|
|
| 761 | - Set<RaceColumn> discardedRacesForCompetitor = discardedRaces.get(competitor);
|
|
| 762 | - if (discardedRacesForCompetitor == null) {
|
|
| 763 | - discardedRacesForCompetitor = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint);
|
|
| 764 | - discardedRaces.put(competitor, discardedRacesForCompetitor);
|
|
| 765 | - }
|
|
| 766 | - boolean discarded = discardedRacesForCompetitor.contains(raceColumn);
|
|
| 767 | - final Double correctedScore = correctedResults.getCorrectedScore();
|
|
| 768 | - Entry entry = new EntryImpl(trackedRankProvider, correctedScore,
|
|
| 769 | - new Callable<Double>() { @Override public Double call() { return correctedResults.getUncorrectedScore(); } },
|
|
| 770 | - correctedResults.isCorrected(),
|
|
| 771 | - discarded ? DOUBLE_0 : (correctedScore==null?null:
|
|
| 772 | - Double.valueOf((correctedScore * raceColumn.getFactor()))), correctedResults.getMaxPointsReason(),
|
|
| 773 | - discarded, raceColumn.getFleetOfCompetitor(competitor));
|
|
| 774 | - result.put(new com.sap.sse.common.Util.Pair<Competitor, RaceColumn>(competitor, raceColumn), entry);
|
|
| 775 | - }
|
|
| 776 | - }
|
|
| 777 | - return result;
|
|
| 778 | - }
|
|
| 779 | -
|
|
| 780 | - @Override
|
|
| 781 | - public void trackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
|
|
| 782 | - getRaceColumnListeners().notifyListenersAboutTrackedRaceLinked(raceColumn, fleet, trackedRace);
|
|
| 783 | - }
|
|
| 784 | -
|
|
| 785 | - @Override
|
|
| 786 | - public void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
|
|
| 787 | - getRaceColumnListeners().notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace);
|
|
| 788 | - // It's generally possible that a leaderboard links to the same tracked race in multiple columns / fleets;
|
|
| 789 | - // only if it no longer references the trackedRace currently unlinked from one column/fleet, also unlink
|
|
| 790 | - // all cache invalidation listeners for said trackedRace
|
|
| 791 | - if (!Util.contains(getTrackedRaces(), trackedRace)) {
|
|
| 792 | - synchronized (cacheInvalidationListeners) {
|
|
| 793 | - for (Iterator<CacheInvalidationListener> cacheInvalidationListenerIter=cacheInvalidationListeners.iterator();
|
|
| 794 | - cacheInvalidationListenerIter.hasNext(); ) {
|
|
| 795 | - CacheInvalidationListener cacheInvalidationListener = cacheInvalidationListenerIter.next();
|
|
| 796 | - if (cacheInvalidationListener.getTrackedRace() == trackedRace) {
|
|
| 797 | - cacheInvalidationListener.removeFromTrackedRace();
|
|
| 798 | - cacheInvalidationListenerIter.remove();
|
|
| 799 | - }
|
|
| 800 | - }
|
|
| 801 | - }
|
|
| 802 | - }
|
|
| 803 | - }
|
|
| 804 | -
|
|
| 805 | - @Override
|
|
| 806 | - public void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) {
|
|
| 807 | - getRaceColumnListeners().notifyListenersAboutIsMedalRaceChanged(raceColumn, newIsMedalRace);
|
|
| 808 | - }
|
|
| 809 | -
|
|
| 810 | - @Override
|
|
| 811 | - public void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) {
|
|
| 812 | - getRaceColumnListeners().notifyListenersAboutIsStartsWithZeroScoreChanged(raceColumn, newIsStartsWithZeroScore);
|
|
| 813 | - }
|
|
| 814 | -
|
|
| 815 | - @Override
|
|
| 816 | - public void isFirstColumnIsNonDiscardableCarryForwardChanged(RaceColumn raceColumn, boolean firstColumnIsNonDiscardableCarryForward) {
|
|
| 817 | - getRaceColumnListeners().notifyListenersAboutIsFirstColumnIsNonDiscardableCarryForwardChanged(raceColumn, firstColumnIsNonDiscardableCarryForward);
|
|
| 818 | - }
|
|
| 819 | -
|
|
| 820 | - @Override
|
|
| 821 | - public void hasSplitFleetContiguousScoringChanged(RaceColumn raceColumn, boolean hasSplitFleetContiguousScoring) {
|
|
| 822 | - getRaceColumnListeners().notifyListenersAboutHasSplitFleetContiguousScoringChanged(raceColumn, hasSplitFleetContiguousScoring);
|
|
| 823 | - }
|
|
| 824 | -
|
|
| 825 | - @Override
|
|
| 826 | - public void raceColumnMoved(RaceColumn raceColumn, int newIndex) {
|
|
| 827 | - getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(raceColumn, newIndex);
|
|
| 828 | - }
|
|
| 829 | -
|
|
| 830 | - @Override
|
|
| 831 | - public void factorChanged(RaceColumn raceColumn, Double oldFactor, Double newFactor) {
|
|
| 832 | - getRaceColumnListeners().notifyListenersAboutFactorChanged(raceColumn, oldFactor, newFactor);
|
|
| 833 | - }
|
|
| 834 | -
|
|
| 835 | - /**
|
|
| 836 | - * A leaderboard will only accept the addition of a race column if the column's name is unique across the leaderboard.
|
|
| 837 | - */
|
|
| 838 | - @Override
|
|
| 839 | - public boolean canAddRaceColumnToContainer(RaceColumn newRaceColumn) {
|
|
| 840 | - boolean result = true;
|
|
| 841 | - for (RaceColumn raceColumn : getRaceColumns()) {
|
|
| 842 | - if (raceColumn.getName().equals(newRaceColumn.getName())) {
|
|
| 843 | - result = false;
|
|
| 844 | - break;
|
|
| 845 | - }
|
|
| 846 | - }
|
|
| 847 | - return result;
|
|
| 848 | - }
|
|
| 849 | -
|
|
| 850 | - @Override
|
|
| 851 | - public void raceColumnAddedToContainer(RaceColumn raceColumn) {
|
|
| 852 | - getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(raceColumn);
|
|
| 853 | - }
|
|
| 854 | -
|
|
| 855 | - @Override
|
|
| 856 | - public void raceColumnRemovedFromContainer(RaceColumn raceColumn) {
|
|
| 857 | - getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn);
|
|
| 858 | - }
|
|
| 859 | -
|
|
| 860 | - @Override
|
|
| 861 | - public void competitorDisplayNameChanged(Competitor competitor, String oldDisplayName, String displayName) {
|
|
| 862 | - getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName);
|
|
| 863 | - }
|
|
| 864 | -
|
|
| 865 | - @Override
|
|
| 866 | - public void resultDiscardingRuleChanged(ResultDiscardingRule oldDiscardingRule, ResultDiscardingRule newDiscardingRule) {
|
|
| 867 | - getRaceColumnListeners().notifyListenersAboutResultDiscardingRuleChanged(oldDiscardingRule, newDiscardingRule);
|
|
| 868 | - }
|
|
| 869 | -
|
|
| 870 | - @Override
|
|
| 871 | - public boolean isTransient() {
|
|
| 872 | - return false;
|
|
| 873 | - }
|
|
| 874 | -
|
|
| 875 | - /**
|
|
| 876 | - * Finds out the time point when any of the {@link Leaderboard#getTrackedRaces() tracked races currently attached to
|
|
| 877 | - * the <code>leaderboard</code>} and the {@link Leaderboard#getScoreCorrection() score corrections} have last been
|
|
| 878 | - * modified. If no tracked race is attached and no time-stamped score corrections have been applied to the leaderboard,
|
|
| 879 | - * <code>null</code> is returned. The time point computed this way is a good choice for normalizing queries for later time
|
|
| 880 | - * points in an attempt to achieve more cache hits.<p>
|
|
| 881 | - *
|
|
| 882 | - * Note, however, that the result does not tell about structural changes to the leaderboard and therefore cannot be used
|
|
| 883 | - * to determine the need for cache invalidation. For example, if a column is added to a leaderboard after the time point
|
|
| 884 | - * returned by this method but that column's attached tracked race has finished before the time point returned by this method,
|
|
| 885 | - * the result of this method won't change. Still, the contents of the leaderboard will change by a change in column structure.
|
|
| 886 | - * A different means to determine the possibility of changes that happened to this leaderboard must be used for cache
|
|
| 887 | - * management. Such a facility has to listen for score correction changes, tracked races being attached or detached and
|
|
| 888 | - * the column structure changing.
|
|
| 889 | - *
|
|
| 890 | - * @see TrackedRace#getTimePointOfNewestEvent()
|
|
| 891 | - * @see SettableScoreCorrection#getTimePointOfLastCorrectionsValidity()
|
|
| 892 | - */
|
|
| 893 | - @Override
|
|
| 894 | - public TimePoint getTimePointOfLatestModification() {
|
|
| 895 | - TimePoint result = null;
|
|
| 896 | - for (TrackedRace trackedRace : getTrackedRaces()) {
|
|
| 897 | - if (result == null || (trackedRace.getTimePointOfNewestEvent() != null && trackedRace.getTimePointOfNewestEvent().after(result))) {
|
|
| 898 | - result = trackedRace.getTimePointOfNewestEvent();
|
|
| 899 | - }
|
|
| 900 | - }
|
|
| 901 | - TimePoint timePointOfLastScoreCorrection = getScoreCorrection().getTimePointOfLastCorrectionsValidity();
|
|
| 902 | - if (timePointOfLastScoreCorrection != null && (result == null || timePointOfLastScoreCorrection.after(result))) {
|
|
| 903 | - result = timePointOfLastScoreCorrection;
|
|
| 904 | - }
|
|
| 905 | - return result;
|
|
| 906 | - }
|
|
| 907 | -
|
|
| 908 | - @Override
|
|
| 909 | - public com.sap.sse.common.Util.Pair<GPSFixMoving, Speed> getMaximumSpeedOverGround(Competitor competitor, TimePoint timePoint) {
|
|
| 910 | - com.sap.sse.common.Util.Pair<GPSFixMoving, Speed> result = null;
|
|
| 911 | - // TODO should we ensure that competitor participated in all race columns?
|
|
| 912 | - for (TrackedRace trackedRace : getTrackedRaces()) {
|
|
| 913 | - if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) {
|
|
| 914 | - NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor);
|
|
| 915 | - if (!markPassings.isEmpty()) {
|
|
| 916 | - TimePoint from = markPassings.first().getTimePoint();
|
|
| 917 | - TimePoint to;
|
|
| 918 | - if (timePoint.after(markPassings.last().getTimePoint()) &&
|
|
| 919 | - markPassings.last().getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) {
|
|
| 920 | - // stop counting when competitor finished the race
|
|
| 921 | - to = markPassings.last().getTimePoint();
|
|
| 922 | - } else {
|
|
| 923 | - to = timePoint;
|
|
| 924 | - }
|
|
| 925 | - com.sap.sse.common.Util.Pair<GPSFixMoving, Speed> maxSpeed = trackedRace.getTrack(competitor).getMaximumSpeedOverGround(from, to);
|
|
| 926 | - if (result == null || result.getB() == null ||
|
|
| 927 | - (maxSpeed != null && maxSpeed.getB() != null && maxSpeed.getB().compareTo(result.getB()) > 0)) {
|
|
| 928 | - result = maxSpeed;
|
|
| 929 | - }
|
|
| 930 | - }
|
|
| 931 | - }
|
|
| 932 | - }
|
|
| 933 | - return result;
|
|
| 934 | - }
|
|
| 935 | -
|
|
| 936 | - @Override
|
|
| 937 | - public Speed getAverageSpeedOverGround(Competitor competitor, TimePoint timePoint) {
|
|
| 938 | - Speed result = null;
|
|
| 939 | - for (TrackedRace trackedRace : getTrackedRaces()) {
|
|
| 940 | - if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) {
|
|
| 941 | - NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor);
|
|
| 942 | - if (!markPassings.isEmpty()) {
|
|
| 943 | - TimePoint from = markPassings.first().getTimePoint();
|
|
| 944 | - TimePoint to;
|
|
| 945 | - if (timePoint.after(markPassings.last().getTimePoint()) &&
|
|
| 946 | - markPassings.last().getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) {
|
|
| 947 | - // stop counting when competitor finished the race
|
|
| 948 | - to = markPassings.last().getTimePoint();
|
|
| 949 | - } else {
|
|
| 950 | - if (markPassings.last().getWaypoint() != trackedRace.getRace().getCourse().getLastWaypoint() &&
|
|
| 951 | - timePoint.after(markPassings.last().getTimePoint())) {
|
|
| 952 | - result = null;
|
|
| 953 | - break;
|
|
| 954 | - }
|
|
| 955 | - to = timePoint;
|
|
| 956 | - }
|
|
| 957 | - Distance distanceTraveled = trackedRace.getDistanceTraveled(competitor, timePoint);
|
|
| 958 | - if (distanceTraveled != null) {
|
|
| 959 | - result = distanceTraveled.inTime(to.asMillis()-from.asMillis());
|
|
| 960 | - }
|
|
| 961 | - }
|
|
| 962 | - }
|
|
| 963 | - }
|
|
| 964 | - return result;
|
|
| 965 | - }
|
|
| 966 | -
|
|
| 967 | - @Override
|
|
| 968 | - public Duration getTotalTimeSailedInLegType(Competitor competitor, LegType legType, TimePoint timePoint) throws NoWindException {
|
|
| 969 | - return getTotalTimeSailedInLegType(competitor, legType, timePoint, new HashMap<TrackedLeg, LegType>());
|
|
| 970 | - }
|
|
| 971 | -
|
|
| 972 | - private Duration getTotalTimeSailedInLegType(Competitor competitor, LegType legType, TimePoint timePoint, Map<TrackedLeg, LegType> legTypeCache) throws NoWindException {
|
|
| 973 | - Duration result = null;
|
|
| 974 | - // TODO should we ensure that competitor participated in all race columns?
|
|
| 975 | - outerLoop:
|
|
| 976 | - for (TrackedRace trackedRace : getTrackedRaces()) {
|
|
| 977 | - if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) {
|
|
| 978 | - trackedRace.getRace().getCourse().lockForRead();
|
|
| 979 | - try {
|
|
| 980 | - for (Leg leg : trackedRace.getRace().getCourse().getLegs()) {
|
|
| 981 | - TrackedLegOfCompetitor trackedLegOfCompetitor = trackedRace.getTrackedLeg(competitor, leg);
|
|
| 982 | - if (trackedLegOfCompetitor.hasStartedLeg(timePoint)) {
|
|
| 983 | - // find out leg type at the time the competitor started the leg
|
|
| 984 | - try {
|
|
| 985 | - final TrackedLeg trackedLeg = trackedRace.getTrackedLeg(leg);
|
|
| 986 | - LegType trackedLegType = legTypeCache.get(trackedLeg);
|
|
| 987 | - if (trackedLegType == null) {
|
|
| 988 | - final TimePoint startTime = trackedLegOfCompetitor.getStartTime();
|
|
| 989 | - TimePoint finishTime = trackedLegOfCompetitor.getFinishTime();
|
|
| 990 | - if (finishTime == null) {
|
|
| 991 | - finishTime = timePoint;
|
|
| 992 | - }
|
|
| 993 | - trackedLegType = trackedLeg.getLegType(startTime.plus(startTime.until(finishTime).divide(2))); // middle of the leg
|
|
| 994 | - legTypeCache.put(trackedLeg, trackedLegType);
|
|
| 995 | - }
|
|
| 996 | - if (legType == trackedLegType) {
|
|
| 997 | - Duration timeSpentOnDownwind = trackedLegOfCompetitor.getTime(timePoint);
|
|
| 998 | - if (timeSpentOnDownwind != null) {
|
|
| 999 | - if (result == null) {
|
|
| 1000 | - result = timeSpentOnDownwind;
|
|
| 1001 | - } else {
|
|
| 1002 | - result = result.plus(timeSpentOnDownwind);
|
|
| 1003 | - }
|
|
| 1004 | - } else {
|
|
| 1005 | - // Although the competitor has started the leg, no value was produced. This
|
|
| 1006 | - // means that the competitor didn't finish the leg before tracking ended. No useful value
|
|
| 1007 | - // can be obtained for this competitor anymore.
|
|
| 1008 | - result = null;
|
|
| 1009 | - break outerLoop;
|
|
| 1010 | - }
|
|
| 1011 | - }
|
|
| 1012 | - } catch (NoWindException nwe) {
|
|
| 1013 | - // without wind there is no leg type and hence there is no reasonable value for this:
|
|
| 1014 | - result = null;
|
|
| 1015 | - break outerLoop;
|
|
| 1016 | - }
|
|
| 1017 | - }
|
|
| 1018 | - }
|
|
| 1019 | - } finally {
|
|
| 1020 | - trackedRace.getRace().getCourse().unlockAfterRead();
|
|
| 1021 | - }
|
|
| 1022 | - }
|
|
| 1023 | - }
|
|
| 1024 | - return result;
|
|
| 1025 | - }
|
|
| 1026 | -
|
|
| 1027 | - @Override
|
|
| 1028 | - public Duration getTotalTimeSailed(Competitor competitor, TimePoint timePoint) {
|
|
| 1029 | - Duration result = null;
|
|
| 1030 | - for (TrackedRace trackedRace : getTrackedRaces()) {
|
|
| 1031 | - if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) {
|
|
| 1032 | - NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor);
|
|
| 1033 | - if (!markPassings.isEmpty()) {
|
|
| 1034 | - TimePoint from = trackedRace.getStartOfRace(); // start counting at race start, not when the competitor passed the line
|
|
| 1035 | - if (from != null && !timePoint.before(from)) { // but only if the race started after timePoint
|
|
| 1036 | - TimePoint to;
|
|
| 1037 | - if (timePoint.after(markPassings.last().getTimePoint())
|
|
| 1038 | - && markPassings.last().getWaypoint() == trackedRace.getRace().getCourse()
|
|
| 1039 | - .getLastWaypoint()) {
|
|
| 1040 | - // stop counting when competitor finished the race
|
|
| 1041 | - to = markPassings.last().getTimePoint();
|
|
| 1042 | - } else {
|
|
| 1043 | - if (trackedRace.getEndOfTracking() != null
|
|
| 1044 | - && timePoint.after(trackedRace.getEndOfTracking())) {
|
|
| 1045 | - result = null; // race not finished until end of tracking; no reasonable value can be
|
|
| 1046 | - // computed for competitor
|
|
| 1047 | - break;
|
|
| 1048 | - } else {
|
|
| 1049 | - to = timePoint;
|
|
| 1050 | - }
|
|
| 1051 | - }
|
|
| 1052 | - Duration timeSpent = from.until(to);
|
|
| 1053 | - if (result == null) {
|
|
| 1054 | - result = timeSpent;
|
|
| 1055 | - } else {
|
|
| 1056 | - result=result.plus(timeSpent);
|
|
| 1057 | - }
|
|
| 1058 | - }
|
|
| 1059 | - }
|
|
| 1060 | - }
|
|
| 1061 | - }
|
|
| 1062 | - return result;
|
|
| 1063 | - }
|
|
| 1064 | -
|
|
| 1065 | - @Override
|
|
| 1066 | - public Distance getTotalDistanceTraveled(Competitor competitor, TimePoint timePoint) {
|
|
| 1067 | - Distance result = null;
|
|
| 1068 | - for (TrackedRace trackedRace : getTrackedRaces()) {
|
|
| 1069 | - TimePoint startOfRace;
|
|
| 1070 | - if (Util.contains(trackedRace.getRace().getCompetitors(), competitor) &&
|
|
| 1071 | - (startOfRace=trackedRace.getStartOfRace()) != null &&
|
|
| 1072 | - !startOfRace.after(timePoint)) {
|
|
| 1073 | - Distance distanceSailedInRace = trackedRace.getDistanceTraveled(competitor, timePoint);
|
|
| 1074 | - if (distanceSailedInRace != null) {
|
|
| 1075 | - if (result == null) {
|
|
| 1076 | - result = distanceSailedInRace;
|
|
| 1077 | - } else {
|
|
| 1078 | - result = result.add(distanceSailedInRace);
|
|
| 1079 | - }
|
|
| 1080 | - } else {
|
|
| 1081 | - // if competitor has not finished one single race in the whole
|
|
| 1082 | - // series then we can not return a meaningful value for all
|
|
| 1083 | - // all races
|
|
| 1084 | - return null;
|
|
| 1085 | - }
|
|
| 1086 | - }
|
|
| 1087 | - }
|
|
| 1088 | - return result;
|
|
| 1089 | - }
|
|
| 1090 | -
|
|
| 1091 | - protected RaceColumnListeners getRaceColumnListeners() {
|
|
| 1092 | - return raceColumnListeners;
|
|
| 1093 | - }
|
|
| 1094 | -
|
|
| 1095 | - @Override
|
|
| 1096 | - public Iterable<Competitor> getCompetitors() {
|
|
| 1097 | - final Iterable<Competitor> result;
|
|
| 1098 | - // mostly the set of suppressed competitors is empty; in this case, avoid having to loop over the
|
|
| 1099 | - // potentially large set of competitors
|
|
| 1100 | - if (Util.isEmpty(getSuppressedCompetitors())) {
|
|
| 1101 | - result = getAllCompetitors();
|
|
| 1102 | - } else {
|
|
| 1103 | - final Iterable<Competitor> allCompetitors = getAllCompetitors();
|
|
| 1104 | - final Set<Competitor> suppressed = new HashSet<>();
|
|
| 1105 | - Util.addAll(getSuppressedCompetitors(), suppressed);
|
|
| 1106 | - result = getCompetitorIterableSkippingSuppressed(allCompetitors, suppressed);
|
|
| 1107 | - }
|
|
| 1108 | - return result;
|
|
| 1109 | - }
|
|
| 1110 | -
|
|
| 1111 | - @Override
|
|
| 1112 | - public Iterable<Competitor> getCompetitors(RaceColumn raceColumn, Fleet fleet) {
|
|
| 1113 | - return getCompetitorIterableSkippingSuppressed(getAllCompetitors(raceColumn, fleet), getSuppressedCompetitors());
|
|
| 1114 | - }
|
|
| 1115 | -
|
|
| 1116 | - /**
|
|
| 1117 | - * return an iterable with a smart iterator that filters out the suppressed elements on demand
|
|
| 1118 | - */
|
|
| 1119 | - protected Iterable<Competitor> getCompetitorIterableSkippingSuppressed(final Iterable<Competitor> allCompetitors,
|
|
| 1120 | - final Iterable<Competitor> suppressed) {
|
|
| 1121 | - final Iterable<Competitor> result;
|
|
| 1122 | - result = new Iterable<Competitor>() {
|
|
| 1123 | - @Override
|
|
| 1124 | - public Iterator<Competitor> iterator() {
|
|
| 1125 | - return new Iterator<Competitor>() {
|
|
| 1126 | - private final Iterator<Competitor> allIter = allCompetitors.iterator();
|
|
| 1127 | - private Competitor next = advance();
|
|
| 1128 | -
|
|
| 1129 | - private Competitor advance() {
|
|
| 1130 | - next = null;
|
|
| 1131 | - while (allIter.hasNext() && next == null) {
|
|
| 1132 | - next = allIter.next();
|
|
| 1133 | - if (Util.contains(suppressed, next)) {
|
|
| 1134 | - next = null;
|
|
| 1135 | - }
|
|
| 1136 | - }
|
|
| 1137 | - return next;
|
|
| 1138 | - }
|
|
| 1139 | -
|
|
| 1140 | - @Override
|
|
| 1141 | - public boolean hasNext() {
|
|
| 1142 | - return next != null;
|
|
| 1143 | - }
|
|
| 1144 | -
|
|
| 1145 | - @Override
|
|
| 1146 | - public Competitor next() {
|
|
| 1147 | - if (next == null) {
|
|
| 1148 | - throw new NoSuchElementException();
|
|
| 1149 | - }
|
|
| 1150 | - final Competitor result = next;
|
|
| 1151 | - advance();
|
|
| 1152 | - return result;
|
|
| 1153 | - }
|
|
| 1154 | - };
|
|
| 1155 | - }
|
|
| 1156 | - };
|
|
| 1157 | - return result;
|
|
| 1158 | - }
|
|
| 1159 | -
|
|
| 1160 | - @Override
|
|
| 1161 | - public Iterable<Competitor> getSuppressedCompetitors() {
|
|
| 1162 | - LockUtil.lockForRead(suppressedCompetitorsLock);
|
|
| 1163 | - try {
|
|
| 1164 | - return new HashSet<Competitor>(suppressedCompetitors);
|
|
| 1165 | - } finally {
|
|
| 1166 | - LockUtil.unlockAfterRead(suppressedCompetitorsLock);
|
|
| 1167 | - }
|
|
| 1168 | - }
|
|
| 1169 | -
|
|
| 1170 | - @Override
|
|
| 1171 | - public void setSuppressed(Competitor competitor, boolean suppressed) {
|
|
| 1172 | - LockUtil.lockForWrite(suppressedCompetitorsLock);
|
|
| 1173 | - try {
|
|
| 1174 | - if (suppressed) {
|
|
| 1175 | - suppressedCompetitors.add(competitor);
|
|
| 1176 | - } else {
|
|
| 1177 | - suppressedCompetitors.remove(competitor);
|
|
| 1178 | - }
|
|
| 1179 | - } finally {
|
|
| 1180 | - LockUtil.unlockAfterWrite(suppressedCompetitorsLock);
|
|
| 1181 | - }
|
|
| 1182 | - getScoreCorrection().notifyListenersAboutIsSuppressedChange(competitor, suppressed);
|
|
| 1183 | - }
|
|
| 1184 | -
|
|
| 1185 | - @Override
|
|
| 1186 | - public TimePoint getNowMinusDelay() {
|
|
| 1187 | - final TimePoint now = MillisecondsTimePoint.now();
|
|
| 1188 | - final Long delayToLiveInMillis = getDelayToLiveInMillis();
|
|
| 1189 | - TimePoint timePoint = delayToLiveInMillis == null ? now : now.minus(delayToLiveInMillis);
|
|
| 1190 | - return timePoint;
|
|
| 1191 | - }
|
|
| 1192 | -
|
|
| 1193 | - @Override
|
|
| 1194 | - public void raceLogEventAdded(RaceColumn raceColumn, RaceLogIdentifier raceLogIdentifier, RaceLogEvent event) {
|
|
| 1195 | - getRaceColumnListeners().notifyListenersAboutRaceLogEventAdded(raceColumn, raceLogIdentifier, event);
|
|
| 1196 | - if (event instanceof InvalidatesLeaderboardCache) {
|
|
| 1197 | - // make sure to invalidate the cache as this event indicates that
|
|
| 1198 | - // it changes values the cache could still hold
|
|
| 1199 | - if (leaderboardDTOCache != null) {
|
|
| 1200 | - leaderboardDTOCache.invalidate(this);
|
|
| 1201 | - }
|
|
| 1202 | - }
|
|
| 1203 | - }
|
|
| 1204 | -
|
|
| 1205 | - @Override
|
|
| 1206 | - public LeaderboardDTO computeDTO(final TimePoint timePoint,
|
|
| 1207 | - final Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails,
|
|
| 1208 | - final boolean waitForLatestAnalyses, TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory,
|
|
| 1209 | - final boolean fillNetPointsUncorrected)
|
|
| 1210 | - throws NoWindException {
|
|
| 1211 | - long startOfRequestHandling = System.currentTimeMillis();
|
|
| 1212 | - final LeaderboardDTOCalculationReuseCache cache = new LeaderboardDTOCalculationReuseCache(timePoint);
|
|
| 1213 | - final LeaderboardDTO result = new LeaderboardDTO(this.getScoreCorrection().getTimePointOfLastCorrectionsValidity() == null ? null
|
|
| 1214 | - : this.getScoreCorrection().getTimePointOfLastCorrectionsValidity().asDate(),
|
|
| 1215 | - this.getScoreCorrection() == null ? null : this.getScoreCorrection().getComment(),
|
|
| 1216 | - this.getScoringScheme() == null ? null : this.getScoringScheme().getType(), this
|
|
| 1217 | - .getScoringScheme().isHigherBetter(), new UUIDGenerator(), addOverallDetails);
|
|
| 1218 | - result.competitors = new ArrayList<CompetitorDTO>();
|
|
| 1219 | - result.name = this.getName();
|
|
| 1220 | - result.competitorDisplayNames = new HashMap<CompetitorDTO, String>();
|
|
| 1221 | - for (Competitor suppressedCompetitor : this.getSuppressedCompetitors()) {
|
|
| 1222 | - result.setSuppressed(baseDomainFactory.convertToCompetitorDTO(suppressedCompetitor), true);
|
|
| 1223 | - }
|
|
| 1224 | - // Now create the race columns and, as a future task, set their competitorsFromBestToWorst, then wait for all these
|
|
| 1225 | - // futures to finish:
|
|
| 1226 | - Map<RaceColumn, FutureTask<List<CompetitorDTO>>> competitorsFromBestToWorstTasks = new HashMap<>();
|
|
| 1227 | - for (final RaceColumn raceColumn : this.getRaceColumns()) {
|
|
| 1228 | - result.createEmptyRaceColumn(raceColumn.getName(), raceColumn.isMedalRace(),
|
|
| 1229 | - raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null,
|
|
| 1230 | - raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null);
|
|
| 1231 | - for (Fleet fleet : raceColumn.getFleets()) {
|
|
| 1232 | - RegattaAndRaceIdentifier raceIdentifier = null;
|
|
| 1233 | - RaceDTO race = null;
|
|
| 1234 | - TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
|
|
| 1235 | - final FleetDTO fleetDTO = baseDomainFactory.convertToFleetDTO(fleet);
|
|
| 1236 | - if (trackedRace != null) {
|
|
| 1237 | - raceIdentifier = new RegattaNameAndRaceName(trackedRace.getTrackedRegatta().getRegatta().getName(),
|
|
| 1238 | - trackedRace.getRace().getName());
|
|
| 1239 | - race = baseDomainFactory.createRaceDTO(trackedRegattaRegistry, /* withGeoLocationData */ false, raceIdentifier, trackedRace);
|
|
| 1240 | - }
|
|
| 1241 | - // Note: the RaceColumnDTO won't be created by the following addRace call because it has been created
|
|
| 1242 | - // above by the result.createEmptyRaceColumn call
|
|
| 1243 | - result.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(),
|
|
| 1244 | - raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null,
|
|
| 1245 | - raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null,
|
|
| 1246 | - fleetDTO, raceColumn.isMedalRace(), raceIdentifier, race);
|
|
| 1247 | - }
|
|
| 1248 | - FutureTask<List<CompetitorDTO>> task = new FutureTask<List<CompetitorDTO>>(
|
|
| 1249 | - () -> baseDomainFactory.getCompetitorDTOList(AbstractSimpleLeaderboardImpl.this.getCompetitorsFromBestToWorst(raceColumn, timePoint)));
|
|
| 1250 | - executor.execute(task);
|
|
| 1251 | - competitorsFromBestToWorstTasks.put(raceColumn, task);
|
|
| 1252 | - }
|
|
| 1253 | - // wait for the competitor orderings to have been computed for all race columns before continuing; subsequent tasks may depend on these data
|
|
| 1254 | - for (Map.Entry<RaceColumn, FutureTask<List<CompetitorDTO>>> raceColumnAndTaskToJoin : competitorsFromBestToWorstTasks.entrySet()) {
|
|
| 1255 | - try {
|
|
| 1256 | - result.setCompetitorsFromBestToWorst(raceColumnAndTaskToJoin.getKey().getName(), raceColumnAndTaskToJoin.getValue().get());
|
|
| 1257 | - } catch (InterruptedException e) {
|
|
| 1258 | - throw new RuntimeException(e);
|
|
| 1259 | - } catch (ExecutionException e) {
|
|
| 1260 | - // See also bug 1371: for stability reasons, don't let the exception percolate but rather accept
|
|
| 1261 | - // null values.
|
|
| 1262 | - // If new evidence is provided, a re-calculation of the leaderboard will be triggered anyway. So
|
|
| 1263 | - // this helps robustness from a user's perspective.
|
|
| 1264 | - logger.log(
|
|
| 1265 | - Level.SEVERE,
|
|
| 1266 | - AbstractSimpleLeaderboardImpl.class.getName() + ".computeDTO(" + this.getName() + ", "
|
|
| 1267 | - + timePoint + ", " + namesOfRaceColumnsForWhichToLoadLegDetails+", addOverallDetails="+addOverallDetails
|
|
| 1268 | - + "): exception during computing competitor ordering for race column "+raceColumnAndTaskToJoin.getKey().getName(), e);
|
|
| 1269 | - }
|
|
| 1270 | - }
|
|
| 1271 | - result.setDelayToLiveInMillisForLatestRace(this.getDelayToLiveInMillis());
|
|
| 1272 | - result.rows = new HashMap<CompetitorDTO, LeaderboardRowDTO>();
|
|
| 1273 | - result.hasCarriedPoints = this.hasCarriedPoints();
|
|
| 1274 | - if (this.getResultDiscardingRule() instanceof ThresholdBasedResultDiscardingRule) {
|
|
| 1275 | - result.discardThresholds = ((ThresholdBasedResultDiscardingRule) this.getResultDiscardingRule())
|
|
| 1276 | - .getDiscardIndexResultsStartingWithHowManyRaces();
|
|
| 1277 | - } else {
|
|
| 1278 | - result.discardThresholds = null;
|
|
| 1279 | - }
|
|
| 1280 | - // Computing the competitor leg ranks is expensive, especially in live mode, in case new events keep
|
|
| 1281 | - // invalidating the ranks cache in TrackedLegImpl. The problem then is that the sorting based on wind data is repeated for
|
|
| 1282 | - // each competitor, leading to square effort. We therefore need to compute the leg ranks for those races where leg
|
|
| 1283 | - // details are requested only once and pass them into getLeaderboardEntryDTO
|
|
| 1284 | - final Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache = new HashMap<Leg, LinkedHashMap<Competitor, Integer>>();
|
|
| 1285 | - for (final RaceColumn raceColumn : this.getRaceColumns()) {
|
|
| 1286 | - // if details for the column are requested, cache the leg's ranks
|
|
| 1287 | - if (namesOfRaceColumnsForWhichToLoadLegDetails != null
|
|
| 1288 | - && namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn.getName())) {
|
|
| 1289 | - for (Fleet fleet : raceColumn.getFleets()) {
|
|
| 1290 | - TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
|
|
| 1291 | - if (trackedRace != null) {
|
|
| 1292 | - trackedRace.getRace().getCourse().lockForRead();
|
|
| 1293 | - try {
|
|
| 1294 | - for (TrackedLeg trackedLeg : trackedRace.getTrackedLegs()) {
|
|
| 1295 | - legRanksCache.put(trackedLeg.getLeg(), trackedLeg.getRanks(timePoint, cache));
|
|
| 1296 | - }
|
|
| 1297 | - } finally {
|
|
| 1298 | - trackedRace.getRace().getCourse().unlockAfterRead();
|
|
| 1299 | - }
|
|
| 1300 | - }
|
|
| 1301 | - }
|
|
| 1302 | - }
|
|
| 1303 | - }
|
|
| 1304 | - for (final Competitor competitor : this.getCompetitorsFromBestToWorst(timePoint)) {
|
|
| 1305 | - CompetitorDTO competitorDTO = baseDomainFactory.convertToCompetitorDTO(competitor);
|
|
| 1306 | - LeaderboardRowDTO row = new LeaderboardRowDTO();
|
|
| 1307 | - row.competitor = competitorDTO;
|
|
| 1308 | - row.fieldsByRaceColumnName = new HashMap<String, LeaderboardEntryDTO>();
|
|
| 1309 | - row.carriedPoints = this.hasCarriedPoints(competitor) ? this.getCarriedPoints(competitor) : null;
|
|
| 1310 | - row.totalPoints = this.getTotalPoints(competitor, timePoint);
|
|
| 1311 | - if (addOverallDetails) {
|
|
| 1312 | - addOverallDetailsToRow(timePoint, competitor, row);
|
|
| 1313 | - }
|
|
| 1314 | - result.competitors.add(competitorDTO);
|
|
| 1315 | - Map<String, Future<LeaderboardEntryDTO>> futuresForColumnName = new HashMap<String, Future<LeaderboardEntryDTO>>();
|
|
| 1316 | - final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint);
|
|
| 1317 | - for (final RaceColumn raceColumn : this.getRaceColumns()) {
|
|
| 1318 | - RunnableFuture<LeaderboardEntryDTO> future = new FutureTask<LeaderboardEntryDTO>(() -> {
|
|
| 1319 | - Entry entry = AbstractSimpleLeaderboardImpl.this.getEntry(competitor, raceColumn, timePoint, discardedRaceColumns);
|
|
| 1320 | - return getLeaderboardEntryDTO(entry, raceColumn, competitor, timePoint,
|
|
| 1321 | - namesOfRaceColumnsForWhichToLoadLegDetails != null
|
|
| 1322 | - && namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn
|
|
| 1323 | - .getName()), waitForLatestAnalyses, legRanksCache, baseDomainFactory,
|
|
| 1324 | - fillNetPointsUncorrected, cache);
|
|
| 1325 | - });
|
|
| 1326 | - executor.execute(future);
|
|
| 1327 | - futuresForColumnName.put(raceColumn.getName(), future);
|
|
| 1328 | - }
|
|
| 1329 | - for (Map.Entry<String, Future<LeaderboardEntryDTO>> raceColumnNameAndFuture : futuresForColumnName.entrySet()) {
|
|
| 1330 | - try {
|
|
| 1331 | - row.fieldsByRaceColumnName.put(raceColumnNameAndFuture.getKey(), raceColumnNameAndFuture.getValue().get());
|
|
| 1332 | - } catch (InterruptedException e) {
|
|
| 1333 | - throw new RuntimeException(e);
|
|
| 1334 | - } catch (ExecutionException e) {
|
|
| 1335 | - // See also bug 1371: for stability reasons, don't let the exception percolate but rather accept
|
|
| 1336 | - // null values.
|
|
| 1337 | - // If new evidence is provided, a re-calculation of the leaderboard will be triggered anyway. So
|
|
| 1338 | - // this helps robustness from a user's perspective.
|
|
| 1339 | - logger.log(
|
|
| 1340 | - Level.SEVERE,
|
|
| 1341 | - AbstractSimpleLeaderboardImpl.class.getName() + ".computeDTO(" + this.getName() + ", "
|
|
| 1342 | - + timePoint + ", " + namesOfRaceColumnsForWhichToLoadLegDetails+", addOverallDetails="+addOverallDetails
|
|
| 1343 | - + "): exception during computing leaderboard entry for competitor "
|
|
| 1344 | - + competitor.getName() + " in race column " + raceColumnNameAndFuture.getKey()
|
|
| 1345 | - + ". Leaving empty.", e);
|
|
| 1346 | - }
|
|
| 1347 | - }
|
|
| 1348 | - result.rows.put(competitorDTO, row);
|
|
| 1349 | - String displayName = this.getDisplayName(competitor);
|
|
| 1350 | - if (displayName != null) {
|
|
| 1351 | - result.competitorDisplayNames.put(competitorDTO, displayName);
|
|
| 1352 | - }
|
|
| 1353 | - }
|
|
| 1354 | - logger.info("computeLeaderboardByName(" + this.getName() + ", " + timePoint + ", "
|
|
| 1355 | - + namesOfRaceColumnsForWhichToLoadLegDetails + ", addOverallDetails=" + addOverallDetails + ") took "
|
|
| 1356 | - + (System.currentTimeMillis() - startOfRequestHandling) + "ms");
|
|
| 1357 | - return result;
|
|
| 1358 | - }
|
|
| 1359 | -
|
|
| 1360 | - private void addOverallDetailsToRow(final TimePoint timePoint,
|
|
| 1361 | - final Competitor competitor, LeaderboardRowDTO row) throws NoWindException {
|
|
| 1362 | - final com.sap.sse.common.Util.Pair<GPSFixMoving, Speed> maximumSpeedOverGround = this.getMaximumSpeedOverGround(competitor, timePoint);
|
|
| 1363 | - if (maximumSpeedOverGround != null && maximumSpeedOverGround.getB() != null) {
|
|
| 1364 | - row.maximumSpeedOverGroundInKnots = maximumSpeedOverGround.getB().getKnots();
|
|
| 1365 | - row.whenMaximumSpeedOverGroundWasAchieved = maximumSpeedOverGround.getA().getTimePoint().asDate();
|
|
| 1366 | - }
|
|
| 1367 | - Map<TrackedLeg, LegType> legTypeCache = new HashMap<>();
|
|
| 1368 | - final Duration totalTimeSailedDownwind = this.getTotalTimeSailedInLegType(competitor, LegType.DOWNWIND, timePoint, legTypeCache);
|
|
| 1369 | - row.totalTimeSailedDownwindInSeconds = totalTimeSailedDownwind==null?null:totalTimeSailedDownwind.asSeconds();
|
|
| 1370 | - final Duration totalTimeSailedUpwind = this.getTotalTimeSailedInLegType(competitor, LegType.UPWIND, timePoint, legTypeCache);
|
|
| 1371 | - row.totalTimeSailedUpwindInSeconds = totalTimeSailedUpwind==null?null:totalTimeSailedUpwind.asSeconds();
|
|
| 1372 | - final Duration totalTimeSailedReaching = this.getTotalTimeSailedInLegType(competitor, LegType.REACHING, timePoint, legTypeCache);
|
|
| 1373 | - row.totalTimeSailedReachingInSeconds = totalTimeSailedReaching==null?null:totalTimeSailedReaching.asSeconds();
|
|
| 1374 | - final Duration totalTimeSailed = this.getTotalTimeSailed(competitor, timePoint);
|
|
| 1375 | - row.totalTimeSailedInSeconds = totalTimeSailed==null?null:totalTimeSailed.asSeconds();
|
|
| 1376 | - final Distance totalDistanceTraveledInMeters = this.getTotalDistanceTraveled(competitor, timePoint);
|
|
| 1377 | - row.totalDistanceTraveledInMeters = totalDistanceTraveledInMeters==null?null:totalDistanceTraveledInMeters.getMeters();
|
|
| 1378 | - }
|
|
| 1379 | -
|
|
| 1380 | - /**
|
|
| 1381 | - * @param waitForLatestAnalyses
|
|
| 1382 | - * if <code>false</code>, this method is allowed to read the maneuver analysis results from a cache that
|
|
| 1383 | - * may not reflect all data already received; otherwise, the method will always block for the latest
|
|
| 1384 | - * cache updates to have happened before returning.
|
|
| 1385 | - * @param fillNetPointsUncorrected
|
|
| 1386 | - * tells if {@link LeaderboardEntryDTO#netPointsUncorrected} shall be filled; filling it is rather
|
|
| 1387 | - * expensive, especially when compared to simply retrieving a score correction, and particularly if in a
|
|
| 1388 | - * larger fleet a number of competitors haven't properly finished the race. This should only be used for
|
|
| 1389 | - * leaderboard editing where a user needs to see what the uncorrected score was that would be used when
|
|
| 1390 | - * the correction was removed.
|
|
| 1391 | - */
|
|
| 1392 | - private LeaderboardEntryDTO getLeaderboardEntryDTO(Entry entry, RaceColumn raceColumn, Competitor competitor,
|
|
| 1393 | - TimePoint timePoint, boolean addLegDetails, boolean waitForLatestAnalyses,
|
|
| 1394 | - Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache, DomainFactory baseDomainFactory,
|
|
| 1395 | - boolean fillNetPointsUncorrected, WindLegTypeAndLegBearingCache cache) {
|
|
| 1396 | - LeaderboardEntryDTO entryDTO = new LeaderboardEntryDTO();
|
|
| 1397 | - TrackedRace trackedRace = raceColumn.getTrackedRace(competitor);
|
|
| 1398 | - entryDTO.race = trackedRace == null ? null : trackedRace.getRaceIdentifier();
|
|
| 1399 | - entryDTO.netPoints = entry.getNetPoints();
|
|
| 1400 | - if (fillNetPointsUncorrected) {
|
|
| 1401 | - entryDTO.netPointsUncorrected = entry.getNetPointsUncorrected();
|
|
| 1402 | - }
|
|
| 1403 | - entryDTO.netPointsCorrected = entry.isNetPointsCorrected();
|
|
| 1404 | - entryDTO.totalPoints = entry.getTotalPoints();
|
|
| 1405 | - entryDTO.reasonForMaxPoints = entry.getMaxPointsReason();
|
|
| 1406 | - entryDTO.discarded = entry.isDiscarded();
|
|
| 1407 | - final GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace == null ? null : trackedRace.getTrack(competitor);
|
|
| 1408 | - if (trackedRace != null) {
|
|
| 1409 | - Date timePointOfLastPositionFixAtOrBeforeQueryTimePoint = getTimePointOfLastFixAtOrBefore(competitor, trackedRace, timePoint);
|
|
| 1410 | - if (track != null) {
|
|
| 1411 | - entryDTO.averageSamplingInterval = track.getAverageIntervalBetweenRawFixes();
|
|
| 1412 | - }
|
|
| 1413 | - if (timePointOfLastPositionFixAtOrBeforeQueryTimePoint != null) {
|
|
| 1414 | - long timeDifferenceInMs = timePoint.asMillis() - timePointOfLastPositionFixAtOrBeforeQueryTimePoint.getTime();
|
|
| 1415 | - entryDTO.timeSinceLastPositionFixInSeconds = timeDifferenceInMs == 0 ? 0.0 : timeDifferenceInMs / 1000.0;
|
|
| 1416 | - } else {
|
|
| 1417 | - entryDTO.timeSinceLastPositionFixInSeconds = null;
|
|
| 1418 | - }
|
|
| 1419 | - }
|
|
| 1420 | - if (addLegDetails && trackedRace != null) {
|
|
| 1421 | - try {
|
|
| 1422 | - final RankingInfo rankingInfo = trackedRace.getRankingMetric().getRankingInfo(timePoint, cache);
|
|
| 1423 | - RaceDetails raceDetails = getRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses,
|
|
| 1424 | - legRanksCache, rankingInfo, cache);
|
|
| 1425 | - entryDTO.legDetails = raceDetails.getLegDetails();
|
|
| 1426 | - entryDTO.windwardDistanceToCompetitorFarthestAheadInMeters = raceDetails.getWindwardDistanceToCompetitorFarthestAhead() == null ? null
|
|
| 1427 | - : raceDetails.getWindwardDistanceToCompetitorFarthestAhead().getMeters();
|
|
| 1428 | - entryDTO.gapToLeaderInOwnTime = trackedRace.getRankingMetric().getGapToLeaderInOwnTime(rankingInfo, competitor, cache);
|
|
| 1429 | - entryDTO.averageAbsoluteCrossTrackErrorInMeters = raceDetails.getAverageAbsoluteCrossTrackError() == null ? null
|
|
| 1430 | - : raceDetails.getAverageAbsoluteCrossTrackError().getMeters();
|
|
| 1431 | - entryDTO.averageSignedCrossTrackErrorInMeters = raceDetails.getAverageSignedCrossTrackError() == null ? null
|
|
| 1432 | - : raceDetails.getAverageSignedCrossTrackError().getMeters();
|
|
| 1433 | - entryDTO.calculatedTime = raceDetails.getCorrectedTime();
|
|
| 1434 | - entryDTO.calculatedTimeAtEstimatedArrivalAtCompetitorFarthestAhead = raceDetails.getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead();
|
|
| 1435 | - entryDTO.gapToLeaderInOwnTime = raceDetails.getGapToLeaderInOwnTime();
|
|
| 1436 | - final TimePoint startOfRace = trackedRace.getStartOfRace();
|
|
| 1437 | - if (startOfRace != null) {
|
|
| 1438 | - Waypoint startWaypoint = trackedRace.getRace().getCourse().getFirstWaypoint();
|
|
| 1439 | - NavigableSet<MarkPassing> competitorMarkPassings = trackedRace.getMarkPassings(competitor);
|
|
| 1440 | - trackedRace.lockForRead(competitorMarkPassings);
|
|
| 1441 | - try {
|
|
| 1442 | - if (!competitorMarkPassings.isEmpty()) {
|
|
| 1443 | - final MarkPassing firstMarkPassing = competitorMarkPassings.iterator().next();
|
|
| 1444 | - if (firstMarkPassing.getWaypoint() == startWaypoint) {
|
|
| 1445 | - Distance distanceToStartLineFiveSecondsBeforeStartOfRace = trackedRace.getDistanceToStartLine(competitor, /*milliseconds before start*/ 5000);
|
|
| 1446 | - entryDTO.distanceToStartLineFiveSecondsBeforeStartInMeters = distanceToStartLineFiveSecondsBeforeStartOfRace == null ? null
|
|
| 1447 | - : distanceToStartLineFiveSecondsBeforeStartOfRace.getMeters();
|
|
| 1448 | - Speed speedFiveSecondsBeforeStartOfRace = trackedRace.getSpeed(competitor, /*milliseconds before start*/ 5000);
|
|
| 1449 | - entryDTO.speedOverGroundFiveSecondsBeforeStartInKnots = speedFiveSecondsBeforeStartOfRace == null ? null
|
|
| 1450 | - : speedFiveSecondsBeforeStartOfRace.getKnots();
|
|
| 1451 | - Distance distanceToStartLineAtStartOfRace = trackedRace.getDistanceToStartLine(
|
|
| 1452 | - competitor, startOfRace);
|
|
| 1453 | - entryDTO.distanceToStartLineAtStartOfRaceInMeters = distanceToStartLineAtStartOfRace == null ? null
|
|
| 1454 | - : distanceToStartLineAtStartOfRace.getMeters();
|
|
| 1455 | - Speed speedAtStartTime = track == null ? null : track.getEstimatedSpeed(startOfRace);
|
|
| 1456 | - entryDTO.speedOverGroundAtStartOfRaceInKnots = speedAtStartTime == null ? null
|
|
| 1457 | - : speedAtStartTime.getKnots();
|
|
| 1458 | - TimePoint competitorStartTime = firstMarkPassing.getTimePoint();
|
|
| 1459 | - entryDTO.timeBetweenRaceStartAndCompetitorStartInSeconds = startOfRace.until(competitorStartTime).asSeconds();
|
|
| 1460 | - Speed competitorSpeedWhenPassingStart = track == null ? null : track
|
|
| 1461 | - .getEstimatedSpeed(competitorStartTime);
|
|
| 1462 | - entryDTO.speedOverGroundAtPassingStartWaypointInKnots = competitorSpeedWhenPassingStart == null ? null
|
|
| 1463 | - : competitorSpeedWhenPassingStart.getKnots();
|
|
| 1464 | - try {
|
|
| 1465 | - entryDTO.startTack = trackedRace.getTack(competitor, competitorStartTime);
|
|
| 1466 | - } catch (NoWindException nwe) {
|
|
| 1467 | - entryDTO.startTack = null; // leave empty in case no wind information is available
|
|
| 1468 | - }
|
|
| 1469 | - Distance distanceFromStarboardSideOfStartLineWhenPassingStart = trackedRace
|
|
| 1470 | - .getDistanceFromStarboardSideOfStartLineWhenPassingStart(competitor);
|
|
| 1471 | - entryDTO.distanceToStarboardSideOfStartLineInMeters = distanceFromStarboardSideOfStartLineWhenPassingStart == null ? null
|
|
| 1472 | - : distanceFromStarboardSideOfStartLineWhenPassingStart.getMeters();
|
|
| 1473 | - }
|
|
| 1474 | - }
|
|
| 1475 | - } finally {
|
|
| 1476 | - trackedRace.unlockAfterRead(competitorMarkPassings);
|
|
| 1477 | - }
|
|
| 1478 | - }
|
|
| 1479 | - } catch (InterruptedException e) {
|
|
| 1480 | - throw new RuntimeException(e);
|
|
| 1481 | - } catch (ExecutionException e) {
|
|
| 1482 | - throw new RuntimeException(e); // the future used to calculate the leg details was interrupted; escalate as runtime exception
|
|
| 1483 | - }
|
|
| 1484 | - }
|
|
| 1485 | - final Fleet fleet = entry.getFleet();
|
|
| 1486 | - entryDTO.fleet = fleet == null ? null : baseDomainFactory.convertToFleetDTO(fleet);
|
|
| 1487 | - return entryDTO;
|
|
| 1488 | - }
|
|
| 1489 | -
|
|
| 1490 | - /**
|
|
| 1491 | - * Determines the time point of the last raw fix (with outliers not removed) for <code>competitor</code> in
|
|
| 1492 | - * <code>trackedRace</code>. If the competitor's track is <code>null</code> or empty, <code>null</code> is returned.
|
|
| 1493 | - * @param trackedRace must not be <code>null</code>
|
|
| 1494 | - * @param atOrBefore find the last fix at or before the time point specified
|
|
| 1495 | - */
|
|
| 1496 | - private Date getTimePointOfLastFixAtOrBefore(Competitor competitor, TrackedRace trackedRace, TimePoint atOrBefore) {
|
|
| 1497 | - assert trackedRace != null;
|
|
| 1498 | - final Date timePointOfLastPositionFix;
|
|
| 1499 | - GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(competitor);
|
|
| 1500 | - if (track == null) {
|
|
| 1501 | - timePointOfLastPositionFix = null;
|
|
| 1502 | - } else {
|
|
| 1503 | - GPSFixMoving lastFix = track.getLastFixAtOrBefore(atOrBefore);
|
|
| 1504 | - if (lastFix == null) {
|
|
| 1505 | - timePointOfLastPositionFix = null;
|
|
| 1506 | - } else {
|
|
| 1507 | - timePointOfLastPositionFix = lastFix.getTimePoint().asDate();
|
|
| 1508 | - }
|
|
| 1509 | - }
|
|
| 1510 | - return timePointOfLastPositionFix;
|
|
| 1511 | - }
|
|
| 1512 | -
|
|
| 1513 | - private static class RaceDetails {
|
|
| 1514 | - private final List<LegEntryDTO> legDetails;
|
|
| 1515 | - private final Distance windwardDistanceToCompetitorFarthestAhead;
|
|
| 1516 | - private final Distance averageAbsoluteCrossTrackError;
|
|
| 1517 | - private final Distance averageSignedCrossTrackError;
|
|
| 1518 | - private final Duration gapToLeaderInOwnTime;
|
|
| 1519 | - private final Duration correctedTime;
|
|
| 1520 | - private final Duration correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead;
|
|
| 1521 | -
|
|
| 1522 | - public RaceDetails(List<LegEntryDTO> legDetails, Distance windwardDistanceToCompetitorFarthestAhead,
|
|
| 1523 | - Distance averageAbsoluteCrossTrackError, Distance averageSignedCrossTrackError,
|
|
| 1524 | - Duration gapToLeaderInOwnTime, Duration correctedTime,
|
|
| 1525 | - Duration correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead) {
|
|
| 1526 | - super();
|
|
| 1527 | - this.legDetails = legDetails;
|
|
| 1528 | - this.windwardDistanceToCompetitorFarthestAhead = windwardDistanceToCompetitorFarthestAhead;
|
|
| 1529 | - this.averageAbsoluteCrossTrackError = averageAbsoluteCrossTrackError;
|
|
| 1530 | - this.averageSignedCrossTrackError = averageSignedCrossTrackError;
|
|
| 1531 | - this.gapToLeaderInOwnTime = gapToLeaderInOwnTime;
|
|
| 1532 | - this.correctedTime = correctedTime;
|
|
| 1533 | - this.correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead = correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead;
|
|
| 1534 | - }
|
|
| 1535 | - public List<LegEntryDTO> getLegDetails() {
|
|
| 1536 | - return legDetails;
|
|
| 1537 | - }
|
|
| 1538 | - public Distance getWindwardDistanceToCompetitorFarthestAhead() {
|
|
| 1539 | - return windwardDistanceToCompetitorFarthestAhead;
|
|
| 1540 | - }
|
|
| 1541 | - public Distance getAverageAbsoluteCrossTrackError() {
|
|
| 1542 | - return averageAbsoluteCrossTrackError;
|
|
| 1543 | - }
|
|
| 1544 | - public Distance getAverageSignedCrossTrackError() {
|
|
| 1545 | - return averageSignedCrossTrackError;
|
|
| 1546 | - }
|
|
| 1547 | - public Duration getGapToLeaderInOwnTime() {
|
|
| 1548 | - return gapToLeaderInOwnTime;
|
|
| 1549 | - }
|
|
| 1550 | - public Duration getCorrectedTime() {
|
|
| 1551 | - return correctedTime;
|
|
| 1552 | - }
|
|
| 1553 | - public Duration getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead() {
|
|
| 1554 | - return correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead;
|
|
| 1555 | - }
|
|
| 1556 | - }
|
|
| 1557 | -
|
|
| 1558 | - /**
|
|
| 1559 | - * If <code>timePoint</code> is after the end of the race's tracking the query will be adjusted to obtain the values
|
|
| 1560 | - * at the end of the {@link TrackedRace#getEndOfTracking() race's tracking time}. If the time point adjusted this
|
|
| 1561 | - * way equals the end of the tracking time, the query results will be looked up in a cache first and if not found,
|
|
| 1562 | - * they will be stored to the cache after calculating them. A cache invalidation {@link RaceChangeListener listener}
|
|
| 1563 | - * will be registered with the race which will be triggered for any event received by the race.
|
|
| 1564 | - * @param waitForLatestAnalyses
|
|
| 1565 | - * if <code>false</code>, this method is allowed to read the maneuver analysis results from a cache that
|
|
| 1566 | - * may not reflect all data already received; otherwise, the method will always block for the latest
|
|
| 1567 | - * cache updates to have happened before returning.
|
|
| 1568 | - */
|
|
| 1569 | - private RaceDetails getRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint,
|
|
| 1570 | - boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache,
|
|
| 1571 | - RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) throws InterruptedException, ExecutionException {
|
|
| 1572 | - final RaceDetails raceDetails;
|
|
| 1573 | - if (trackedRace.getEndOfTracking() != null && trackedRace.getEndOfTracking().compareTo(timePoint) < 0) {
|
|
| 1574 | - raceDetails = getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(trackedRace, competitor, legRanksCache, rankingInfo, cache);
|
|
| 1575 | - } else {
|
|
| 1576 | - raceDetails = calculateRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses, legRanksCache, cache, rankingInfo);
|
|
| 1577 | - }
|
|
| 1578 | - return raceDetails;
|
|
| 1579 | - }
|
|
| 1580 | -
|
|
| 1581 | - private RaceDetails getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(final TrackedRace trackedRace,
|
|
| 1582 | - final Competitor competitor, final Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache,
|
|
| 1583 | - RankingInfo rankingInfo, final WindLegTypeAndLegBearingCache cache) throws InterruptedException, ExecutionException {
|
|
| 1584 | - final com.sap.sse.common.Util.Pair<TrackedRace, Competitor> key = new com.sap.sse.common.Util.Pair<TrackedRace, Competitor>(trackedRace, competitor);
|
|
| 1585 | - RunnableFuture<RaceDetails> raceDetails;
|
|
| 1586 | - synchronized (raceDetailsAtEndOfTrackingCache) {
|
|
| 1587 | - raceDetails = raceDetailsAtEndOfTrackingCache.get(key);
|
|
| 1588 | - if (raceDetails == null) {
|
|
| 1589 | - raceDetails = new FutureTask<RaceDetails>(new Callable<RaceDetails>() {
|
|
| 1590 | - @Override
|
|
| 1591 | - public RaceDetails call() throws Exception {
|
|
| 1592 | - TimePoint end = trackedRace.getEndOfRace();
|
|
| 1593 | - if (end == null) {
|
|
| 1594 | - end = trackedRace.getEndOfTracking();
|
|
| 1595 | - }
|
|
| 1596 | - return calculateRaceDetails(trackedRace, competitor, end,
|
|
| 1597 | - // TODO see bug 1358: for now, use waitForLatest==false until we've switched to optimistic locking for the course read lock
|
|
| 1598 | - /* TODO old comment when it was still true: "because this is done only once after end of tracking" */
|
|
| 1599 | - /* waitForLatestAnalyses (maneuver and cross track error) */ false,
|
|
| 1600 | - legRanksCache, cache, rankingInfo);
|
|
| 1601 | - }
|
|
| 1602 | - });
|
|
| 1603 | - raceDetailsExecutor.execute(raceDetails);
|
|
| 1604 | - raceDetailsAtEndOfTrackingCache.put(key, raceDetails);
|
|
| 1605 | - final CacheInvalidationListener cacheInvalidationListener = new CacheInvalidationListener(trackedRace, competitor);
|
|
| 1606 | - trackedRace.addListener(cacheInvalidationListener);
|
|
| 1607 | - cacheInvalidationListeners.add(cacheInvalidationListener);
|
|
| 1608 | - }
|
|
| 1609 | - }
|
|
| 1610 | - return raceDetails.get();
|
|
| 1611 | - }
|
|
| 1612 | -
|
|
| 1613 | - private RaceDetails calculateRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint,
|
|
| 1614 | - boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache,
|
|
| 1615 | - WindLegTypeAndLegBearingCache cache, final RankingInfo rankingInfo) {
|
|
| 1616 | - final List<LegEntryDTO> legDetails = new ArrayList<LegEntryDTO>();
|
|
| 1617 | - final Course course = trackedRace.getRace().getCourse();
|
|
| 1618 | - course.lockForRead(); // hold back any course re-configurations while looping over the legs
|
|
| 1619 | - try {
|
|
| 1620 | - for (Leg leg : course.getLegs()) {
|
|
| 1621 | - LegEntryDTO legEntry;
|
|
| 1622 | - // We loop over a copy of the course's legs; during a course change, legs may become "stale," even with
|
|
| 1623 | - // regard to the leg/trackedLeg structures inside the tracked race which is updated by the course change
|
|
| 1624 | - // immediately. That's why we've acquired a read lock for the course above.
|
|
| 1625 | - TrackedLegOfCompetitor trackedLeg = trackedRace.getTrackedLeg(competitor, leg);
|
|
| 1626 | - if (trackedLeg != null && trackedLeg.hasStartedLeg(timePoint)) {
|
|
| 1627 | - legEntry = createLegEntry(trackedLeg, timePoint, waitForLatestAnalyses, legRanksCache, rankingInfo, cache);
|
|
| 1628 | - } else {
|
|
| 1629 | - legEntry = null;
|
|
| 1630 | - }
|
|
| 1631 | - legDetails.add(legEntry);
|
|
| 1632 | - }
|
|
| 1633 | - final Distance windwardDistanceToCompetitorFarthestAhead = trackedRace == null ? null : trackedRace
|
|
| 1634 | - .getWindwardDistanceToCompetitorFarthestAhead(competitor, timePoint, WindPositionMode.LEG_MIDDLE, rankingInfo, cache);
|
|
| 1635 | - Distance averageAbsoluteCrossTrackError;
|
|
| 1636 | - try {
|
|
| 1637 | - averageAbsoluteCrossTrackError = trackedRace == null ? null : trackedRace.getAverageAbsoluteCrossTrackError(
|
|
| 1638 | - competitor, timePoint, waitForLatestAnalyses, cache);
|
|
| 1639 | - } catch (NoWindException nwe) {
|
|
| 1640 | - // without wind information, use null meaning "unknown"
|
|
| 1641 | - averageAbsoluteCrossTrackError = null;
|
|
| 1642 | - }
|
|
| 1643 | - Distance averageSignedCrossTrackError;
|
|
| 1644 | - try {
|
|
| 1645 | - averageSignedCrossTrackError = trackedRace == null ? null : trackedRace.getAverageSignedCrossTrackError(
|
|
| 1646 | - competitor, timePoint, waitForLatestAnalyses, cache);
|
|
| 1647 | - } catch (NoWindException nwe) {
|
|
| 1648 | - // without wind information, use null meaning "unknown"
|
|
| 1649 | - averageSignedCrossTrackError = null;
|
|
| 1650 | - }
|
|
| 1651 | - final CompetitorRankingInfo competitorRankingInfo = rankingInfo.getCompetitorRankingInfo().apply(competitor);
|
|
| 1652 | - return new RaceDetails(legDetails, windwardDistanceToCompetitorFarthestAhead, averageAbsoluteCrossTrackError, averageSignedCrossTrackError,
|
|
| 1653 | - trackedRace.getRankingMetric().getGapToLeaderInOwnTime(rankingInfo, competitor, cache),
|
|
| 1654 | - trackedRace.getRankingMetric().getCorrectedTime(competitor, timePoint),
|
|
| 1655 | - competitorRankingInfo == null ? null : competitorRankingInfo.getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead());
|
|
| 1656 | - } finally {
|
|
| 1657 | - course.unlockAfterRead();
|
|
| 1658 | - }
|
|
| 1659 | - }
|
|
| 1660 | -
|
|
| 1661 | - private LegEntryDTO createLegEntry(TrackedLegOfCompetitor trackedLeg, TimePoint timePoint,
|
|
| 1662 | - boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache,
|
|
| 1663 | - RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) {
|
|
| 1664 | - LegEntryDTO result;
|
|
| 1665 | - final Duration time = trackedLeg.getTime(timePoint);
|
|
| 1666 | - if (trackedLeg == null || time == null) {
|
|
| 1667 | - result = null;
|
|
| 1668 | - } else {
|
|
| 1669 | - result = new LegEntryDTO();
|
|
| 1670 | - try {
|
|
| 1671 | - result.legType = trackedLeg.getTrackedLeg().getLegType(timePoint);
|
|
| 1672 | - } catch (NoWindException nwe) {
|
|
| 1673 | - result.legType = null; // can't determine leg type without wind data
|
|
| 1674 | - }
|
|
| 1675 | - final Speed averageSpeedOverGround = trackedLeg.getAverageSpeedOverGround(timePoint);
|
|
| 1676 | - result.averageSpeedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots();
|
|
| 1677 | - Distance averageAbsoluteCrossTrackError;
|
|
| 1678 | - try {
|
|
| 1679 | - averageAbsoluteCrossTrackError = trackedLeg.getAverageAbsoluteCrossTrackError(timePoint, waitForLatestAnalyses);
|
|
| 1680 | - } catch (NoWindException nwe) {
|
|
| 1681 | - // leave averageAbsoluteCrossTrackError as null, meaning "unknown"
|
|
| 1682 | - averageAbsoluteCrossTrackError = null;
|
|
| 1683 | - }
|
|
| 1684 | - result.averageAbsoluteCrossTrackErrorInMeters = averageAbsoluteCrossTrackError == null ? null : averageAbsoluteCrossTrackError.getMeters();
|
|
| 1685 | - Distance averageSignedCrossTrackError;
|
|
| 1686 | - try {
|
|
| 1687 | - averageSignedCrossTrackError = trackedLeg.getAverageSignedCrossTrackError(timePoint, waitForLatestAnalyses);
|
|
| 1688 | - } catch (NoWindException nwe) {
|
|
| 1689 | - // leave averageSignedCrossTrackError as null, meaning "unknown"
|
|
| 1690 | - averageSignedCrossTrackError = null;
|
|
| 1691 | - }
|
|
| 1692 | - result.averageSignedCrossTrackErrorInMeters = averageSignedCrossTrackError == null ? null : averageSignedCrossTrackError.getMeters();
|
|
| 1693 | - Double speedOverGroundInKnots;
|
|
| 1694 | - if (trackedLeg.hasFinishedLeg(timePoint)) {
|
|
| 1695 | - speedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots();
|
|
| 1696 | - } else {
|
|
| 1697 | - final SpeedWithBearing speedOverGround = trackedLeg.getSpeedOverGround(timePoint);
|
|
| 1698 | - speedOverGroundInKnots = speedOverGround == null ? null : speedOverGround.getKnots();
|
|
| 1699 | - }
|
|
| 1700 | - result.currentSpeedOverGroundInKnots = speedOverGroundInKnots == null ? null : speedOverGroundInKnots;
|
|
| 1701 | - Distance distanceTraveled = trackedLeg.getDistanceTraveled(timePoint);
|
|
| 1702 | - result.distanceTraveledInMeters = distanceTraveled == null ? null : distanceTraveled.getMeters();
|
|
| 1703 | - Distance distanceTraveledConsideringGateStart = trackedLeg.getDistanceTraveledConsideringGateStart(timePoint);
|
|
| 1704 | - result.distanceTraveledIncludingGateStartInMeters = distanceTraveledConsideringGateStart == null ? null : distanceTraveledConsideringGateStart.getMeters();
|
|
| 1705 | - final Duration estimatedTimeToNextMarkInSeconds = trackedLeg.getEstimatedTimeToNextMark(timePoint, WindPositionMode.EXACT, cache);
|
|
| 1706 | - result.estimatedTimeToNextWaypointInSeconds = estimatedTimeToNextMarkInSeconds==null?null:estimatedTimeToNextMarkInSeconds.asSeconds();
|
|
| 1707 | - result.timeInMilliseconds = time.asMillis();
|
|
| 1708 | - result.finished = trackedLeg.hasFinishedLeg(timePoint);
|
|
| 1709 | - final TimePoint legFinishTime = trackedLeg.getFinishTime();
|
|
| 1710 | - result.correctedTotalTime = trackedLeg.hasStartedLeg(timePoint) ? trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric().getCorrectedTime(trackedLeg.getCompetitor(),
|
|
| 1711 | - trackedLeg.hasFinishedLeg(timePoint) ? legFinishTime : timePoint, cache) : null;
|
|
| 1712 | - // fetch the leg gap in own corrected time from the ranking metric
|
|
| 1713 | - final Duration gapToLeaderInOwnTime = trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric().
|
|
| 1714 | - getLegGapToLegLeaderInOwnTime(trackedLeg, timePoint, rankingInfo, cache);
|
|
| 1715 | - result.gapToLeaderInSeconds = gapToLeaderInOwnTime == null ? null : gapToLeaderInOwnTime.asSeconds();
|
|
| 1716 | - if (result.gapToLeaderInSeconds != null) {
|
|
| 1717 | - final Duration gapAtEndOfPreviousLeg = getGapAtEndOfPreviousLeg(trackedLeg, rankingInfo, cache);
|
|
| 1718 | - if (gapAtEndOfPreviousLeg != null) {
|
|
| 1719 | - result.gapChangeSinceLegStartInSeconds = result.gapToLeaderInSeconds - gapAtEndOfPreviousLeg.asSeconds();
|
|
| 1720 | - }
|
|
| 1721 | - }
|
|
| 1722 | - LinkedHashMap<Competitor, Integer> legRanks = legRanksCache.get(trackedLeg.getLeg());
|
|
| 1723 | - if (legRanks != null) {
|
|
| 1724 | - result.rank = legRanks.get(trackedLeg.getCompetitor());
|
|
| 1725 | - } else {
|
|
| 1726 | - result.rank = trackedLeg.getRank(timePoint, cache);
|
|
| 1727 | - }
|
|
| 1728 | - result.started = trackedLeg.hasStartedLeg(timePoint);
|
|
| 1729 | - Speed velocityMadeGood;
|
|
| 1730 | - if (trackedLeg.hasFinishedLeg(timePoint)) {
|
|
| 1731 | - velocityMadeGood = trackedLeg.getAverageVelocityMadeGood(timePoint);
|
|
| 1732 | - } else {
|
|
| 1733 | - velocityMadeGood = trackedLeg.getVelocityMadeGood(timePoint, WindPositionMode.EXACT);
|
|
| 1734 | - }
|
|
| 1735 | - result.velocityMadeGoodInKnots = velocityMadeGood == null ? null : velocityMadeGood.getKnots();
|
|
| 1736 | - Distance windwardDistanceToGo = trackedLeg.getWindwardDistanceToGo(timePoint, WindPositionMode.LEG_MIDDLE);
|
|
| 1737 | - result.windwardDistanceToGoInMeters = windwardDistanceToGo == null ? null : windwardDistanceToGo
|
|
| 1738 | - .getMeters();
|
|
| 1739 | - final TimePoint startOfRace = trackedLeg.getTrackedLeg().getTrackedRace().getStartOfRace();
|
|
| 1740 | - if (startOfRace != null && trackedLeg.hasStartedLeg(timePoint)) {
|
|
| 1741 | - // not using trackedLeg.getManeuvers(...) because it may not catch the mark passing maneuver starting this leg
|
|
| 1742 | - // because that may have been detected as slightly before the mark passing time, hence associated with the previous leg
|
|
| 1743 | - List<Maneuver> maneuvers = trackedLeg.getTrackedLeg().getTrackedRace()
|
|
| 1744 | - .getManeuvers(trackedLeg.getCompetitor(), startOfRace, timePoint, waitForLatestAnalyses);
|
|
| 1745 | - if (maneuvers != null) {
|
|
| 1746 | - result.numberOfManeuvers = new HashMap<ManeuverType, Integer>();
|
|
| 1747 | - result.numberOfManeuvers.put(ManeuverType.TACK, 0);
|
|
| 1748 | - result.numberOfManeuvers.put(ManeuverType.JIBE, 0);
|
|
| 1749 | - result.numberOfManeuvers.put(ManeuverType.PENALTY_CIRCLE, 0);
|
|
| 1750 | - Map<ManeuverType, Double> totalManeuverLossInMeters = new HashMap<ManeuverType, Double>();
|
|
| 1751 | - totalManeuverLossInMeters.put(ManeuverType.TACK, 0.0);
|
|
| 1752 | - totalManeuverLossInMeters.put(ManeuverType.JIBE, 0.0);
|
|
| 1753 | - totalManeuverLossInMeters.put(ManeuverType.PENALTY_CIRCLE, 0.0);
|
|
| 1754 | - TimePoint startOfLeg = trackedLeg.getStartTime();
|
|
| 1755 | - for (Maneuver maneuver : maneuvers) {
|
|
| 1756 | - // don't count maneuvers that were in previous legs
|
|
| 1757 | - switch (maneuver.getType()) {
|
|
| 1758 | - case TACK:
|
|
| 1759 | - case JIBE:
|
|
| 1760 | - case PENALTY_CIRCLE:
|
|
| 1761 | - if (!maneuver.getTimePoint().before(startOfLeg) && (legFinishTime == null || legFinishTime.after(timePoint) ||
|
|
| 1762 | - maneuver.getTimePoint().before(legFinishTime))) {
|
|
| 1763 | - if (maneuver.getManeuverLoss() != null) {
|
|
| 1764 | - result.numberOfManeuvers.put(maneuver.getType(),
|
|
| 1765 | - result.numberOfManeuvers.get(maneuver.getType()) + 1);
|
|
| 1766 | - totalManeuverLossInMeters.put(maneuver.getType(),
|
|
| 1767 | - totalManeuverLossInMeters.get(maneuver.getType())
|
|
| 1768 | - + maneuver.getManeuverLoss().getMeters());
|
|
| 1769 | - }
|
|
| 1770 | - }
|
|
| 1771 | - break;
|
|
| 1772 | - case MARK_PASSING:
|
|
| 1773 | - // analyze all mark passings, not only those after this leg's start, to catch the mark passing
|
|
| 1774 | - // maneuver starting this leg, even if its time point is slightly before the mark passing starting this leg
|
|
| 1775 | - MarkPassingManeuver mpm = (MarkPassingManeuver) maneuver;
|
|
| 1776 | - if (mpm.getWaypointPassed() == trackedLeg.getLeg().getFrom()) {
|
|
| 1777 | - result.sideToWhichMarkAtLegStartWasRounded = mpm.getSide();
|
|
| 1778 | - }
|
|
| 1779 | - break;
|
|
| 1780 | - default:
|
|
| 1781 | - /* Do nothing here.
|
|
| 1782 | - * Throwing an exception destroys the toggling (and maybe other behaviour) of the leaderboard.
|
|
| 1783 | - */
|
|
| 1784 | - }
|
|
| 1785 | - }
|
|
| 1786 | - result.averageManeuverLossInMeters = new HashMap<ManeuverType, Double>();
|
|
| 1787 | - for (ManeuverType maneuverType : new ManeuverType[] { ManeuverType.TACK, ManeuverType.JIBE,
|
|
| 1788 | - ManeuverType.PENALTY_CIRCLE }) {
|
|
| 1789 | - if (result.numberOfManeuvers.get(maneuverType) != 0) {
|
|
| 1790 | - result.averageManeuverLossInMeters.put(
|
|
| 1791 | - maneuverType,
|
|
| 1792 | - totalManeuverLossInMeters.get(maneuverType)
|
|
| 1793 | - / result.numberOfManeuvers.get(maneuverType));
|
|
| 1794 | - }
|
|
| 1795 | - }
|
|
| 1796 | - }
|
|
| 1797 | - }
|
|
| 1798 | - }
|
|
| 1799 | - return result;
|
|
| 1800 | - }
|
|
| 1801 | -
|
|
| 1802 | - private Duration getGapAtEndOfPreviousLeg(TrackedLegOfCompetitor trackedLeg, final RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) {
|
|
| 1803 | - final Duration result;
|
|
| 1804 | - final Course course = trackedLeg.getTrackedLeg().getTrackedRace().getRace().getCourse();
|
|
| 1805 | - // if trackedLeg is the first leg, compute the gap at the start of this leg; otherwise, compute gap
|
|
| 1806 | - // at the end of the previous leg
|
|
| 1807 | - final TimePoint timePoint = trackedLeg.getStartTime();
|
|
| 1808 | - final TrackedLegOfCompetitor tloc;
|
|
| 1809 | - if (course.getFirstWaypoint() == trackedLeg.getLeg().getFrom()) {
|
|
| 1810 | - tloc = trackedLeg;
|
|
| 1811 | - } else {
|
|
| 1812 | - tloc = trackedLeg.getTrackedLeg().getTrackedRace().getTrackedLegFinishingAt(trackedLeg.getLeg().getFrom())
|
|
| 1813 | - .getTrackedLeg(trackedLeg.getCompetitor());
|
|
| 1814 | - }
|
|
| 1815 | - result = trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric().getLegGapToLegLeaderInOwnTime(tloc, timePoint, rankingInfo, cache);
|
|
| 1816 | - return result;
|
|
| 1817 | - }
|
|
| 1818 | -
|
|
| 1819 | - private LeaderboardDTO getLiveLeaderboard(Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails,
|
|
| 1820 | - boolean addOverallDetails, TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException, ExecutionException {
|
|
| 1821 | - LiveLeaderboardUpdater liveLeaderboardUpdater = getLiveLeaderboardUpdater(trackedRegattaRegistry,
|
|
| 1822 | - baseDomainFactory);
|
|
| 1823 | - return liveLeaderboardUpdater.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails);
|
|
| 1824 | - }
|
|
| 1825 | -
|
|
| 1826 | - private LiveLeaderboardUpdater getLiveLeaderboardUpdater(TrackedRegattaRegistry trackedRegattaRegistry,
|
|
| 1827 | - DomainFactory baseDomainFactory) {
|
|
| 1828 | - LiveLeaderboardUpdater result = this.liveLeaderboardUpdater;
|
|
| 1829 | - if (result == null) {
|
|
| 1830 | - synchronized (this) {
|
|
| 1831 | - result = this.liveLeaderboardUpdater;
|
|
| 1832 | - if (result == null) {
|
|
| 1833 | - this.liveLeaderboardUpdater = new LiveLeaderboardUpdater(this, trackedRegattaRegistry, baseDomainFactory);
|
|
| 1834 | - result = this.liveLeaderboardUpdater;
|
|
| 1835 | - }
|
|
| 1836 | - }
|
|
| 1837 | - }
|
|
| 1838 | - return result;
|
|
| 1839 | - }
|
|
| 1840 | -
|
|
| 1841 | - private LeaderboardDTOCache getLeaderboardDTOCache() {
|
|
| 1842 | - LeaderboardDTOCache result = this.leaderboardDTOCache;
|
|
| 1843 | - if (result == null) {
|
|
| 1844 | - synchronized (this) {
|
|
| 1845 | - result = this.leaderboardDTOCache;
|
|
| 1846 | - if (result == null) {
|
|
| 1847 | - // The leaderboard cache is invalidated upon all competitor and mark position changes; some analyzes
|
|
| 1848 | - // are pretty expensive, such as the maneuver re-calculation. Waiting for the latest analysis after only a
|
|
| 1849 | - // single fix was updated is too expensive if users use the replay feature while a race is still running.
|
|
| 1850 | - // Therefore, using waitForLatestAnalyses==false seems appropriate here.
|
|
| 1851 | - this.leaderboardDTOCache = new LeaderboardDTOCache(/* waitForLatestAnalyses */false, this);
|
|
| 1852 | - result = this.leaderboardDTOCache;
|
|
| 1853 | - }
|
|
| 1854 | - }
|
|
| 1855 | - }
|
|
| 1856 | - return result;
|
|
| 1857 | - }
|
|
| 1858 | -
|
|
| 1859 | - @Override
|
|
| 1860 | - public LeaderboardDTO getLeaderboardDTO(TimePoint timePoint,
|
|
| 1861 | - Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails,
|
|
| 1862 | - TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory, boolean fillNetPointsUncorrected) throws NoWindException,
|
|
| 1863 | - InterruptedException, ExecutionException {
|
|
| 1864 | - LeaderboardDTO result = null;
|
|
| 1865 | - if (timePoint == null) {
|
|
| 1866 | - // timePoint==null means live mode; however, if we're after the end of all races and after all score
|
|
| 1867 | - // corrections, don't use the live leaderboard updater which would keep re-calculating over and over again, but map
|
|
| 1868 | - // this to a usual non-live call which uses the regular LeaderboardDTOCache which is invalidated properly
|
|
| 1869 | - // when the tracked race associations or score corrections or tracked race contents changes:
|
|
| 1870 | - final TimePoint nowMinusDelay = this.getNowMinusDelay();
|
|
| 1871 | - final TimePoint timePointOfLatestModification = this.getTimePointOfLatestModification();
|
|
| 1872 | - if (fillNetPointsUncorrected || (timePointOfLatestModification != null && !nowMinusDelay.before(timePointOfLatestModification))) {
|
|
| 1873 | - // if there hasn't been any modification to the leaderboard since nowMinusDelay, use non-live mode
|
|
| 1874 | - // and pull the result from the regular leaderboard cache:
|
|
| 1875 | - timePoint = timePointOfLatestModification;
|
|
| 1876 | - } else {
|
|
| 1877 | - // don't use the regular leaderboard cache; the race still seems to be on; use the live leaderboard updater instead:
|
|
| 1878 | - timePoint = null;
|
|
| 1879 | - result = this.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, trackedRegattaRegistry, baseDomainFactory);
|
|
| 1880 | - }
|
|
| 1881 | - }
|
|
| 1882 | - if (timePoint != null) {
|
|
| 1883 | - if (fillNetPointsUncorrected) {
|
|
| 1884 | - // explicitly filling the uncorrected net points requires uncached recalculation
|
|
| 1885 | - result = computeDTO(timePoint, namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, /* waitForLatestAnalyses */ true,
|
|
| 1886 | - trackedRegattaRegistry, baseDomainFactory, fillNetPointsUncorrected);
|
|
| 1887 | - } else {
|
|
| 1888 | - // in replay we'd like up-to-date results; they are still cached
|
|
| 1889 | - // which is OK because the cache is invalidated whenever any of the tracked races attached to the
|
|
| 1890 | - // leaderboard changes.
|
|
| 1891 | - result = getLeaderboardDTOCache().getLeaderboardByName(timePoint,
|
|
| 1892 | - namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, baseDomainFactory,
|
|
| 1893 | - trackedRegattaRegistry);
|
|
| 1894 | - }
|
|
| 1895 | - }
|
|
| 1896 | - return result;
|
|
| 1897 | - }
|
|
| 1898 | -
|
|
| 1899 | - public String toString() {
|
|
| 1900 | - return getName() + " " + (getDefaultCourseArea() != null ? getDefaultCourseArea().getName() : "<No course area defined>") + " " + (getScoringScheme() != null ? getScoringScheme().getType().name() : "<No scoring scheme set>");
|
|
| 1901 | - }
|
|
| 1902 | -
|
|
| 1903 | - @Override
|
|
| 1904 | - public NumberOfCompetitorsInLeaderboardFetcher getNumberOfCompetitorsInLeaderboardFetcher() {
|
|
| 1905 | - return new NumberOfCompetitorsFetcherImpl();
|
|
| 1906 | - }
|
|
| 1907 | -}
|
|
| 1 | +package com.sap.sailing.domain.leaderboard.impl; |
|
| 2 | + |
|
| 3 | +import java.io.IOException; |
|
| 4 | +import java.io.ObjectInputStream; |
|
| 5 | +import java.util.ArrayList; |
|
| 6 | +import java.util.Collection; |
|
| 7 | +import java.util.Collections; |
|
| 8 | +import java.util.Comparator; |
|
| 9 | +import java.util.Date; |
|
| 10 | +import java.util.HashMap; |
|
| 11 | +import java.util.HashSet; |
|
| 12 | +import java.util.Iterator; |
|
| 13 | +import java.util.LinkedHashMap; |
|
| 14 | +import java.util.List; |
|
| 15 | +import java.util.Map; |
|
| 16 | +import java.util.NavigableSet; |
|
| 17 | +import java.util.NoSuchElementException; |
|
| 18 | +import java.util.Set; |
|
| 19 | +import java.util.UUID; |
|
| 20 | +import java.util.concurrent.Callable; |
|
| 21 | +import java.util.concurrent.ExecutionException; |
|
| 22 | +import java.util.concurrent.Executor; |
|
| 23 | +import java.util.concurrent.Executors; |
|
| 24 | +import java.util.concurrent.Future; |
|
| 25 | +import java.util.concurrent.FutureTask; |
|
| 26 | +import java.util.concurrent.LinkedBlockingQueue; |
|
| 27 | +import java.util.concurrent.RunnableFuture; |
|
| 28 | +import java.util.concurrent.ThreadPoolExecutor; |
|
| 29 | +import java.util.concurrent.TimeUnit; |
|
| 30 | +import java.util.logging.Level; |
|
| 31 | +import java.util.logging.Logger; |
|
| 32 | + |
|
| 33 | +import com.sap.sailing.domain.abstractlog.race.InvalidatesLeaderboardCache; |
|
| 34 | +import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; |
|
| 35 | +import com.sap.sailing.domain.base.Competitor; |
|
| 36 | +import com.sap.sailing.domain.base.Course; |
|
| 37 | +import com.sap.sailing.domain.base.DomainFactory; |
|
| 38 | +import com.sap.sailing.domain.base.Fleet; |
|
| 39 | +import com.sap.sailing.domain.base.Leg; |
|
| 40 | +import com.sap.sailing.domain.base.RaceColumn; |
|
| 41 | +import com.sap.sailing.domain.base.RaceColumnInSeries; |
|
| 42 | +import com.sap.sailing.domain.base.RaceColumnListener; |
|
| 43 | +import com.sap.sailing.domain.base.Waypoint; |
|
| 44 | +import com.sap.sailing.domain.common.Distance; |
|
| 45 | +import com.sap.sailing.domain.common.LeaderboardType; |
|
| 46 | +import com.sap.sailing.domain.common.LegType; |
|
| 47 | +import com.sap.sailing.domain.common.ManeuverType; |
|
| 48 | +import com.sap.sailing.domain.common.MaxPointsReason; |
|
| 49 | +import com.sap.sailing.domain.common.NoWindError; |
|
| 50 | +import com.sap.sailing.domain.common.NoWindException; |
|
| 51 | +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; |
|
| 52 | +import com.sap.sailing.domain.common.RegattaNameAndRaceName; |
|
| 53 | +import com.sap.sailing.domain.common.Speed; |
|
| 54 | +import com.sap.sailing.domain.common.SpeedWithBearing; |
|
| 55 | +import com.sap.sailing.domain.common.dto.BasicRaceDTO; |
|
| 56 | +import com.sap.sailing.domain.common.dto.CompetitorDTO; |
|
| 57 | +import com.sap.sailing.domain.common.dto.FleetDTO; |
|
| 58 | +import com.sap.sailing.domain.common.dto.LeaderboardDTO; |
|
| 59 | +import com.sap.sailing.domain.common.dto.LeaderboardEntryDTO; |
|
| 60 | +import com.sap.sailing.domain.common.dto.LeaderboardRowDTO; |
|
| 61 | +import com.sap.sailing.domain.common.dto.LegEntryDTO; |
|
| 62 | +import com.sap.sailing.domain.common.dto.MetaLeaderboardRaceColumnDTO; |
|
| 63 | +import com.sap.sailing.domain.common.dto.RaceColumnDTO; |
|
| 64 | +import com.sap.sailing.domain.common.dto.RaceDTO; |
|
| 65 | +import com.sap.sailing.domain.common.tracking.GPSFixMoving; |
|
| 66 | +import com.sap.sailing.domain.leaderboard.Leaderboard; |
|
| 67 | +import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher; |
|
| 68 | +import com.sap.sailing.domain.leaderboard.ResultDiscardingRule; |
|
| 69 | +import com.sap.sailing.domain.leaderboard.ScoreCorrection.Result; |
|
| 70 | +import com.sap.sailing.domain.leaderboard.SettableScoreCorrection; |
|
| 71 | +import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; |
|
| 72 | +import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCache; |
|
| 73 | +import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache; |
|
| 74 | +import com.sap.sailing.domain.leaderboard.caching.LiveLeaderboardUpdater; |
|
| 75 | +import com.sap.sailing.domain.leaderboard.meta.MetaLeaderboardColumn; |
|
| 76 | +import com.sap.sailing.domain.racelog.RaceLogIdentifier; |
|
| 77 | +import com.sap.sailing.domain.ranking.RankingMetric.CompetitorRankingInfo; |
|
| 78 | +import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo; |
|
| 79 | +import com.sap.sailing.domain.tracking.GPSFixTrack; |
|
| 80 | +import com.sap.sailing.domain.tracking.Maneuver; |
|
| 81 | +import com.sap.sailing.domain.tracking.MarkPassing; |
|
| 82 | +import com.sap.sailing.domain.tracking.MarkPassingManeuver; |
|
| 83 | +import com.sap.sailing.domain.tracking.RaceChangeListener; |
|
| 84 | +import com.sap.sailing.domain.tracking.TrackedLeg; |
|
| 85 | +import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; |
|
| 86 | +import com.sap.sailing.domain.tracking.TrackedRace; |
|
| 87 | +import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; |
|
| 88 | +import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache; |
|
| 89 | +import com.sap.sailing.domain.tracking.WindPositionMode; |
|
| 90 | +import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener; |
|
| 91 | +import com.sap.sailing.util.impl.RaceColumnListeners; |
|
| 92 | +import com.sap.sse.common.Duration; |
|
| 93 | +import com.sap.sse.common.TimePoint; |
|
| 94 | +import com.sap.sse.common.Util; |
|
| 95 | +import com.sap.sse.common.impl.MillisecondsTimePoint; |
|
| 96 | +import com.sap.sse.concurrent.LockUtil; |
|
| 97 | +import com.sap.sse.concurrent.NamedReentrantReadWriteLock; |
|
| 98 | +import com.sap.sse.util.impl.ThreadFactoryWithPriority; |
|
| 99 | + |
|
| 100 | +/** |
|
| 101 | + * Base implementation for various types of leaderboards. The {@link RaceColumnListener} implementation forwards events |
|
| 102 | + * received to all {@link RaceColumnListener} subscribed with this leaderboard. To which objects this leaderboard subscribes |
|
| 103 | + * as {@link RaceColumnListener} is left to the concrete subclasses to implement, but the race columns seem like useful |
|
| 104 | + * candidates. |
|
| 105 | + * |
|
| 106 | + * @author Axel Uhl (d043530) |
|
| 107 | + * |
|
| 108 | + */ |
|
| 109 | +public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, RaceColumnListener { |
|
| 110 | + private static final long serialVersionUID = 330156778603279333L; |
|
| 111 | + |
|
| 112 | + private static final Logger logger = Logger.getLogger(AbstractSimpleLeaderboardImpl.class.getName()); |
|
| 113 | + |
|
| 114 | + static final Double DOUBLE_0 = new Double(0); |
|
| 115 | + |
|
| 116 | + private final SettableScoreCorrection scoreCorrection; |
|
| 117 | + |
|
| 118 | + private ThresholdBasedResultDiscardingRule crossLeaderboardResultDiscardingRule; |
|
| 119 | + |
|
| 120 | + /** |
|
| 121 | + * The optional display name mappings for competitors. This allows a user to override the tracking-provided |
|
| 122 | + * competitor names for display in a leaderboard. |
|
| 123 | + */ |
|
| 124 | + private final Map<Competitor, String> displayNames; |
|
| 125 | + |
|
| 126 | + /** the display name of the leaderboard */ |
|
| 127 | + private String displayName; |
|
| 128 | + |
|
| 129 | + /** |
|
| 130 | + * Backs the {@link #getCarriedPoints(Competitor)} API with data. Can be used to prime this leaderboard |
|
| 131 | + * with aggregated results of races not tracked / displayed by this leaderboard in detail. The points |
|
| 132 | + * provided by this map are considered by {@link #getTotalPoints(Competitor, TimePoint)}. |
|
| 133 | + */ |
|
| 134 | + private final Map<Competitor, Double> carriedPoints; |
|
| 135 | + |
|
| 136 | + private final RaceColumnListeners raceColumnListeners; |
|
| 137 | + |
|
| 138 | + /** |
|
| 139 | + * A set that manages the difference between {@link #getCompetitors()} and {@link #getAllCompetitors()}. Access |
|
| 140 | + * is controlled by the {@link #suppressedCompetitorsLock} lock. |
|
| 141 | + */ |
|
| 142 | + private final Set<Competitor> suppressedCompetitors; |
|
| 143 | + private final NamedReentrantReadWriteLock suppressedCompetitorsLock; |
|
| 144 | + |
|
| 145 | + private transient Map<com.sap.sse.common.Util.Pair<TrackedRace, Competitor>, RunnableFuture<RaceDetails>> raceDetailsAtEndOfTrackingCache; |
|
| 146 | + |
|
| 147 | + /** |
|
| 148 | + * This executor needs to be a different one than {@link #executor} because the tasks run by {@link #executor} |
|
| 149 | + * can depend on the results of the tasks run by {@link #raceDetailsExecutor}, and an {@link Executor} doesn't |
|
| 150 | + * move a task that is blocked by waiting for another {@link FutureTask} to the side but blocks permanently, |
|
| 151 | + * ending in a deadlock (one that cannot easily be detected by the Eclipse debugger either). |
|
| 152 | + */ |
|
| 153 | + private transient Executor raceDetailsExecutor; |
|
| 154 | + |
|
| 155 | + /** |
|
| 156 | + * Used to remove all these listeners from their tracked races when this servlet is {@link #destroy() destroyed}. |
|
| 157 | + */ |
|
| 158 | + private transient Set<CacheInvalidationListener> cacheInvalidationListeners; |
|
| 159 | + |
|
| 160 | + private transient ThreadPoolExecutor executor; |
|
| 161 | + |
|
| 162 | + private transient LiveLeaderboardUpdater liveLeaderboardUpdater; |
|
| 163 | + |
|
| 164 | + private transient LeaderboardDTOCache leaderboardDTOCache; |
|
| 165 | + |
|
| 166 | + /** |
|
| 167 | + * A leaderboard entry representing a snapshot of a cell at a given time point for a single race/competitor. |
|
| 168 | + * |
|
| 169 | + * @author Axel Uhl (d043530) |
|
| 170 | + * |
|
| 171 | + */ |
|
| 172 | + class EntryImpl implements Entry { |
|
| 173 | + private final Callable<Integer> trackedRankProvider; |
|
| 174 | + private final Double netPoints; |
|
| 175 | + private final Callable<Double> netPointsUncorrectedProvider; |
|
| 176 | + private final boolean isNetPointsCorrected; |
|
| 177 | + private final Double totalPoints; |
|
| 178 | + private final MaxPointsReason maxPointsReason; |
|
| 179 | + private final boolean discarded; |
|
| 180 | + private final Fleet fleet; |
|
| 181 | + |
|
| 182 | + private EntryImpl(Callable<Integer> trackedRankProvider, Double netPoints, |
|
| 183 | + Callable<Double> netPointsUncorrectedProvider, boolean isNetPointsCorrected, |
|
| 184 | + Double totalPoints, MaxPointsReason maxPointsReason, boolean discarded, Fleet fleet) { |
|
| 185 | + super(); |
|
| 186 | + this.trackedRankProvider = trackedRankProvider; |
|
| 187 | + this.netPoints = netPoints; |
|
| 188 | + this.netPointsUncorrectedProvider = netPointsUncorrectedProvider; |
|
| 189 | + this.isNetPointsCorrected = isNetPointsCorrected; |
|
| 190 | + this.totalPoints = totalPoints; |
|
| 191 | + this.maxPointsReason = maxPointsReason; |
|
| 192 | + this.discarded = discarded; |
|
| 193 | + this.fleet = fleet; |
|
| 194 | + } |
|
| 195 | + @Override |
|
| 196 | + public int getTrackedRank() { |
|
| 197 | + try { |
|
| 198 | + return trackedRankProvider.call(); |
|
| 199 | + } catch (Exception e) { |
|
| 200 | + throw new RuntimeException(e); |
|
| 201 | + } |
|
| 202 | + } |
|
| 203 | + @Override |
|
| 204 | + public Double getNetPoints() { |
|
| 205 | + return netPoints; |
|
| 206 | + } |
|
| 207 | + @Override |
|
| 208 | + public boolean isNetPointsCorrected() { |
|
| 209 | + return isNetPointsCorrected; |
|
| 210 | + } |
|
| 211 | + @Override |
|
| 212 | + public Double getTotalPoints() { |
|
| 213 | + return totalPoints; |
|
| 214 | + } |
|
| 215 | + @Override |
|
| 216 | + public MaxPointsReason getMaxPointsReason() { |
|
| 217 | + return maxPointsReason; |
|
| 218 | + } |
|
| 219 | + @Override |
|
| 220 | + public boolean isDiscarded() { |
|
| 221 | + return discarded; |
|
| 222 | + } |
|
| 223 | + @Override |
|
| 224 | + public Fleet getFleet() { |
|
| 225 | + return fleet; |
|
| 226 | + } |
|
| 227 | + @Override |
|
| 228 | + public Double getNetPointsUncorrected() { |
|
| 229 | + try { |
|
| 230 | + return netPointsUncorrectedProvider.call(); |
|
| 231 | + } catch (Exception e) { |
|
| 232 | + throw new RuntimeException(e); |
|
| 233 | + } |
|
| 234 | + } |
|
| 235 | + } |
|
| 236 | + |
|
| 237 | + /** |
|
| 238 | + * Computing the competitors can be a bit expensive, particularly if the fleet is large and there may be suppressed |
|
| 239 | + * competitors, and the leaderboard may be a meta-leaderboard that refers to other leaderboards which each have |
|
| 240 | + * several tracked races attached from where the competitors need to be retrieved. Ideally, the competitors list |
|
| 241 | + * would be cached, but that is again difficult because we would have to monitor all changes in all dependent |
|
| 242 | + * leaderboards and columns and tracked races properly. |
|
| 243 | + * <p> |
|
| 244 | + * |
|
| 245 | + * As it turns out, one of the most frequent uses of the {@link AbstractSimpleLeaderboardImpl#getCompetitors} |
|
| 246 | + * competitors list is to determine their number which in turn is only required for high-point scoring systems and |
|
| 247 | + * for computing the default score for penalties. Again, the most frequently used low-point family of scoring schemes |
|
| 248 | + * does not require this number. Yet, the scoring scheme requires an argument for polymorphic use by those that |
|
| 249 | + * need it. Instead of computing it for each call, this interface lets us defer the actual calculation until the |
|
| 250 | + * point when it's really needed. Once asked, this object will cache the result. Therefore, a new one should be |
|
| 251 | + * constructed each time the number shall be computed. |
|
| 252 | + * |
|
| 253 | + * @author Axel Uhl (D043530) |
|
| 254 | + * |
|
| 255 | + */ |
|
| 256 | + public class NumberOfCompetitorsFetcherImpl implements NumberOfCompetitorsInLeaderboardFetcher { |
|
| 257 | + private int numberOfCompetitors = -1; |
|
| 258 | + |
|
| 259 | + @Override |
|
| 260 | + public int getNumberOfCompetitorsInLeaderboard() { |
|
| 261 | + if (numberOfCompetitors == -1) { |
|
| 262 | + numberOfCompetitors = Util.size(getCompetitors()); |
|
| 263 | + } |
|
| 264 | + return numberOfCompetitors; |
|
| 265 | + } |
|
| 266 | + } |
|
| 267 | + |
|
| 268 | + /** |
|
| 269 | + * Handles the invalidation of the {@link SailingServiceImpl#raceDetailsAtEndOfTrackingCache} entries if the tracked |
|
| 270 | + * race changes in any way. In particular, for {@link #statusChanged}, when the status changes away from LOADING, |
|
| 271 | + * calculations may start or resume, making it necessary to clear the cache. |
|
| 272 | + * |
|
| 273 | + * @author Axel Uhl (D043530) |
|
| 274 | + * |
|
| 275 | + */ |
|
| 276 | + private class CacheInvalidationListener extends AbstractRaceChangeListener { |
|
| 277 | + private final TrackedRace trackedRace; |
|
| 278 | + private final Competitor competitor; |
|
| 279 | + |
|
| 280 | + public CacheInvalidationListener(TrackedRace trackedRace, Competitor competitor) { |
|
| 281 | + this.trackedRace = trackedRace; |
|
| 282 | + this.competitor = competitor; |
|
| 283 | + } |
|
| 284 | + |
|
| 285 | + public TrackedRace getTrackedRace() { |
|
| 286 | + return trackedRace; |
|
| 287 | + } |
|
| 288 | + |
|
| 289 | + public void removeFromTrackedRace() { |
|
| 290 | + trackedRace.removeListener(this); |
|
| 291 | + } |
|
| 292 | + |
|
| 293 | + private void invalidateCacheAndRemoveThisListenerFromTrackedRace() { |
|
| 294 | + synchronized (raceDetailsAtEndOfTrackingCache) { |
|
| 295 | + raceDetailsAtEndOfTrackingCache.remove(new com.sap.sse.common.Util.Pair<TrackedRace, Competitor>(trackedRace, competitor)); |
|
| 296 | + removeFromTrackedRace(); |
|
| 297 | + } |
|
| 298 | + } |
|
| 299 | + |
|
| 300 | + @Override |
|
| 301 | + protected void defaultAction() { |
|
| 302 | + invalidateCacheAndRemoveThisListenerFromTrackedRace(); |
|
| 303 | + } |
|
| 304 | + } |
|
| 305 | + |
|
| 306 | + private static class UUIDGenerator implements LeaderboardDTO.UUIDGenerator { |
|
| 307 | + @Override |
|
| 308 | + public String generateRandomUUID() { |
|
| 309 | + return UUID.randomUUID().toString(); |
|
| 310 | + } |
|
| 311 | + } |
|
| 312 | + |
|
| 313 | + public AbstractSimpleLeaderboardImpl(ThresholdBasedResultDiscardingRule resultDiscardingRule) { |
|
| 314 | + this.carriedPoints = new HashMap<Competitor, Double>(); |
|
| 315 | + this.scoreCorrection = createScoreCorrection(); |
|
| 316 | + this.displayNames = new HashMap<Competitor, String>(); |
|
| 317 | + this.crossLeaderboardResultDiscardingRule = resultDiscardingRule; |
|
| 318 | + this.suppressedCompetitors = new HashSet<Competitor>(); |
|
| 319 | + this.suppressedCompetitorsLock = new NamedReentrantReadWriteLock("suppressedCompetitorsLock", /* fair */ false); |
|
| 320 | + this.raceColumnListeners = new RaceColumnListeners(); |
|
| 321 | + this.raceDetailsAtEndOfTrackingCache = new HashMap<com.sap.sse.common.Util.Pair<TrackedRace, Competitor>, RunnableFuture<RaceDetails>>(); |
|
| 322 | + initTransientFields(); |
|
| 323 | + } |
|
| 324 | + |
|
| 325 | + /** |
|
| 326 | + * Produces the score correction object to use in this leaderboard. Used by the constructor. Subclasses may override |
|
| 327 | + * this method to create a more specific type of score correction. This implementation produces an object of type |
|
| 328 | + * {@link ScoreCorrectionImpl}. |
|
| 329 | + */ |
|
| 330 | + protected SettableScoreCorrection createScoreCorrection() { |
|
| 331 | + return new ScoreCorrectionImpl(this); |
|
| 332 | + } |
|
| 333 | + |
|
| 334 | + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { |
|
| 335 | + ois.defaultReadObject(); |
|
| 336 | + initTransientFields(); |
|
| 337 | + } |
|
| 338 | + |
|
| 339 | + private void initTransientFields() { |
|
| 340 | + this.raceDetailsAtEndOfTrackingCache = new HashMap<com.sap.sse.common.Util.Pair<TrackedRace,Competitor>, RunnableFuture<RaceDetails>>(); |
|
| 341 | + this.raceDetailsExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); |
|
| 342 | + this.cacheInvalidationListeners = new HashSet<CacheInvalidationListener>(); |
|
| 343 | + // When many updates are triggered in a short period of time by a single thread, ensure that the single thread |
|
| 344 | + // providing the updates is not outperformed by all the re-calculations happening here. Leave at least one |
|
| 345 | + // core to other things, but by using at least three threads ensure that no simplistic deadlocks may occur. |
|
| 346 | + final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors(), 3); |
|
| 347 | + executor = new ThreadPoolExecutor(/* corePoolSize */ THREAD_POOL_SIZE, |
|
| 348 | + /* maximumPoolSize */ THREAD_POOL_SIZE, |
|
| 349 | + /* keepAliveTime */ 60, TimeUnit.SECONDS, |
|
| 350 | + /* workQueue */ new LinkedBlockingQueue<Runnable>(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY-1, /* daemon */ true)); |
|
| 351 | + } |
|
| 352 | + |
|
| 353 | + @Override |
|
| 354 | + public void destroy() { |
|
| 355 | + for (CacheInvalidationListener cacheInvalidationListener : cacheInvalidationListeners) { |
|
| 356 | + cacheInvalidationListener.removeFromTrackedRace(); |
|
| 357 | + } |
|
| 358 | + } |
|
| 359 | + |
|
| 360 | + @Override |
|
| 361 | + public SettableScoreCorrection getScoreCorrection() { |
|
| 362 | + return scoreCorrection; |
|
| 363 | + } |
|
| 364 | + |
|
| 365 | + @Override |
|
| 366 | + public String getDisplayName(Competitor competitor) { |
|
| 367 | + return displayNames.get(competitor); |
|
| 368 | + } |
|
| 369 | + |
|
| 370 | + @Override |
|
| 371 | + public String getDisplayName() { |
|
| 372 | + return displayName; |
|
| 373 | + } |
|
| 374 | + |
|
| 375 | + @Override |
|
| 376 | + public void setDisplayName(String displayName) { |
|
| 377 | + this.displayName = displayName; |
|
| 378 | + } |
|
| 379 | + |
|
| 380 | + @Override |
|
| 381 | + public ResultDiscardingRule getResultDiscardingRule() { |
|
| 382 | + return crossLeaderboardResultDiscardingRule; |
|
| 383 | + } |
|
| 384 | + |
|
| 385 | + @Override |
|
| 386 | + public void setCarriedPoints(Competitor competitor, double carriedPoints) { |
|
| 387 | + Double oldCarriedPoints = this.carriedPoints.put(competitor, carriedPoints); |
|
| 388 | + if (!Util.equalsWithNull(oldCarriedPoints, carriedPoints)) { |
|
| 389 | + getScoreCorrection().notifyListenersAboutCarriedPointsChange(competitor, oldCarriedPoints, carriedPoints); |
|
| 390 | + } |
|
| 391 | + } |
|
| 392 | + |
|
| 393 | + @Override |
|
| 394 | + public double getCarriedPoints(Competitor competitor) { |
|
| 395 | + Double result = carriedPoints.get(competitor); |
|
| 396 | + return result == null ? 0 : result; |
|
| 397 | + } |
|
| 398 | + |
|
| 399 | + @Override |
|
| 400 | + public Map<Competitor, Double> getCompetitorsForWhichThereAreCarriedPoints() { |
|
| 401 | + return Collections.unmodifiableMap(carriedPoints); |
|
| 402 | + } |
|
| 403 | + |
|
| 404 | + @Override |
|
| 405 | + public void unsetCarriedPoints(Competitor competitor) { |
|
| 406 | + Double oldCarriedPoints = carriedPoints.remove(competitor); |
|
| 407 | + if (oldCarriedPoints != null) { |
|
| 408 | + getScoreCorrection().notifyListenersAboutCarriedPointsChange(competitor, oldCarriedPoints, null); |
|
| 409 | + } |
|
| 410 | + } |
|
| 411 | + |
|
| 412 | + @Override |
|
| 413 | + public boolean hasCarriedPoints() { |
|
| 414 | + return !carriedPoints.isEmpty(); |
|
| 415 | + } |
|
| 416 | + |
|
| 417 | + @Override |
|
| 418 | + public boolean hasCarriedPoints(Competitor competitor) { |
|
| 419 | + return carriedPoints.containsKey(competitor); |
|
| 420 | + } |
|
| 421 | + |
|
| 422 | + @Override |
|
| 423 | + public void setDisplayName(Competitor competitor, String displayName) { |
|
| 424 | + String oldDisplayName = displayNames.get(competitor); |
|
| 425 | + displayNames.put(competitor, displayName); |
|
| 426 | + if (!Util.equalsWithNull(oldDisplayName, displayName)) { |
|
| 427 | + getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName); |
|
| 428 | + } |
|
| 429 | + } |
|
| 430 | + |
|
| 431 | + @Override |
|
| 432 | + public void setCrossLeaderboardResultDiscardingRule(ThresholdBasedResultDiscardingRule discardingRule) { |
|
| 433 | + ResultDiscardingRule oldDiscardingRule = getResultDiscardingRule(); |
|
| 434 | + this.crossLeaderboardResultDiscardingRule = discardingRule; |
|
| 435 | + getRaceColumnListeners().notifyListenersAboutResultDiscardingRuleChanged(oldDiscardingRule, discardingRule); |
|
| 436 | + } |
|
| 437 | + |
|
| 438 | + @Override |
|
| 439 | + public Double getNetPoints(final Competitor competitor, final RaceColumn raceColumn, final TimePoint timePoint) { |
|
| 440 | + return getScoreCorrection().getCorrectedScore( |
|
| 441 | + new Callable<Integer>() { |
|
| 442 | + public Integer call() throws NoWindException { |
|
| 443 | + return getTrackedRank(competitor, raceColumn, timePoint); |
|
| 444 | + } |
|
| 445 | + }, competitor, |
|
| 446 | + raceColumn, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()).getCorrectedScore(); |
|
| 447 | + } |
|
| 448 | + |
|
| 449 | + @Override |
|
| 450 | + public MaxPointsReason getMaxPointsReason(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { |
|
| 451 | + return getScoreCorrection().getMaxPointsReason(competitor, raceColumn, timePoint); |
|
| 452 | + } |
|
| 453 | + |
|
| 454 | + @Override |
|
| 455 | + public boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { |
|
| 456 | + return isDiscarded(competitor, raceColumn, getRaceColumns(), timePoint); |
|
| 457 | + } |
|
| 458 | + |
|
| 459 | + private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, |
|
| 460 | + Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) { |
|
| 461 | + final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule() |
|
| 462 | + .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); |
|
| 463 | + return isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns); |
|
| 464 | + } |
|
| 465 | + |
|
| 466 | + /** |
|
| 467 | + * Same as {@link #isDiscarded(Competitor, RaceColumn, TimePoint)}, only that the set of discarded race columns can |
|
| 468 | + * be specified which is useful when total points are to be computed for more than one column for the same |
|
| 469 | + * competitor because then the calculation of discards (which requires looking at all columns) only needs to be done |
|
| 470 | + * once and not again for each column (which would lead to quadratic effort). |
|
| 471 | + * |
|
| 472 | + * @param discardedRaceColumns |
|
| 473 | + * expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}. |
|
| 474 | + * {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint) |
|
| 475 | + * getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}. |
|
| 476 | + */ |
|
| 477 | + private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, |
|
| 478 | + final Set<RaceColumn> discardedRaceColumns) { |
|
| 479 | + return !raceColumn.isMedalRace() |
|
| 480 | + && getMaxPointsReason(competitor, raceColumn, timePoint).isDiscardable() |
|
| 481 | + && discardedRaceColumns.contains(raceColumn); |
|
| 482 | + } |
|
| 483 | + |
|
| 484 | + @Override |
|
| 485 | + public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) throws NoWindException { |
|
| 486 | + return getTotalPoints(competitor, raceColumn, getRaceColumns(), timePoint); |
|
| 487 | + } |
|
| 488 | + |
|
| 489 | + @Override |
|
| 490 | + public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, |
|
| 491 | + Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) throws NoWindException { |
|
| 492 | + final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule() |
|
| 493 | + .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); |
|
| 494 | + return getTotalPoints(competitor, raceColumn, timePoint, discardedRaceColumns); |
|
| 495 | + } |
|
| 496 | + |
|
| 497 | + /** |
|
| 498 | + * Same as {@link #getTotalPoints(Competitor, RaceColumn, Iterable, TimePoint)}, only that the set of discarded race columns can |
|
| 499 | + * be specified which is useful when total points are to be computed for more than one column for the same |
|
| 500 | + * competitor because then the calculation of discards (which requires looking at all columns) only needs to be done |
|
| 501 | + * once and not again for each column (which would lead to quadratic effort). |
|
| 502 | + * |
|
| 503 | + * @param discardedRaceColumns |
|
| 504 | + * expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}. |
|
| 505 | + * {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint) |
|
| 506 | + * getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}. |
|
| 507 | + */ |
|
| 508 | + @Override |
|
| 509 | + public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, Set<RaceColumn> discardedRaceColumns) { |
|
| 510 | + Double result; |
|
| 511 | + if (isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns)) { |
|
| 512 | + result = 0.0; |
|
| 513 | + } else { |
|
| 514 | + final Double netPoints = getNetPoints(competitor, raceColumn, timePoint); |
|
| 515 | + if (netPoints == null) { |
|
| 516 | + result = null; |
|
| 517 | + } else { |
|
| 518 | + result = raceColumn.getFactor() * netPoints; |
|
| 519 | + } |
|
| 520 | + } |
|
| 521 | + return result; |
|
| 522 | + } |
|
| 523 | + |
|
| 524 | + @Override |
|
| 525 | + public Double getTotalPoints(Competitor competitor, TimePoint timePoint) { |
|
| 526 | + return getTotalPoints(competitor, getRaceColumns(), timePoint); |
|
| 527 | + } |
|
| 528 | + |
|
| 529 | + @Override |
|
| 530 | + public Double getTotalPoints(Competitor competitor, final Iterable<RaceColumn> raceColumnsToConsider, |
|
| 531 | + TimePoint timePoint) { |
|
| 532 | + // when a column with isStartsWithZeroScore() is found, only reset score if the competitor scored in any race from there on |
|
| 533 | + boolean needToResetScoreUponNextNonEmptyEntry = false; |
|
| 534 | + double result = getCarriedPoints(competitor); |
|
| 535 | + final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule() |
|
| 536 | + .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); |
|
| 537 | + for (RaceColumn raceColumn : raceColumnsToConsider) { |
|
| 538 | + if (raceColumn.isStartsWithZeroScore()) { |
|
| 539 | + needToResetScoreUponNextNonEmptyEntry = true; |
|
| 540 | + } |
|
| 541 | + if (getScoringScheme().isValidInTotalScore(this, raceColumn, competitor, timePoint)) { |
|
| 542 | + final Double totalPoints = getTotalPoints(competitor, raceColumn, timePoint, discardedRaceColumns); |
|
| 543 | + if (totalPoints != null) { |
|
| 544 | + if (needToResetScoreUponNextNonEmptyEntry) { |
|
| 545 | + result = 0; |
|
| 546 | + needToResetScoreUponNextNonEmptyEntry = false; |
|
| 547 | + } |
|
| 548 | + result += totalPoints; |
|
| 549 | + } |
|
| 550 | + } |
|
| 551 | + } |
|
| 552 | + return result; |
|
| 553 | + } |
|
| 554 | + |
|
| 555 | + /** |
|
| 556 | + * All competitors with non-<code>null</code> net points are added to the result which is then sorted by net points in ascending |
|
| 557 | + * order. The fleet, if ordered, is the primary ordering criterion, followed by the net points. |
|
| 558 | + */ |
|
| 559 | + @Override |
|
| 560 | + public List<Competitor> getCompetitorsFromBestToWorst(final RaceColumn raceColumn, TimePoint timePoint) throws NoWindException { |
|
| 561 | + final Map<Competitor, com.sap.sse.common.Util.Pair<Double, Fleet>> netPointsAndFleet = new HashMap<Competitor, com.sap.sse.common.Util.Pair<Double, Fleet>>(); |
|
| 562 | + for (Competitor competitor : getCompetitors()) { |
|
| 563 | + Double netPoints = getNetPoints(competitor, raceColumn, timePoint); |
|
| 564 | + if (netPoints != null) { |
|
| 565 | + netPointsAndFleet.put(competitor, new com.sap.sse.common.Util.Pair<Double, Fleet>(netPoints, raceColumn.getFleetOfCompetitor(competitor))); |
|
| 566 | + } |
|
| 567 | + } |
|
| 568 | + List<Competitor> result = new ArrayList<Competitor>(netPointsAndFleet.keySet()); |
|
| 569 | + Collections.sort(result, new Comparator<Competitor>() { |
|
| 570 | + @Override |
|
| 571 | + public int compare(Competitor o1, Competitor o2) { |
|
| 572 | + int comparisonResult; |
|
| 573 | + if (o1 == o2) { |
|
| 574 | + comparisonResult = 0; |
|
| 575 | + } else { |
|
| 576 | + if (raceColumn.hasSplitFleets() && !raceColumn.hasSplitFleetContiguousScoring()) { |
|
| 577 | + // only check fleets if there are more than one and the column is not to be contiguously scored even in case |
|
| 578 | + // of split fleets |
|
| 579 | + final Fleet o1Fleet = netPointsAndFleet.get(o1).getB(); |
|
| 580 | + final Fleet o2Fleet = netPointsAndFleet.get(o2).getB(); |
|
| 581 | + if (o1Fleet == null) { |
|
| 582 | + if (o2Fleet == null) { |
|
| 583 | + comparisonResult = 0; |
|
| 584 | + } else { |
|
| 585 | + comparisonResult = 1; // o1 ranks "worse" because it doesn't have a fleet set while o2 |
|
| 586 | + // has |
|
| 587 | + } |
|
| 588 | + } else { |
|
| 589 | + if (o2Fleet == null) { |
|
| 590 | + comparisonResult = -1; // o1 ranks "better" because it has a fleet set while o2 hasn't |
|
| 591 | + } else { |
|
| 592 | + comparisonResult = o1Fleet.compareTo(o2Fleet); |
|
| 593 | + } |
|
| 594 | + } |
|
| 595 | + } else { |
|
| 596 | + // either there are no split fleets or the split isn't relevant for scoring as for ordered fleets |
|
| 597 | + // the scoring runs contiguously from top to bottom |
|
| 598 | + comparisonResult = 0; |
|
| 599 | + } |
|
| 600 | + if (comparisonResult == 0) { |
|
| 601 | + comparisonResult = getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare( |
|
| 602 | + netPointsAndFleet.get(o1).getA(), netPointsAndFleet.get(o2).getA()); |
|
| 603 | + } |
|
| 604 | + } |
|
| 605 | + return comparisonResult; |
|
| 606 | + } |
|
| 607 | + }); |
|
| 608 | + return result; |
|
| 609 | + } |
|
| 610 | + |
|
| 611 | + @Override |
|
| 612 | + public List<Competitor> getCompetitorsFromBestToWorst(TimePoint timePoint) { |
|
| 613 | + return getCompetitorsFromBestToWorst(getRaceColumns(), timePoint); |
|
| 614 | + } |
|
| 615 | + |
|
| 616 | + private List<Competitor> getCompetitorsFromBestToWorst(Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) { |
|
| 617 | + List<Competitor> result = new ArrayList<Competitor>(); |
|
| 618 | + for (Competitor competitor : getCompetitors()) { |
|
| 619 | + result.add(competitor); |
|
| 620 | + } |
|
| 621 | + Collections.sort(result, getTotalRankComparator(raceColumnsToConsider, timePoint)); |
|
| 622 | + return result; |
|
| 623 | + } |
|
| 624 | + |
|
| 625 | + @Override |
|
| 626 | + public int getTotalRankOfCompetitor(Competitor competitor, TimePoint timePoint) throws NoWindException { |
|
| 627 | + List<Competitor> competitorsFromBestToWorst = getCompetitorsFromBestToWorst(timePoint); |
|
| 628 | + return competitorsFromBestToWorst.indexOf(competitor) + 1; |
|
| 629 | + } |
|
| 630 | + |
|
| 631 | + protected Comparator<? super Competitor> getTotalRankComparator(Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) { |
|
| 632 | + return new LeaderboardTotalRankComparator(this, timePoint, getScoringScheme(), /* nullScoresAreBetter */ false, raceColumnsToConsider); |
|
| 633 | + } |
|
| 634 | + |
|
| 635 | + @Override |
|
| 636 | + public RaceColumn getRaceColumnByName(String columnName) { |
|
| 637 | + RaceColumn result = null; |
|
| 638 | + for (RaceColumn r : getRaceColumns()) { |
|
| 639 | + if (r.getName().equals(columnName)) { |
|
| 640 | + result = r; |
|
| 641 | + break; |
|
| 642 | + } |
|
| 643 | + } |
|
| 644 | + return result; |
|
| 645 | + } |
|
| 646 | + |
|
| 647 | + @Override |
|
| 648 | + public Competitor getCompetitorByName(String competitorName) { |
|
| 649 | + for (Competitor competitor : getAllCompetitors()) { |
|
| 650 | + if (competitor.getName().equals(competitorName)) { |
|
| 651 | + return competitor; |
|
| 652 | + } |
|
| 653 | + } |
|
| 654 | + return null; |
|
| 655 | + } |
|
| 656 | + |
|
| 657 | + @Override |
|
| 658 | + public boolean countRaceForComparisonWithDiscardingThresholds(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { |
|
| 659 | + TrackedRace trackedRaceForCompetitorInColumn; |
|
| 660 | + return getScoringScheme().isValidInTotalScore(this, raceColumn, competitor, timePoint) && |
|
| 661 | + (getScoreCorrection().isScoreCorrected(competitor, raceColumn, timePoint) || |
|
| 662 | + ((trackedRaceForCompetitorInColumn=raceColumn.getTrackedRace(competitor)) != null && |
|
| 663 | + trackedRaceForCompetitorInColumn.hasStarted(timePoint) && |
|
| 664 | + trackedRaceForCompetitorInColumn.getRank(competitor, timePoint) != 0)); |
|
| 665 | + } |
|
| 666 | + |
|
| 667 | + @Override |
|
| 668 | + public void addRaceColumnListener(RaceColumnListener listener) { |
|
| 669 | + getRaceColumnListeners().addRaceColumnListener(listener); |
|
| 670 | + } |
|
| 671 | + |
|
| 672 | + @Override |
|
| 673 | + public void removeRaceColumnListener(RaceColumnListener listener) { |
|
| 674 | + getRaceColumnListeners().removeRaceColumnListener(listener); |
|
| 675 | + } |
|
| 676 | + |
|
| 677 | + @Override |
|
| 678 | + public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint) throws NoWindException { |
|
| 679 | + final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint); |
|
| 680 | + return getEntry(competitor, race, timePoint, discardedRaceColumns); |
|
| 681 | + } |
|
| 682 | + |
|
| 683 | + @Override |
|
| 684 | + public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint, |
|
| 685 | + Set<RaceColumn> discardedRaceColumns) throws NoWindException { |
|
| 686 | + Callable<Integer> trackedRankProvider = new Callable<Integer>() { |
|
| 687 | + public Integer call() throws NoWindException { |
|
| 688 | + return getTrackedRank(competitor, race, timePoint); |
|
| 689 | + } |
|
| 690 | + }; |
|
| 691 | + final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, race, |
|
| 692 | + timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()); |
|
| 693 | + boolean discarded = isDiscarded(competitor, race, timePoint, discardedRaceColumns); |
|
| 694 | + final Double correctedScore = correctedResults.getCorrectedScore(); |
|
| 695 | + return new EntryImpl(trackedRankProvider, correctedScore, new Callable<Double>() { |
|
| 696 | + @Override |
|
| 697 | + public Double call() { |
|
| 698 | + return correctedResults.getUncorrectedScore(); |
|
| 699 | + } |
|
| 700 | + }, correctedResults.isCorrected(), discarded ? DOUBLE_0 : correctedScore == null ? null |
|
| 701 | + : Double.valueOf(correctedScore * race.getFactor()), correctedResults.getMaxPointsReason(), discarded, |
|
| 702 | + race.getFleetOfCompetitor(competitor)); |
|
| 703 | + } |
|
| 704 | + |
|
| 705 | + @Override |
|
| 706 | + public Map<RaceColumn, List<Competitor>> getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(TimePoint timePoint) throws NoWindException { |
|
| 707 | + Map<RaceColumn, List<Competitor>> result = new LinkedHashMap<>(); |
|
| 708 | + List<RaceColumn> raceColumnsToConsider = new ArrayList<>(); |
|
| 709 | + for (RaceColumn raceColumn : getRaceColumns()) { |
|
| 710 | + raceColumnsToConsider.add(raceColumn); |
|
| 711 | + result.put(raceColumn, getCompetitorsFromBestToWorst(raceColumnsToConsider, timePoint)); |
|
| 712 | + } |
|
| 713 | + return result; |
|
| 714 | + } |
|
| 715 | + |
|
| 716 | + @Override |
|
| 717 | + public Map<RaceColumn, Map<Competitor, Double>> getTotalPointsSumAfterRaceColumn(final TimePoint timePoint) |
|
| 718 | + throws NoWindException { |
|
| 719 | + final Map<RaceColumn, Map<Competitor, Double>> result = new LinkedHashMap<>(); |
|
| 720 | + List<RaceColumn> raceColumnsToConsider = new ArrayList<>(); |
|
| 721 | + Map<RaceColumn, Future<Map<Competitor, Double>>> futures = new HashMap<>(); |
|
| 722 | + for (final RaceColumn raceColumn : getRaceColumns()) { |
|
| 723 | + raceColumnsToConsider.add(raceColumn); |
|
| 724 | + final Iterable<RaceColumn> finalRaceColumnsToConsider = new ArrayList<>(raceColumnsToConsider); |
|
| 725 | + futures.put(raceColumn, executor.submit(new Callable<Map<Competitor, Double>>() { |
|
| 726 | + @Override |
|
| 727 | + public Map<Competitor, Double> call() { |
|
| 728 | + Map<Competitor, Double> totalPointsSumPerCompetitorInColumn = new HashMap<>(); |
|
| 729 | + for (Competitor competitor : getCompetitors()) { |
|
| 730 | + totalPointsSumPerCompetitorInColumn.put(competitor, getTotalPoints(competitor, finalRaceColumnsToConsider, timePoint)); |
|
| 731 | + } |
|
| 732 | + synchronized (result) { |
|
| 733 | + return totalPointsSumPerCompetitorInColumn; |
|
| 734 | + } |
|
| 735 | + } |
|
| 736 | + })); |
|
| 737 | + } |
|
| 738 | + for (RaceColumn raceColumn : getRaceColumns()) { |
|
| 739 | + try { |
|
| 740 | + result.put(raceColumn, futures.get(raceColumn).get()); |
|
| 741 | + } catch (InterruptedException | ExecutionException e) { |
|
| 742 | + if (e.getCause() instanceof NoWindError) { |
|
| 743 | + throw ((NoWindError) e.getCause()).getCause(); |
|
| 744 | + } else { |
|
| 745 | + throw new RuntimeException(e); // no caught exceptions occur in the futures executed |
|
| 746 | + } |
|
| 747 | + } |
|
| 748 | + } |
|
| 749 | + return result; |
|
| 750 | + } |
|
| 751 | + |
|
| 752 | + @Override |
|
| 753 | + public Map<com.sap.sse.common.Util.Pair<Competitor, RaceColumn>, Entry> getContent(final TimePoint timePoint) throws NoWindException { |
|
| 754 | + Map<com.sap.sse.common.Util.Pair<Competitor, RaceColumn>, Entry> result = new HashMap<com.sap.sse.common.Util.Pair<Competitor, RaceColumn>, Entry>(); |
|
| 755 | + Map<Competitor, Set<RaceColumn>> discardedRaces = new HashMap<Competitor, Set<RaceColumn>>(); |
|
| 756 | + for (final RaceColumn raceColumn : getRaceColumns()) { |
|
| 757 | + for (final Competitor competitor : getCompetitors()) { |
|
| 758 | + Callable<Integer> trackedRankProvider = new Callable<Integer>() { |
|
| 759 | + @Override |
|
| 760 | + public Integer call() throws Exception { |
|
| 761 | + return getTrackedRank(competitor, raceColumn, timePoint); |
|
| 762 | + } |
|
| 763 | + }; |
|
| 764 | + final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, raceColumn, |
|
| 765 | + timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()); |
|
| 766 | + Set<RaceColumn> discardedRacesForCompetitor = discardedRaces.get(competitor); |
|
| 767 | + if (discardedRacesForCompetitor == null) { |
|
| 768 | + discardedRacesForCompetitor = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint); |
|
| 769 | + discardedRaces.put(competitor, discardedRacesForCompetitor); |
|
| 770 | + } |
|
| 771 | + boolean discarded = discardedRacesForCompetitor.contains(raceColumn); |
|
| 772 | + final Double correctedScore = correctedResults.getCorrectedScore(); |
|
| 773 | + Entry entry = new EntryImpl(trackedRankProvider, correctedScore, |
|
| 774 | + new Callable<Double>() { @Override public Double call() { return correctedResults.getUncorrectedScore(); } }, |
|
| 775 | + correctedResults.isCorrected(), |
|
| 776 | + discarded ? DOUBLE_0 : (correctedScore==null?null: |
|
| 777 | + Double.valueOf((correctedScore * raceColumn.getFactor()))), correctedResults.getMaxPointsReason(), |
|
| 778 | + discarded, raceColumn.getFleetOfCompetitor(competitor)); |
|
| 779 | + result.put(new com.sap.sse.common.Util.Pair<Competitor, RaceColumn>(competitor, raceColumn), entry); |
|
| 780 | + } |
|
| 781 | + } |
|
| 782 | + return result; |
|
| 783 | + } |
|
| 784 | + |
|
| 785 | + @Override |
|
| 786 | + public void trackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { |
|
| 787 | + getRaceColumnListeners().notifyListenersAboutTrackedRaceLinked(raceColumn, fleet, trackedRace); |
|
| 788 | + } |
|
| 789 | + |
|
| 790 | + @Override |
|
| 791 | + public void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { |
|
| 792 | + getRaceColumnListeners().notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace); |
|
| 793 | + // It's generally possible that a leaderboard links to the same tracked race in multiple columns / fleets; |
|
| 794 | + // only if it no longer references the trackedRace currently unlinked from one column/fleet, also unlink |
|
| 795 | + // all cache invalidation listeners for said trackedRace |
|
| 796 | + if (!Util.contains(getTrackedRaces(), trackedRace)) { |
|
| 797 | + synchronized (cacheInvalidationListeners) { |
|
| 798 | + for (Iterator<CacheInvalidationListener> cacheInvalidationListenerIter=cacheInvalidationListeners.iterator(); |
|
| 799 | + cacheInvalidationListenerIter.hasNext(); ) { |
|
| 800 | + CacheInvalidationListener cacheInvalidationListener = cacheInvalidationListenerIter.next(); |
|
| 801 | + if (cacheInvalidationListener.getTrackedRace() == trackedRace) { |
|
| 802 | + cacheInvalidationListener.removeFromTrackedRace(); |
|
| 803 | + cacheInvalidationListenerIter.remove(); |
|
| 804 | + } |
|
| 805 | + } |
|
| 806 | + } |
|
| 807 | + } |
|
| 808 | + } |
|
| 809 | + |
|
| 810 | + @Override |
|
| 811 | + public void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) { |
|
| 812 | + getRaceColumnListeners().notifyListenersAboutIsMedalRaceChanged(raceColumn, newIsMedalRace); |
|
| 813 | + } |
|
| 814 | + |
|
| 815 | + @Override |
|
| 816 | + public void isStartsWithZeroScoreChanged(RaceColumn raceColumn, boolean newIsStartsWithZeroScore) { |
|
| 817 | + getRaceColumnListeners().notifyListenersAboutIsStartsWithZeroScoreChanged(raceColumn, newIsStartsWithZeroScore); |
|
| 818 | + } |
|
| 819 | + |
|
| 820 | + @Override |
|
| 821 | + public void isFirstColumnIsNonDiscardableCarryForwardChanged(RaceColumn raceColumn, boolean firstColumnIsNonDiscardableCarryForward) { |
|
| 822 | + getRaceColumnListeners().notifyListenersAboutIsFirstColumnIsNonDiscardableCarryForwardChanged(raceColumn, firstColumnIsNonDiscardableCarryForward); |
|
| 823 | + } |
|
| 824 | + |
|
| 825 | + @Override |
|
| 826 | + public void hasSplitFleetContiguousScoringChanged(RaceColumn raceColumn, boolean hasSplitFleetContiguousScoring) { |
|
| 827 | + getRaceColumnListeners().notifyListenersAboutHasSplitFleetContiguousScoringChanged(raceColumn, hasSplitFleetContiguousScoring); |
|
| 828 | + } |
|
| 829 | + |
|
| 830 | + @Override |
|
| 831 | + public void raceColumnMoved(RaceColumn raceColumn, int newIndex) { |
|
| 832 | + getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(raceColumn, newIndex); |
|
| 833 | + } |
|
| 834 | + |
|
| 835 | + @Override |
|
| 836 | + public void factorChanged(RaceColumn raceColumn, Double oldFactor, Double newFactor) { |
|
| 837 | + getRaceColumnListeners().notifyListenersAboutFactorChanged(raceColumn, oldFactor, newFactor); |
|
| 838 | + } |
|
| 839 | + |
|
| 840 | + /** |
|
| 841 | + * A leaderboard will only accept the addition of a race column if the column's name is unique across the leaderboard. |
|
| 842 | + */ |
|
| 843 | + @Override |
|
| 844 | + public boolean canAddRaceColumnToContainer(RaceColumn newRaceColumn) { |
|
| 845 | + boolean result = true; |
|
| 846 | + for (RaceColumn raceColumn : getRaceColumns()) { |
|
| 847 | + if (raceColumn.getName().equals(newRaceColumn.getName())) { |
|
| 848 | + result = false; |
|
| 849 | + break; |
|
| 850 | + } |
|
| 851 | + } |
|
| 852 | + return result; |
|
| 853 | + } |
|
| 854 | + |
|
| 855 | + @Override |
|
| 856 | + public void raceColumnAddedToContainer(RaceColumn raceColumn) { |
|
| 857 | + getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(raceColumn); |
|
| 858 | + } |
|
| 859 | + |
|
| 860 | + @Override |
|
| 861 | + public void raceColumnRemovedFromContainer(RaceColumn raceColumn) { |
|
| 862 | + getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn); |
|
| 863 | + } |
|
| 864 | + |
|
| 865 | + @Override |
|
| 866 | + public void competitorDisplayNameChanged(Competitor competitor, String oldDisplayName, String displayName) { |
|
| 867 | + getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName); |
|
| 868 | + } |
|
| 869 | + |
|
| 870 | + @Override |
|
| 871 | + public void resultDiscardingRuleChanged(ResultDiscardingRule oldDiscardingRule, ResultDiscardingRule newDiscardingRule) { |
|
| 872 | + getRaceColumnListeners().notifyListenersAboutResultDiscardingRuleChanged(oldDiscardingRule, newDiscardingRule); |
|
| 873 | + } |
|
| 874 | + |
|
| 875 | + @Override |
|
| 876 | + public boolean isTransient() { |
|
| 877 | + return false; |
|
| 878 | + } |
|
| 879 | + |
|
| 880 | + /** |
|
| 881 | + * Finds out the time point when any of the {@link Leaderboard#getTrackedRaces() tracked races currently attached to |
|
| 882 | + * the <code>leaderboard</code>} and the {@link Leaderboard#getScoreCorrection() score corrections} have last been |
|
| 883 | + * modified. If no tracked race is attached and no time-stamped score corrections have been applied to the leaderboard, |
|
| 884 | + * <code>null</code> is returned. The time point computed this way is a good choice for normalizing queries for later time |
|
| 885 | + * points in an attempt to achieve more cache hits.<p> |
|
| 886 | + * |
|
| 887 | + * Note, however, that the result does not tell about structural changes to the leaderboard and therefore cannot be used |
|
| 888 | + * to determine the need for cache invalidation. For example, if a column is added to a leaderboard after the time point |
|
| 889 | + * returned by this method but that column's attached tracked race has finished before the time point returned by this method, |
|
| 890 | + * the result of this method won't change. Still, the contents of the leaderboard will change by a change in column structure. |
|
| 891 | + * A different means to determine the possibility of changes that happened to this leaderboard must be used for cache |
|
| 892 | + * management. Such a facility has to listen for score correction changes, tracked races being attached or detached and |
|
| 893 | + * the column structure changing. |
|
| 894 | + * |
|
| 895 | + * @see TrackedRace#getTimePointOfNewestEvent() |
|
| 896 | + * @see SettableScoreCorrection#getTimePointOfLastCorrectionsValidity() |
|
| 897 | + */ |
|
| 898 | + @Override |
|
| 899 | + public TimePoint getTimePointOfLatestModification() { |
|
| 900 | + TimePoint result = null; |
|
| 901 | + for (TrackedRace trackedRace : getTrackedRaces()) { |
|
| 902 | + if (result == null || (trackedRace.getTimePointOfNewestEvent() != null && trackedRace.getTimePointOfNewestEvent().after(result))) { |
|
| 903 | + result = trackedRace.getTimePointOfNewestEvent(); |
|
| 904 | + } |
|
| 905 | + } |
|
| 906 | + TimePoint timePointOfLastScoreCorrection = getScoreCorrection().getTimePointOfLastCorrectionsValidity(); |
|
| 907 | + if (timePointOfLastScoreCorrection != null && (result == null || timePointOfLastScoreCorrection.after(result))) { |
|
| 908 | + result = timePointOfLastScoreCorrection; |
|
| 909 | + } |
|
| 910 | + return result; |
|
| 911 | + } |
|
| 912 | + |
|
| 913 | + @Override |
|
| 914 | + public com.sap.sse.common.Util.Pair<GPSFixMoving, Speed> getMaximumSpeedOverGround(Competitor competitor, TimePoint timePoint) { |
|
| 915 | + com.sap.sse.common.Util.Pair<GPSFixMoving, Speed> result = null; |
|
| 916 | + // TODO should we ensure that competitor participated in all race columns? |
|
| 917 | + for (TrackedRace trackedRace : getTrackedRaces()) { |
|
| 918 | + if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { |
|
| 919 | + NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor); |
|
| 920 | + if (!markPassings.isEmpty()) { |
|
| 921 | + TimePoint from = markPassings.first().getTimePoint(); |
|
| 922 | + TimePoint to; |
|
| 923 | + if (timePoint.after(markPassings.last().getTimePoint()) && |
|
| 924 | + markPassings.last().getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) { |
|
| 925 | + // stop counting when competitor finished the race |
|
| 926 | + to = markPassings.last().getTimePoint(); |
|
| 927 | + } else { |
|
| 928 | + to = timePoint; |
|
| 929 | + } |
|
| 930 | + com.sap.sse.common.Util.Pair<GPSFixMoving, Speed> maxSpeed = trackedRace.getTrack(competitor).getMaximumSpeedOverGround(from, to); |
|
| 931 | + if (result == null || result.getB() == null || |
|
| 932 | + (maxSpeed != null && maxSpeed.getB() != null && maxSpeed.getB().compareTo(result.getB()) > 0)) { |
|
| 933 | + result = maxSpeed; |
|
| 934 | + } |
|
| 935 | + } |
|
| 936 | + } |
|
| 937 | + } |
|
| 938 | + return result; |
|
| 939 | + } |
|
| 940 | + |
|
| 941 | + @Override |
|
| 942 | + public Speed getAverageSpeedOverGround(Competitor competitor, TimePoint timePoint) { |
|
| 943 | + Speed result = null; |
|
| 944 | + for (TrackedRace trackedRace : getTrackedRaces()) { |
|
| 945 | + if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { |
|
| 946 | + NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor); |
|
| 947 | + if (!markPassings.isEmpty()) { |
|
| 948 | + TimePoint from = markPassings.first().getTimePoint(); |
|
| 949 | + TimePoint to; |
|
| 950 | + if (timePoint.after(markPassings.last().getTimePoint()) && |
|
| 951 | + markPassings.last().getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) { |
|
| 952 | + // stop counting when competitor finished the race |
|
| 953 | + to = markPassings.last().getTimePoint(); |
|
| 954 | + } else { |
|
| 955 | + if (markPassings.last().getWaypoint() != trackedRace.getRace().getCourse().getLastWaypoint() && |
|
| 956 | + timePoint.after(markPassings.last().getTimePoint())) { |
|
| 957 | + result = null; |
|
| 958 | + break; |
|
| 959 | + } |
|
| 960 | + to = timePoint; |
|
| 961 | + } |
|
| 962 | + Distance distanceTraveled = trackedRace.getDistanceTraveled(competitor, timePoint); |
|
| 963 | + if (distanceTraveled != null) { |
|
| 964 | + result = distanceTraveled.inTime(to.asMillis()-from.asMillis()); |
|
| 965 | + } |
|
| 966 | + } |
|
| 967 | + } |
|
| 968 | + } |
|
| 969 | + return result; |
|
| 970 | + } |
|
| 971 | + |
|
| 972 | + @Override |
|
| 973 | + public Duration getTotalTimeSailedInLegType(Competitor competitor, LegType legType, TimePoint timePoint) throws NoWindException { |
|
| 974 | + return getTotalTimeSailedInLegType(competitor, legType, timePoint, new HashMap<TrackedLeg, LegType>()); |
|
| 975 | + } |
|
| 976 | + |
|
| 977 | + private Duration getTotalTimeSailedInLegType(Competitor competitor, LegType legType, TimePoint timePoint, Map<TrackedLeg, LegType> legTypeCache) throws NoWindException { |
|
| 978 | + Duration result = null; |
|
| 979 | + // TODO should we ensure that competitor participated in all race columns? |
|
| 980 | + outerLoop: |
|
| 981 | + for (TrackedRace trackedRace : getTrackedRaces()) { |
|
| 982 | + if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { |
|
| 983 | + trackedRace.getRace().getCourse().lockForRead(); |
|
| 984 | + try { |
|
| 985 | + for (Leg leg : trackedRace.getRace().getCourse().getLegs()) { |
|
| 986 | + TrackedLegOfCompetitor trackedLegOfCompetitor = trackedRace.getTrackedLeg(competitor, leg); |
|
| 987 | + if (trackedLegOfCompetitor.hasStartedLeg(timePoint)) { |
|
| 988 | + // find out leg type at the time the competitor started the leg |
|
| 989 | + try { |
|
| 990 | + final TrackedLeg trackedLeg = trackedRace.getTrackedLeg(leg); |
|
| 991 | + LegType trackedLegType = legTypeCache.get(trackedLeg); |
|
| 992 | + if (trackedLegType == null) { |
|
| 993 | + final TimePoint startTime = trackedLegOfCompetitor.getStartTime(); |
|
| 994 | + TimePoint finishTime = trackedLegOfCompetitor.getFinishTime(); |
|
| 995 | + if (finishTime == null) { |
|
| 996 | + finishTime = timePoint; |
|
| 997 | + } |
|
| 998 | + trackedLegType = trackedLeg.getLegType(startTime.plus(startTime.until(finishTime).divide(2))); // middle of the leg |
|
| 999 | + legTypeCache.put(trackedLeg, trackedLegType); |
|
| 1000 | + } |
|
| 1001 | + if (legType == trackedLegType) { |
|
| 1002 | + Duration timeSpentOnDownwind = trackedLegOfCompetitor.getTime(timePoint); |
|
| 1003 | + if (timeSpentOnDownwind != null) { |
|
| 1004 | + if (result == null) { |
|
| 1005 | + result = timeSpentOnDownwind; |
|
| 1006 | + } else { |
|
| 1007 | + result = result.plus(timeSpentOnDownwind); |
|
| 1008 | + } |
|
| 1009 | + } else { |
|
| 1010 | + // Although the competitor has started the leg, no value was produced. This |
|
| 1011 | + // means that the competitor didn't finish the leg before tracking ended. No useful value |
|
| 1012 | + // can be obtained for this competitor anymore. |
|
| 1013 | + result = null; |
|
| 1014 | + break outerLoop; |
|
| 1015 | + } |
|
| 1016 | + } |
|
| 1017 | + } catch (NoWindException nwe) { |
|
| 1018 | + // without wind there is no leg type and hence there is no reasonable value for this: |
|
| 1019 | + result = null; |
|
| 1020 | + break outerLoop; |
|
| 1021 | + } |
|
| 1022 | + } |
|
| 1023 | + } |
|
| 1024 | + } finally { |
|
| 1025 | + trackedRace.getRace().getCourse().unlockAfterRead(); |
|
| 1026 | + } |
|
| 1027 | + } |
|
| 1028 | + } |
|
| 1029 | + return result; |
|
| 1030 | + } |
|
| 1031 | + |
|
| 1032 | + @Override |
|
| 1033 | + public Duration getTotalTimeSailed(Competitor competitor, TimePoint timePoint) { |
|
| 1034 | + Duration result = null; |
|
| 1035 | + for (TrackedRace trackedRace : getTrackedRaces()) { |
|
| 1036 | + if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { |
|
| 1037 | + NavigableSet<MarkPassing> markPassings = trackedRace.getMarkPassings(competitor); |
|
| 1038 | + if (!markPassings.isEmpty()) { |
|
| 1039 | + TimePoint from = trackedRace.getStartOfRace(); // start counting at race start, not when the competitor passed the line |
|
| 1040 | + if (from != null && !timePoint.before(from)) { // but only if the race started after timePoint |
|
| 1041 | + TimePoint to; |
|
| 1042 | + if (timePoint.after(markPassings.last().getTimePoint()) |
|
| 1043 | + && markPassings.last().getWaypoint() == trackedRace.getRace().getCourse() |
|
| 1044 | + .getLastWaypoint()) { |
|
| 1045 | + // stop counting when competitor finished the race |
|
| 1046 | + to = markPassings.last().getTimePoint(); |
|
| 1047 | + } else { |
|
| 1048 | + if (trackedRace.getEndOfTracking() != null |
|
| 1049 | + && timePoint.after(trackedRace.getEndOfTracking())) { |
|
| 1050 | + result = null; // race not finished until end of tracking; no reasonable value can be |
|
| 1051 | + // computed for competitor |
|
| 1052 | + break; |
|
| 1053 | + } else { |
|
| 1054 | + to = timePoint; |
|
| 1055 | + } |
|
| 1056 | + } |
|
| 1057 | + Duration timeSpent = from.until(to); |
|
| 1058 | + if (result == null) { |
|
| 1059 | + result = timeSpent; |
|
| 1060 | + } else { |
|
| 1061 | + result=result.plus(timeSpent); |
|
| 1062 | + } |
|
| 1063 | + } |
|
| 1064 | + } |
|
| 1065 | + } |
|
| 1066 | + } |
|
| 1067 | + return result; |
|
| 1068 | + } |
|
| 1069 | + |
|
| 1070 | + @Override |
|
| 1071 | + public Distance getTotalDistanceTraveled(Competitor competitor, TimePoint timePoint) { |
|
| 1072 | + Distance result = null; |
|
| 1073 | + for (TrackedRace trackedRace : getTrackedRaces()) { |
|
| 1074 | + TimePoint startOfRace; |
|
| 1075 | + if (Util.contains(trackedRace.getRace().getCompetitors(), competitor) && |
|
| 1076 | + (startOfRace=trackedRace.getStartOfRace()) != null && |
|
| 1077 | + !startOfRace.after(timePoint)) { |
|
| 1078 | + Distance distanceSailedInRace = trackedRace.getDistanceTraveled(competitor, timePoint); |
|
| 1079 | + if (distanceSailedInRace != null) { |
|
| 1080 | + if (result == null) { |
|
| 1081 | + result = distanceSailedInRace; |
|
| 1082 | + } else { |
|
| 1083 | + result = result.add(distanceSailedInRace); |
|
| 1084 | + } |
|
| 1085 | + } else { |
|
| 1086 | + // if competitor has not finished one single race in the whole |
|
| 1087 | + // series then we can not return a meaningful value for all |
|
| 1088 | + // all races |
|
| 1089 | + return null; |
|
| 1090 | + } |
|
| 1091 | + } |
|
| 1092 | + } |
|
| 1093 | + return result; |
|
| 1094 | + } |
|
| 1095 | + |
|
| 1096 | + protected RaceColumnListeners getRaceColumnListeners() { |
|
| 1097 | + return raceColumnListeners; |
|
| 1098 | + } |
|
| 1099 | + |
|
| 1100 | + @Override |
|
| 1101 | + public Iterable<Competitor> getCompetitors() { |
|
| 1102 | + final Iterable<Competitor> result; |
|
| 1103 | + // mostly the set of suppressed competitors is empty; in this case, avoid having to loop over the |
|
| 1104 | + // potentially large set of competitors |
|
| 1105 | + if (Util.isEmpty(getSuppressedCompetitors())) { |
|
| 1106 | + result = getAllCompetitors(); |
|
| 1107 | + } else { |
|
| 1108 | + final Iterable<Competitor> allCompetitors = getAllCompetitors(); |
|
| 1109 | + final Set<Competitor> suppressed = new HashSet<>(); |
|
| 1110 | + Util.addAll(getSuppressedCompetitors(), suppressed); |
|
| 1111 | + result = getCompetitorIterableSkippingSuppressed(allCompetitors, suppressed); |
|
| 1112 | + } |
|
| 1113 | + return result; |
|
| 1114 | + } |
|
| 1115 | + |
|
| 1116 | + @Override |
|
| 1117 | + public Iterable<Competitor> getCompetitors(RaceColumn raceColumn, Fleet fleet) { |
|
| 1118 | + return getCompetitorIterableSkippingSuppressed(getAllCompetitors(raceColumn, fleet), getSuppressedCompetitors()); |
|
| 1119 | + } |
|
| 1120 | + |
|
| 1121 | + /** |
|
| 1122 | + * return an iterable with a smart iterator that filters out the suppressed elements on demand |
|
| 1123 | + */ |
|
| 1124 | + protected Iterable<Competitor> getCompetitorIterableSkippingSuppressed(final Iterable<Competitor> allCompetitors, |
|
| 1125 | + final Iterable<Competitor> suppressed) { |
|
| 1126 | + final Iterable<Competitor> result; |
|
| 1127 | + result = new Iterable<Competitor>() { |
|
| 1128 | + @Override |
|
| 1129 | + public Iterator<Competitor> iterator() { |
|
| 1130 | + return new Iterator<Competitor>() { |
|
| 1131 | + private final Iterator<Competitor> allIter = allCompetitors.iterator(); |
|
| 1132 | + private Competitor next = advance(); |
|
| 1133 | + |
|
| 1134 | + private Competitor advance() { |
|
| 1135 | + next = null; |
|
| 1136 | + while (allIter.hasNext() && next == null) { |
|
| 1137 | + next = allIter.next(); |
|
| 1138 | + if (Util.contains(suppressed, next)) { |
|
| 1139 | + next = null; |
|
| 1140 | + } |
|
| 1141 | + } |
|
| 1142 | + return next; |
|
| 1143 | + } |
|
| 1144 | + |
|
| 1145 | + @Override |
|
| 1146 | + public boolean hasNext() { |
|
| 1147 | + return next != null; |
|
| 1148 | + } |
|
| 1149 | + |
|
| 1150 | + @Override |
|
| 1151 | + public Competitor next() { |
|
| 1152 | + if (next == null) { |
|
| 1153 | + throw new NoSuchElementException(); |
|
| 1154 | + } |
|
| 1155 | + final Competitor result = next; |
|
| 1156 | + advance(); |
|
| 1157 | + return result; |
|
| 1158 | + } |
|
| 1159 | + }; |
|
| 1160 | + } |
|
| 1161 | + }; |
|
| 1162 | + return result; |
|
| 1163 | + } |
|
| 1164 | + |
|
| 1165 | + @Override |
|
| 1166 | + public Iterable<Competitor> getSuppressedCompetitors() { |
|
| 1167 | + LockUtil.lockForRead(suppressedCompetitorsLock); |
|
| 1168 | + try { |
|
| 1169 | + return new HashSet<Competitor>(suppressedCompetitors); |
|
| 1170 | + } finally { |
|
| 1171 | + LockUtil.unlockAfterRead(suppressedCompetitorsLock); |
|
| 1172 | + } |
|
| 1173 | + } |
|
| 1174 | + |
|
| 1175 | + @Override |
|
| 1176 | + public void setSuppressed(Competitor competitor, boolean suppressed) { |
|
| 1177 | + LockUtil.lockForWrite(suppressedCompetitorsLock); |
|
| 1178 | + try { |
|
| 1179 | + if (suppressed) { |
|
| 1180 | + suppressedCompetitors.add(competitor); |
|
| 1181 | + } else { |
|
| 1182 | + suppressedCompetitors.remove(competitor); |
|
| 1183 | + } |
|
| 1184 | + } finally { |
|
| 1185 | + LockUtil.unlockAfterWrite(suppressedCompetitorsLock); |
|
| 1186 | + } |
|
| 1187 | + getScoreCorrection().notifyListenersAboutIsSuppressedChange(competitor, suppressed); |
|
| 1188 | + } |
|
| 1189 | + |
|
| 1190 | + @Override |
|
| 1191 | + public TimePoint getNowMinusDelay() { |
|
| 1192 | + final TimePoint now = MillisecondsTimePoint.now(); |
|
| 1193 | + final Long delayToLiveInMillis = getDelayToLiveInMillis(); |
|
| 1194 | + TimePoint timePoint = delayToLiveInMillis == null ? now : now.minus(delayToLiveInMillis); |
|
| 1195 | + return timePoint; |
|
| 1196 | + } |
|
| 1197 | + |
|
| 1198 | + @Override |
|
| 1199 | + public void raceLogEventAdded(RaceColumn raceColumn, RaceLogIdentifier raceLogIdentifier, RaceLogEvent event) { |
|
| 1200 | + getRaceColumnListeners().notifyListenersAboutRaceLogEventAdded(raceColumn, raceLogIdentifier, event); |
|
| 1201 | + if (event instanceof InvalidatesLeaderboardCache) { |
|
| 1202 | + // make sure to invalidate the cache as this event indicates that |
|
| 1203 | + // it changes values the cache could still hold |
|
| 1204 | + if (leaderboardDTOCache != null) { |
|
| 1205 | + leaderboardDTOCache.invalidate(this); |
|
| 1206 | + } |
|
| 1207 | + } |
|
| 1208 | + } |
|
| 1209 | + |
|
| 1210 | + @Override |
|
| 1211 | + public LeaderboardDTO computeDTO(final TimePoint timePoint, |
|
| 1212 | + final Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails, |
|
| 1213 | + final boolean waitForLatestAnalyses, TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory, |
|
| 1214 | + final boolean fillNetPointsUncorrected) |
|
| 1215 | + throws NoWindException { |
|
| 1216 | + long startOfRequestHandling = System.currentTimeMillis(); |
|
| 1217 | + final LeaderboardDTOCalculationReuseCache cache = new LeaderboardDTOCalculationReuseCache(timePoint); |
|
| 1218 | + final LeaderboardDTO result = new LeaderboardDTO(this.getScoreCorrection().getTimePointOfLastCorrectionsValidity() == null ? null |
|
| 1219 | + : this.getScoreCorrection().getTimePointOfLastCorrectionsValidity().asDate(), |
|
| 1220 | + this.getScoreCorrection() == null ? null : this.getScoreCorrection().getComment(), |
|
| 1221 | + this.getScoringScheme() == null ? null : this.getScoringScheme().getType(), this |
|
| 1222 | + .getScoringScheme().isHigherBetter(), new UUIDGenerator(), addOverallDetails); |
|
| 1223 | + result.type = getLeaderboardType(); |
|
| 1224 | + result.competitors = new ArrayList<CompetitorDTO>(); |
|
| 1225 | + result.name = this.getName(); |
|
| 1226 | + result.competitorDisplayNames = new HashMap<CompetitorDTO, String>(); |
|
| 1227 | + for (Competitor suppressedCompetitor : this.getSuppressedCompetitors()) { |
|
| 1228 | + result.setSuppressed(baseDomainFactory.convertToCompetitorDTO(suppressedCompetitor), true); |
|
| 1229 | + } |
|
| 1230 | + // Now create the race columns and, as a future task, set their competitorsFromBestToWorst, then wait for all these |
|
| 1231 | + // futures to finish: |
|
| 1232 | + Map<RaceColumn, FutureTask<List<CompetitorDTO>>> competitorsFromBestToWorstTasks = new HashMap<>(); |
|
| 1233 | + for (final RaceColumn raceColumn : this.getRaceColumns()) { |
|
| 1234 | + boolean isMetaLeaderboardColumn = raceColumn instanceof MetaLeaderboardColumn; |
|
| 1235 | + RaceColumnDTO raceColumnDTO = result.createEmptyRaceColumn(raceColumn.getName(), raceColumn.isMedalRace(), |
|
| 1236 | + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null, |
|
| 1237 | + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null, |
|
| 1238 | + isMetaLeaderboardColumn); |
|
| 1239 | + if (isMetaLeaderboardColumn && raceColumnDTO instanceof MetaLeaderboardRaceColumnDTO) { |
|
| 1240 | + calculateRacesMetadata((MetaLeaderboardColumn) raceColumn, (MetaLeaderboardRaceColumnDTO) raceColumnDTO, |
|
| 1241 | + trackedRegattaRegistry, baseDomainFactory); |
|
| 1242 | + } |
|
| 1243 | + for (Fleet fleet : raceColumn.getFleets()) { |
|
| 1244 | + RegattaAndRaceIdentifier raceIdentifier = null; |
|
| 1245 | + RaceDTO race = null; |
|
| 1246 | + TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); |
|
| 1247 | + final FleetDTO fleetDTO = baseDomainFactory.convertToFleetDTO(fleet); |
|
| 1248 | + if (trackedRace != null) { |
|
| 1249 | + raceIdentifier = new RegattaNameAndRaceName(trackedRace.getTrackedRegatta().getRegatta().getName(), |
|
| 1250 | + trackedRace.getRace().getName()); |
|
| 1251 | + race = baseDomainFactory.createRaceDTO(trackedRegattaRegistry, /* withGeoLocationData */ false, raceIdentifier, trackedRace); |
|
| 1252 | + } |
|
| 1253 | + // Note: the RaceColumnDTO won't be created by the following addRace call because it has been created |
|
| 1254 | + // above by the result.createEmptyRaceColumn call |
|
| 1255 | + result.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(), |
|
| 1256 | + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null, |
|
| 1257 | + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null, |
|
| 1258 | + fleetDTO, raceColumn.isMedalRace(), raceIdentifier, race, isMetaLeaderboardColumn); |
|
| 1259 | + } |
|
| 1260 | + FutureTask<List<CompetitorDTO>> task = new FutureTask<List<CompetitorDTO>>( |
|
| 1261 | + () -> baseDomainFactory.getCompetitorDTOList(AbstractSimpleLeaderboardImpl.this.getCompetitorsFromBestToWorst(raceColumn, timePoint))); |
|
| 1262 | + executor.execute(task); |
|
| 1263 | + competitorsFromBestToWorstTasks.put(raceColumn, task); |
|
| 1264 | + } |
|
| 1265 | + // wait for the competitor orderings to have been computed for all race columns before continuing; subsequent tasks may depend on these data |
|
| 1266 | + for (Map.Entry<RaceColumn, FutureTask<List<CompetitorDTO>>> raceColumnAndTaskToJoin : competitorsFromBestToWorstTasks.entrySet()) { |
|
| 1267 | + try { |
|
| 1268 | + result.setCompetitorsFromBestToWorst(raceColumnAndTaskToJoin.getKey().getName(), raceColumnAndTaskToJoin.getValue().get()); |
|
| 1269 | + } catch (InterruptedException e) { |
|
| 1270 | + throw new RuntimeException(e); |
|
| 1271 | + } catch (ExecutionException e) { |
|
| 1272 | + // See also bug 1371: for stability reasons, don't let the exception percolate but rather accept |
|
| 1273 | + // null values. |
|
| 1274 | + // If new evidence is provided, a re-calculation of the leaderboard will be triggered anyway. So |
|
| 1275 | + // this helps robustness from a user's perspective. |
|
| 1276 | + logger.log( |
|
| 1277 | + Level.SEVERE, |
|
| 1278 | + AbstractSimpleLeaderboardImpl.class.getName() + ".computeDTO(" + this.getName() + ", " |
|
| 1279 | + + timePoint + ", " + namesOfRaceColumnsForWhichToLoadLegDetails+", addOverallDetails="+addOverallDetails |
|
| 1280 | + + "): exception during computing competitor ordering for race column "+raceColumnAndTaskToJoin.getKey().getName(), e); |
|
| 1281 | + } |
|
| 1282 | + } |
|
| 1283 | + result.setDelayToLiveInMillisForLatestRace(this.getDelayToLiveInMillis()); |
|
| 1284 | + result.rows = new HashMap<CompetitorDTO, LeaderboardRowDTO>(); |
|
| 1285 | + result.hasCarriedPoints = this.hasCarriedPoints(); |
|
| 1286 | + if (this.getResultDiscardingRule() instanceof ThresholdBasedResultDiscardingRule) { |
|
| 1287 | + result.discardThresholds = ((ThresholdBasedResultDiscardingRule) this.getResultDiscardingRule()) |
|
| 1288 | + .getDiscardIndexResultsStartingWithHowManyRaces(); |
|
| 1289 | + } else { |
|
| 1290 | + result.discardThresholds = null; |
|
| 1291 | + } |
|
| 1292 | + // Computing the competitor leg ranks is expensive, especially in live mode, in case new events keep |
|
| 1293 | + // invalidating the ranks cache in TrackedLegImpl. The problem then is that the sorting based on wind data is repeated for |
|
| 1294 | + // each competitor, leading to square effort. We therefore need to compute the leg ranks for those races where leg |
|
| 1295 | + // details are requested only once and pass them into getLeaderboardEntryDTO |
|
| 1296 | + final Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache = new HashMap<Leg, LinkedHashMap<Competitor, Integer>>(); |
|
| 1297 | + for (final RaceColumn raceColumn : this.getRaceColumns()) { |
|
| 1298 | + // if details for the column are requested, cache the leg's ranks |
|
| 1299 | + if (namesOfRaceColumnsForWhichToLoadLegDetails != null |
|
| 1300 | + && namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn.getName())) { |
|
| 1301 | + for (Fleet fleet : raceColumn.getFleets()) { |
|
| 1302 | + TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); |
|
| 1303 | + if (trackedRace != null) { |
|
| 1304 | + trackedRace.getRace().getCourse().lockForRead(); |
|
| 1305 | + try { |
|
| 1306 | + for (TrackedLeg trackedLeg : trackedRace.getTrackedLegs()) { |
|
| 1307 | + legRanksCache.put(trackedLeg.getLeg(), trackedLeg.getRanks(timePoint, cache)); |
|
| 1308 | + } |
|
| 1309 | + } finally { |
|
| 1310 | + trackedRace.getRace().getCourse().unlockAfterRead(); |
|
| 1311 | + } |
|
| 1312 | + } |
|
| 1313 | + } |
|
| 1314 | + } |
|
| 1315 | + } |
|
| 1316 | + for (final Competitor competitor : this.getCompetitorsFromBestToWorst(timePoint)) { |
|
| 1317 | + CompetitorDTO competitorDTO = baseDomainFactory.convertToCompetitorDTO(competitor); |
|
| 1318 | + LeaderboardRowDTO row = new LeaderboardRowDTO(); |
|
| 1319 | + row.competitor = competitorDTO; |
|
| 1320 | + row.fieldsByRaceColumnName = new HashMap<String, LeaderboardEntryDTO>(); |
|
| 1321 | + row.carriedPoints = this.hasCarriedPoints(competitor) ? this.getCarriedPoints(competitor) : null; |
|
| 1322 | + row.totalPoints = this.getTotalPoints(competitor, timePoint); |
|
| 1323 | + if (addOverallDetails) { |
|
| 1324 | + addOverallDetailsToRow(timePoint, competitor, row); |
|
| 1325 | + } |
|
| 1326 | + result.competitors.add(competitorDTO); |
|
| 1327 | + Map<String, Future<LeaderboardEntryDTO>> futuresForColumnName = new HashMap<String, Future<LeaderboardEntryDTO>>(); |
|
| 1328 | + final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint); |
|
| 1329 | + for (final RaceColumn raceColumn : this.getRaceColumns()) { |
|
| 1330 | + RunnableFuture<LeaderboardEntryDTO> future = new FutureTask<LeaderboardEntryDTO>(() -> { |
|
| 1331 | + Entry entry = AbstractSimpleLeaderboardImpl.this.getEntry(competitor, raceColumn, timePoint, discardedRaceColumns); |
|
| 1332 | + return getLeaderboardEntryDTO(entry, raceColumn, competitor, timePoint, |
|
| 1333 | + namesOfRaceColumnsForWhichToLoadLegDetails != null |
|
| 1334 | + && namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn |
|
| 1335 | + .getName()), waitForLatestAnalyses, legRanksCache, baseDomainFactory, |
|
| 1336 | + fillNetPointsUncorrected, cache); |
|
| 1337 | + }); |
|
| 1338 | + executor.execute(future); |
|
| 1339 | + futuresForColumnName.put(raceColumn.getName(), future); |
|
| 1340 | + } |
|
| 1341 | + for (Map.Entry<String, Future<LeaderboardEntryDTO>> raceColumnNameAndFuture : futuresForColumnName.entrySet()) { |
|
| 1342 | + try { |
|
| 1343 | + row.fieldsByRaceColumnName.put(raceColumnNameAndFuture.getKey(), raceColumnNameAndFuture.getValue().get()); |
|
| 1344 | + } catch (InterruptedException e) { |
|
| 1345 | + throw new RuntimeException(e); |
|
| 1346 | + } catch (ExecutionException e) { |
|
| 1347 | + // See also bug 1371: for stability reasons, don't let the exception percolate but rather accept |
|
| 1348 | + // null values. |
|
| 1349 | + // If new evidence is provided, a re-calculation of the leaderboard will be triggered anyway. So |
|
| 1350 | + // this helps robustness from a user's perspective. |
|
| 1351 | + logger.log( |
|
| 1352 | + Level.SEVERE, |
|
| 1353 | + AbstractSimpleLeaderboardImpl.class.getName() + ".computeDTO(" + this.getName() + ", " |
|
| 1354 | + + timePoint + ", " + namesOfRaceColumnsForWhichToLoadLegDetails+", addOverallDetails="+addOverallDetails |
|
| 1355 | + + "): exception during computing leaderboard entry for competitor " |
|
| 1356 | + + competitor.getName() + " in race column " + raceColumnNameAndFuture.getKey() |
|
| 1357 | + + ". Leaving empty.", e); |
|
| 1358 | + } |
|
| 1359 | + } |
|
| 1360 | + result.rows.put(competitorDTO, row); |
|
| 1361 | + String displayName = this.getDisplayName(competitor); |
|
| 1362 | + if (displayName != null) { |
|
| 1363 | + result.competitorDisplayNames.put(competitorDTO, displayName); |
|
| 1364 | + } |
|
| 1365 | + } |
|
| 1366 | + logger.info("computeLeaderboardByName(" + this.getName() + ", " + timePoint + ", " |
|
| 1367 | + + namesOfRaceColumnsForWhichToLoadLegDetails + ", addOverallDetails=" + addOverallDetails + ") took " |
|
| 1368 | + + (System.currentTimeMillis() - startOfRequestHandling) + "ms"); |
|
| 1369 | + return result; |
|
| 1370 | + } |
|
| 1371 | + |
|
| 1372 | + private void calculateRacesMetadata(MetaLeaderboardColumn metaLeaderboardColumn, MetaLeaderboardRaceColumnDTO columnDTO, |
|
| 1373 | + TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory) { |
|
| 1374 | + for (final RaceColumn raceColumn : metaLeaderboardColumn.getLeaderboard().getRaceColumns()) { |
|
| 1375 | + for (Fleet fleet : raceColumn.getFleets()) { |
|
| 1376 | + TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); |
|
| 1377 | + if (trackedRace != null) { |
|
| 1378 | + String regattaName = trackedRace.getTrackedRegatta().getRegatta().getName(); |
|
| 1379 | + String raceName = trackedRace.getRace().getName(); |
|
| 1380 | + RegattaAndRaceIdentifier raceIdentifier = new RegattaNameAndRaceName(regattaName, raceName); |
|
| 1381 | + columnDTO.addRace(new BasicRaceDTO(raceIdentifier, baseDomainFactory.createTrackedRaceDTO(trackedRace))); |
|
| 1382 | + } |
|
| 1383 | + } |
|
| 1384 | + } |
|
| 1385 | + } |
|
| 1386 | + |
|
| 1387 | + private void addOverallDetailsToRow(final TimePoint timePoint, |
|
| 1388 | + final Competitor competitor, LeaderboardRowDTO row) throws NoWindException { |
|
| 1389 | + final com.sap.sse.common.Util.Pair<GPSFixMoving, Speed> maximumSpeedOverGround = this.getMaximumSpeedOverGround(competitor, timePoint); |
|
| 1390 | + if (maximumSpeedOverGround != null && maximumSpeedOverGround.getB() != null) { |
|
| 1391 | + row.maximumSpeedOverGroundInKnots = maximumSpeedOverGround.getB().getKnots(); |
|
| 1392 | + row.whenMaximumSpeedOverGroundWasAchieved = maximumSpeedOverGround.getA().getTimePoint().asDate(); |
|
| 1393 | + } |
|
| 1394 | + Map<TrackedLeg, LegType> legTypeCache = new HashMap<>(); |
|
| 1395 | + final Duration totalTimeSailedDownwind = this.getTotalTimeSailedInLegType(competitor, LegType.DOWNWIND, timePoint, legTypeCache); |
|
| 1396 | + row.totalTimeSailedDownwindInSeconds = totalTimeSailedDownwind==null?null:totalTimeSailedDownwind.asSeconds(); |
|
| 1397 | + final Duration totalTimeSailedUpwind = this.getTotalTimeSailedInLegType(competitor, LegType.UPWIND, timePoint, legTypeCache); |
|
| 1398 | + row.totalTimeSailedUpwindInSeconds = totalTimeSailedUpwind==null?null:totalTimeSailedUpwind.asSeconds(); |
|
| 1399 | + final Duration totalTimeSailedReaching = this.getTotalTimeSailedInLegType(competitor, LegType.REACHING, timePoint, legTypeCache); |
|
| 1400 | + row.totalTimeSailedReachingInSeconds = totalTimeSailedReaching==null?null:totalTimeSailedReaching.asSeconds(); |
|
| 1401 | + final Duration totalTimeSailed = this.getTotalTimeSailed(competitor, timePoint); |
|
| 1402 | + row.totalTimeSailedInSeconds = totalTimeSailed==null?null:totalTimeSailed.asSeconds(); |
|
| 1403 | + final Distance totalDistanceTraveledInMeters = this.getTotalDistanceTraveled(competitor, timePoint); |
|
| 1404 | + row.totalDistanceTraveledInMeters = totalDistanceTraveledInMeters==null?null:totalDistanceTraveledInMeters.getMeters(); |
|
| 1405 | + } |
|
| 1406 | + |
|
| 1407 | + /** |
|
| 1408 | + * @param waitForLatestAnalyses |
|
| 1409 | + * if <code>false</code>, this method is allowed to read the maneuver analysis results from a cache that |
|
| 1410 | + * may not reflect all data already received; otherwise, the method will always block for the latest |
|
| 1411 | + * cache updates to have happened before returning. |
|
| 1412 | + * @param fillNetPointsUncorrected |
|
| 1413 | + * tells if {@link LeaderboardEntryDTO#netPointsUncorrected} shall be filled; filling it is rather |
|
| 1414 | + * expensive, especially when compared to simply retrieving a score correction, and particularly if in a |
|
| 1415 | + * larger fleet a number of competitors haven't properly finished the race. This should only be used for |
|
| 1416 | + * leaderboard editing where a user needs to see what the uncorrected score was that would be used when |
|
| 1417 | + * the correction was removed. |
|
| 1418 | + */ |
|
| 1419 | + private LeaderboardEntryDTO getLeaderboardEntryDTO(Entry entry, RaceColumn raceColumn, Competitor competitor, |
|
| 1420 | + TimePoint timePoint, boolean addLegDetails, boolean waitForLatestAnalyses, |
|
| 1421 | + Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache, DomainFactory baseDomainFactory, |
|
| 1422 | + boolean fillNetPointsUncorrected, WindLegTypeAndLegBearingCache cache) { |
|
| 1423 | + LeaderboardEntryDTO entryDTO = new LeaderboardEntryDTO(); |
|
| 1424 | + TrackedRace trackedRace = raceColumn.getTrackedRace(competitor); |
|
| 1425 | + entryDTO.race = trackedRace == null ? null : trackedRace.getRaceIdentifier(); |
|
| 1426 | + entryDTO.netPoints = entry.getNetPoints(); |
|
| 1427 | + if (fillNetPointsUncorrected) { |
|
| 1428 | + entryDTO.netPointsUncorrected = entry.getNetPointsUncorrected(); |
|
| 1429 | + } |
|
| 1430 | + entryDTO.netPointsCorrected = entry.isNetPointsCorrected(); |
|
| 1431 | + entryDTO.totalPoints = entry.getTotalPoints(); |
|
| 1432 | + entryDTO.reasonForMaxPoints = entry.getMaxPointsReason(); |
|
| 1433 | + entryDTO.discarded = entry.isDiscarded(); |
|
| 1434 | + final GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace == null ? null : trackedRace.getTrack(competitor); |
|
| 1435 | + if (trackedRace != null) { |
|
| 1436 | + Date timePointOfLastPositionFixAtOrBeforeQueryTimePoint = getTimePointOfLastFixAtOrBefore(competitor, trackedRace, timePoint); |
|
| 1437 | + if (track != null) { |
|
| 1438 | + entryDTO.averageSamplingInterval = track.getAverageIntervalBetweenRawFixes(); |
|
| 1439 | + } |
|
| 1440 | + if (timePointOfLastPositionFixAtOrBeforeQueryTimePoint != null) { |
|
| 1441 | + long timeDifferenceInMs = timePoint.asMillis() - timePointOfLastPositionFixAtOrBeforeQueryTimePoint.getTime(); |
|
| 1442 | + entryDTO.timeSinceLastPositionFixInSeconds = timeDifferenceInMs == 0 ? 0.0 : timeDifferenceInMs / 1000.0; |
|
| 1443 | + } else { |
|
| 1444 | + entryDTO.timeSinceLastPositionFixInSeconds = null; |
|
| 1445 | + } |
|
| 1446 | + } |
|
| 1447 | + if (addLegDetails && trackedRace != null) { |
|
| 1448 | + try { |
|
| 1449 | + final RankingInfo rankingInfo = trackedRace.getRankingMetric().getRankingInfo(timePoint, cache); |
|
| 1450 | + RaceDetails raceDetails = getRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses, |
|
| 1451 | + legRanksCache, rankingInfo, cache); |
|
| 1452 | + entryDTO.legDetails = raceDetails.getLegDetails(); |
|
| 1453 | + entryDTO.windwardDistanceToCompetitorFarthestAheadInMeters = raceDetails.getWindwardDistanceToCompetitorFarthestAhead() == null ? null |
|
| 1454 | + : raceDetails.getWindwardDistanceToCompetitorFarthestAhead().getMeters(); |
|
| 1455 | + entryDTO.gapToLeaderInOwnTime = trackedRace.getRankingMetric().getGapToLeaderInOwnTime(rankingInfo, competitor, cache); |
|
| 1456 | + entryDTO.averageAbsoluteCrossTrackErrorInMeters = raceDetails.getAverageAbsoluteCrossTrackError() == null ? null |
|
| 1457 | + : raceDetails.getAverageAbsoluteCrossTrackError().getMeters(); |
|
| 1458 | + entryDTO.averageSignedCrossTrackErrorInMeters = raceDetails.getAverageSignedCrossTrackError() == null ? null |
|
| 1459 | + : raceDetails.getAverageSignedCrossTrackError().getMeters(); |
|
| 1460 | + entryDTO.calculatedTime = raceDetails.getCorrectedTime(); |
|
| 1461 | + entryDTO.calculatedTimeAtEstimatedArrivalAtCompetitorFarthestAhead = raceDetails.getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead(); |
|
| 1462 | + entryDTO.gapToLeaderInOwnTime = raceDetails.getGapToLeaderInOwnTime(); |
|
| 1463 | + final TimePoint startOfRace = trackedRace.getStartOfRace(); |
|
| 1464 | + if (startOfRace != null) { |
|
| 1465 | + Waypoint startWaypoint = trackedRace.getRace().getCourse().getFirstWaypoint(); |
|
| 1466 | + NavigableSet<MarkPassing> competitorMarkPassings = trackedRace.getMarkPassings(competitor); |
|
| 1467 | + trackedRace.lockForRead(competitorMarkPassings); |
|
| 1468 | + try { |
|
| 1469 | + if (!competitorMarkPassings.isEmpty()) { |
|
| 1470 | + final MarkPassing firstMarkPassing = competitorMarkPassings.iterator().next(); |
|
| 1471 | + if (firstMarkPassing.getWaypoint() == startWaypoint) { |
|
| 1472 | + Distance distanceToStartLineFiveSecondsBeforeStartOfRace = trackedRace.getDistanceToStartLine(competitor, /*milliseconds before start*/ 5000); |
|
| 1473 | + entryDTO.distanceToStartLineFiveSecondsBeforeStartInMeters = distanceToStartLineFiveSecondsBeforeStartOfRace == null ? null |
|
| 1474 | + : distanceToStartLineFiveSecondsBeforeStartOfRace.getMeters(); |
|
| 1475 | + Speed speedFiveSecondsBeforeStartOfRace = trackedRace.getSpeed(competitor, /*milliseconds before start*/ 5000); |
|
| 1476 | + entryDTO.speedOverGroundFiveSecondsBeforeStartInKnots = speedFiveSecondsBeforeStartOfRace == null ? null |
|
| 1477 | + : speedFiveSecondsBeforeStartOfRace.getKnots(); |
|
| 1478 | + Distance distanceToStartLineAtStartOfRace = trackedRace.getDistanceToStartLine( |
|
| 1479 | + competitor, startOfRace); |
|
| 1480 | + entryDTO.distanceToStartLineAtStartOfRaceInMeters = distanceToStartLineAtStartOfRace == null ? null |
|
| 1481 | + : distanceToStartLineAtStartOfRace.getMeters(); |
|
| 1482 | + Speed speedAtStartTime = track == null ? null : track.getEstimatedSpeed(startOfRace); |
|
| 1483 | + entryDTO.speedOverGroundAtStartOfRaceInKnots = speedAtStartTime == null ? null |
|
| 1484 | + : speedAtStartTime.getKnots(); |
|
| 1485 | + TimePoint competitorStartTime = firstMarkPassing.getTimePoint(); |
|
| 1486 | + entryDTO.timeBetweenRaceStartAndCompetitorStartInSeconds = startOfRace.until(competitorStartTime).asSeconds(); |
|
| 1487 | + Speed competitorSpeedWhenPassingStart = track == null ? null : track |
|
| 1488 | + .getEstimatedSpeed(competitorStartTime); |
|
| 1489 | + entryDTO.speedOverGroundAtPassingStartWaypointInKnots = competitorSpeedWhenPassingStart == null ? null |
|
| 1490 | + : competitorSpeedWhenPassingStart.getKnots(); |
|
| 1491 | + try { |
|
| 1492 | + entryDTO.startTack = trackedRace.getTack(competitor, competitorStartTime); |
|
| 1493 | + } catch (NoWindException nwe) { |
|
| 1494 | + entryDTO.startTack = null; // leave empty in case no wind information is available |
|
| 1495 | + } |
|
| 1496 | + Distance distanceFromStarboardSideOfStartLineWhenPassingStart = trackedRace |
|
| 1497 | + .getDistanceFromStarboardSideOfStartLineWhenPassingStart(competitor); |
|
| 1498 | + entryDTO.distanceToStarboardSideOfStartLineInMeters = distanceFromStarboardSideOfStartLineWhenPassingStart == null ? null |
|
| 1499 | + : distanceFromStarboardSideOfStartLineWhenPassingStart.getMeters(); |
|
| 1500 | + } |
|
| 1501 | + } |
|
| 1502 | + } finally { |
|
| 1503 | + trackedRace.unlockAfterRead(competitorMarkPassings); |
|
| 1504 | + } |
|
| 1505 | + } |
|
| 1506 | + } catch (InterruptedException e) { |
|
| 1507 | + throw new RuntimeException(e); |
|
| 1508 | + } catch (ExecutionException e) { |
|
| 1509 | + throw new RuntimeException(e); // the future used to calculate the leg details was interrupted; escalate as runtime exception |
|
| 1510 | + } |
|
| 1511 | + } |
|
| 1512 | + final Fleet fleet = entry.getFleet(); |
|
| 1513 | + entryDTO.fleet = fleet == null ? null : baseDomainFactory.convertToFleetDTO(fleet); |
|
| 1514 | + return entryDTO; |
|
| 1515 | + } |
|
| 1516 | + |
|
| 1517 | + /** |
|
| 1518 | + * Determines the time point of the last raw fix (with outliers not removed) for <code>competitor</code> in |
|
| 1519 | + * <code>trackedRace</code>. If the competitor's track is <code>null</code> or empty, <code>null</code> is returned. |
|
| 1520 | + * @param trackedRace must not be <code>null</code> |
|
| 1521 | + * @param atOrBefore find the last fix at or before the time point specified |
|
| 1522 | + */ |
|
| 1523 | + private Date getTimePointOfLastFixAtOrBefore(Competitor competitor, TrackedRace trackedRace, TimePoint atOrBefore) { |
|
| 1524 | + assert trackedRace != null; |
|
| 1525 | + final Date timePointOfLastPositionFix; |
|
| 1526 | + GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(competitor); |
|
| 1527 | + if (track == null) { |
|
| 1528 | + timePointOfLastPositionFix = null; |
|
| 1529 | + } else { |
|
| 1530 | + GPSFixMoving lastFix = track.getLastFixAtOrBefore(atOrBefore); |
|
| 1531 | + if (lastFix == null) { |
|
| 1532 | + timePointOfLastPositionFix = null; |
|
| 1533 | + } else { |
|
| 1534 | + timePointOfLastPositionFix = lastFix.getTimePoint().asDate(); |
|
| 1535 | + } |
|
| 1536 | + } |
|
| 1537 | + return timePointOfLastPositionFix; |
|
| 1538 | + } |
|
| 1539 | + |
|
| 1540 | + private static class RaceDetails { |
|
| 1541 | + private final List<LegEntryDTO> legDetails; |
|
| 1542 | + private final Distance windwardDistanceToCompetitorFarthestAhead; |
|
| 1543 | + private final Distance averageAbsoluteCrossTrackError; |
|
| 1544 | + private final Distance averageSignedCrossTrackError; |
|
| 1545 | + private final Duration gapToLeaderInOwnTime; |
|
| 1546 | + private final Duration correctedTime; |
|
| 1547 | + private final Duration correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead; |
|
| 1548 | + |
|
| 1549 | + public RaceDetails(List<LegEntryDTO> legDetails, Distance windwardDistanceToCompetitorFarthestAhead, |
|
| 1550 | + Distance averageAbsoluteCrossTrackError, Distance averageSignedCrossTrackError, |
|
| 1551 | + Duration gapToLeaderInOwnTime, Duration correctedTime, |
|
| 1552 | + Duration correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead) { |
|
| 1553 | + super(); |
|
| 1554 | + this.legDetails = legDetails; |
|
| 1555 | + this.windwardDistanceToCompetitorFarthestAhead = windwardDistanceToCompetitorFarthestAhead; |
|
| 1556 | + this.averageAbsoluteCrossTrackError = averageAbsoluteCrossTrackError; |
|
| 1557 | + this.averageSignedCrossTrackError = averageSignedCrossTrackError; |
|
| 1558 | + this.gapToLeaderInOwnTime = gapToLeaderInOwnTime; |
|
| 1559 | + this.correctedTime = correctedTime; |
|
| 1560 | + this.correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead = correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead; |
|
| 1561 | + } |
|
| 1562 | + public List<LegEntryDTO> getLegDetails() { |
|
| 1563 | + return legDetails; |
|
| 1564 | + } |
|
| 1565 | + public Distance getWindwardDistanceToCompetitorFarthestAhead() { |
|
| 1566 | + return windwardDistanceToCompetitorFarthestAhead; |
|
| 1567 | + } |
|
| 1568 | + public Distance getAverageAbsoluteCrossTrackError() { |
|
| 1569 | + return averageAbsoluteCrossTrackError; |
|
| 1570 | + } |
|
| 1571 | + public Distance getAverageSignedCrossTrackError() { |
|
| 1572 | + return averageSignedCrossTrackError; |
|
| 1573 | + } |
|
| 1574 | + public Duration getGapToLeaderInOwnTime() { |
|
| 1575 | + return gapToLeaderInOwnTime; |
|
| 1576 | + } |
|
| 1577 | + public Duration getCorrectedTime() { |
|
| 1578 | + return correctedTime; |
|
| 1579 | + } |
|
| 1580 | + public Duration getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead() { |
|
| 1581 | + return correctedTimeAtEstimatedArrivalAtCompetitorFarthestAhead; |
|
| 1582 | + } |
|
| 1583 | + } |
|
| 1584 | + |
|
| 1585 | + /** |
|
| 1586 | + * If <code>timePoint</code> is after the end of the race's tracking the query will be adjusted to obtain the values |
|
| 1587 | + * at the end of the {@link TrackedRace#getEndOfTracking() race's tracking time}. If the time point adjusted this |
|
| 1588 | + * way equals the end of the tracking time, the query results will be looked up in a cache first and if not found, |
|
| 1589 | + * they will be stored to the cache after calculating them. A cache invalidation {@link RaceChangeListener listener} |
|
| 1590 | + * will be registered with the race which will be triggered for any event received by the race. |
|
| 1591 | + * @param waitForLatestAnalyses |
|
| 1592 | + * if <code>false</code>, this method is allowed to read the maneuver analysis results from a cache that |
|
| 1593 | + * may not reflect all data already received; otherwise, the method will always block for the latest |
|
| 1594 | + * cache updates to have happened before returning. |
|
| 1595 | + */ |
|
| 1596 | + private RaceDetails getRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint, |
|
| 1597 | + boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache, |
|
| 1598 | + RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) throws InterruptedException, ExecutionException { |
|
| 1599 | + final RaceDetails raceDetails; |
|
| 1600 | + if (trackedRace.getEndOfTracking() != null && trackedRace.getEndOfTracking().compareTo(timePoint) < 0) { |
|
| 1601 | + raceDetails = getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(trackedRace, competitor, legRanksCache, rankingInfo, cache); |
|
| 1602 | + } else { |
|
| 1603 | + raceDetails = calculateRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses, legRanksCache, cache, rankingInfo); |
|
| 1604 | + } |
|
| 1605 | + return raceDetails; |
|
| 1606 | + } |
|
| 1607 | + |
|
| 1608 | + private RaceDetails getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(final TrackedRace trackedRace, |
|
| 1609 | + final Competitor competitor, final Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache, |
|
| 1610 | + RankingInfo rankingInfo, final WindLegTypeAndLegBearingCache cache) throws InterruptedException, ExecutionException { |
|
| 1611 | + final com.sap.sse.common.Util.Pair<TrackedRace, Competitor> key = new com.sap.sse.common.Util.Pair<TrackedRace, Competitor>(trackedRace, competitor); |
|
| 1612 | + RunnableFuture<RaceDetails> raceDetails; |
|
| 1613 | + synchronized (raceDetailsAtEndOfTrackingCache) { |
|
| 1614 | + raceDetails = raceDetailsAtEndOfTrackingCache.get(key); |
|
| 1615 | + if (raceDetails == null) { |
|
| 1616 | + raceDetails = new FutureTask<RaceDetails>(new Callable<RaceDetails>() { |
|
| 1617 | + @Override |
|
| 1618 | + public RaceDetails call() throws Exception { |
|
| 1619 | + TimePoint end = trackedRace.getEndOfRace(); |
|
| 1620 | + if (end == null) { |
|
| 1621 | + end = trackedRace.getEndOfTracking(); |
|
| 1622 | + } |
|
| 1623 | + return calculateRaceDetails(trackedRace, competitor, end, |
|
| 1624 | + // TODO see bug 1358: for now, use waitForLatest==false until we've switched to optimistic locking for the course read lock |
|
| 1625 | + /* TODO old comment when it was still true: "because this is done only once after end of tracking" */ |
|
| 1626 | + /* waitForLatestAnalyses (maneuver and cross track error) */ false, |
|
| 1627 | + legRanksCache, cache, rankingInfo); |
|
| 1628 | + } |
|
| 1629 | + }); |
|
| 1630 | + raceDetailsExecutor.execute(raceDetails); |
|
| 1631 | + raceDetailsAtEndOfTrackingCache.put(key, raceDetails); |
|
| 1632 | + final CacheInvalidationListener cacheInvalidationListener = new CacheInvalidationListener(trackedRace, competitor); |
|
| 1633 | + trackedRace.addListener(cacheInvalidationListener); |
|
| 1634 | + cacheInvalidationListeners.add(cacheInvalidationListener); |
|
| 1635 | + } |
|
| 1636 | + } |
|
| 1637 | + return raceDetails.get(); |
|
| 1638 | + } |
|
| 1639 | + |
|
| 1640 | + private RaceDetails calculateRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint, |
|
| 1641 | + boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache, |
|
| 1642 | + WindLegTypeAndLegBearingCache cache, final RankingInfo rankingInfo) { |
|
| 1643 | + final List<LegEntryDTO> legDetails = new ArrayList<LegEntryDTO>(); |
|
| 1644 | + final Course course = trackedRace.getRace().getCourse(); |
|
| 1645 | + course.lockForRead(); // hold back any course re-configurations while looping over the legs |
|
| 1646 | + try { |
|
| 1647 | + for (Leg leg : course.getLegs()) { |
|
| 1648 | + LegEntryDTO legEntry; |
|
| 1649 | + // We loop over a copy of the course's legs; during a course change, legs may become "stale," even with |
|
| 1650 | + // regard to the leg/trackedLeg structures inside the tracked race which is updated by the course change |
|
| 1651 | + // immediately. That's why we've acquired a read lock for the course above. |
|
| 1652 | + TrackedLegOfCompetitor trackedLeg = trackedRace.getTrackedLeg(competitor, leg); |
|
| 1653 | + if (trackedLeg != null && trackedLeg.hasStartedLeg(timePoint)) { |
|
| 1654 | + legEntry = createLegEntry(trackedLeg, timePoint, waitForLatestAnalyses, legRanksCache, rankingInfo, cache); |
|
| 1655 | + } else { |
|
| 1656 | + legEntry = null; |
|
| 1657 | + } |
|
| 1658 | + legDetails.add(legEntry); |
|
| 1659 | + } |
|
| 1660 | + final Distance windwardDistanceToCompetitorFarthestAhead = trackedRace == null ? null : trackedRace |
|
| 1661 | + .getWindwardDistanceToCompetitorFarthestAhead(competitor, timePoint, WindPositionMode.LEG_MIDDLE, rankingInfo, cache); |
|
| 1662 | + Distance averageAbsoluteCrossTrackError; |
|
| 1663 | + try { |
|
| 1664 | + averageAbsoluteCrossTrackError = trackedRace == null ? null : trackedRace.getAverageAbsoluteCrossTrackError( |
|
| 1665 | + competitor, timePoint, waitForLatestAnalyses, cache); |
|
| 1666 | + } catch (NoWindException nwe) { |
|
| 1667 | + // without wind information, use null meaning "unknown" |
|
| 1668 | + averageAbsoluteCrossTrackError = null; |
|
| 1669 | + } |
|
| 1670 | + Distance averageSignedCrossTrackError; |
|
| 1671 | + try { |
|
| 1672 | + averageSignedCrossTrackError = trackedRace == null ? null : trackedRace.getAverageSignedCrossTrackError( |
|
| 1673 | + competitor, timePoint, waitForLatestAnalyses, cache); |
|
| 1674 | + } catch (NoWindException nwe) { |
|
| 1675 | + // without wind information, use null meaning "unknown" |
|
| 1676 | + averageSignedCrossTrackError = null; |
|
| 1677 | + } |
|
| 1678 | + final CompetitorRankingInfo competitorRankingInfo = rankingInfo.getCompetitorRankingInfo().apply(competitor); |
|
| 1679 | + return new RaceDetails(legDetails, windwardDistanceToCompetitorFarthestAhead, averageAbsoluteCrossTrackError, averageSignedCrossTrackError, |
|
| 1680 | + trackedRace.getRankingMetric().getGapToLeaderInOwnTime(rankingInfo, competitor, cache), |
|
| 1681 | + trackedRace.getRankingMetric().getCorrectedTime(competitor, timePoint), |
|
| 1682 | + competitorRankingInfo == null ? null : competitorRankingInfo.getCorrectedTimeAtEstimatedArrivalAtCompetitorFarthestAhead()); |
|
| 1683 | + } finally { |
|
| 1684 | + course.unlockAfterRead(); |
|
| 1685 | + } |
|
| 1686 | + } |
|
| 1687 | + |
|
| 1688 | + private LegEntryDTO createLegEntry(TrackedLegOfCompetitor trackedLeg, TimePoint timePoint, |
|
| 1689 | + boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache, |
|
| 1690 | + RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) { |
|
| 1691 | + LegEntryDTO result; |
|
| 1692 | + final Duration time = trackedLeg.getTime(timePoint); |
|
| 1693 | + if (trackedLeg == null || time == null) { |
|
| 1694 | + result = null; |
|
| 1695 | + } else { |
|
| 1696 | + result = new LegEntryDTO(); |
|
| 1697 | + try { |
|
| 1698 | + result.legType = trackedLeg.getTrackedLeg().getLegType(timePoint); |
|
| 1699 | + } catch (NoWindException nwe) { |
|
| 1700 | + result.legType = null; // can't determine leg type without wind data |
|
| 1701 | + } |
|
| 1702 | + final Speed averageSpeedOverGround = trackedLeg.getAverageSpeedOverGround(timePoint); |
|
| 1703 | + result.averageSpeedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots(); |
|
| 1704 | + Distance averageAbsoluteCrossTrackError; |
|
| 1705 | + try { |
|
| 1706 | + averageAbsoluteCrossTrackError = trackedLeg.getAverageAbsoluteCrossTrackError(timePoint, waitForLatestAnalyses); |
|
| 1707 | + } catch (NoWindException nwe) { |
|
| 1708 | + // leave averageAbsoluteCrossTrackError as null, meaning "unknown" |
|
| 1709 | + averageAbsoluteCrossTrackError = null; |
|
| 1710 | + } |
|
| 1711 | + result.averageAbsoluteCrossTrackErrorInMeters = averageAbsoluteCrossTrackError == null ? null : averageAbsoluteCrossTrackError.getMeters(); |
|
| 1712 | + Distance averageSignedCrossTrackError; |
|
| 1713 | + try { |
|
| 1714 | + averageSignedCrossTrackError = trackedLeg.getAverageSignedCrossTrackError(timePoint, waitForLatestAnalyses); |
|
| 1715 | + } catch (NoWindException nwe) { |
|
| 1716 | + // leave averageSignedCrossTrackError as null, meaning "unknown" |
|
| 1717 | + averageSignedCrossTrackError = null; |
|
| 1718 | + } |
|
| 1719 | + result.averageSignedCrossTrackErrorInMeters = averageSignedCrossTrackError == null ? null : averageSignedCrossTrackError.getMeters(); |
|
| 1720 | + Double speedOverGroundInKnots; |
|
| 1721 | + if (trackedLeg.hasFinishedLeg(timePoint)) { |
|
| 1722 | + speedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots(); |
|
| 1723 | + } else { |
|
| 1724 | + final SpeedWithBearing speedOverGround = trackedLeg.getSpeedOverGround(timePoint); |
|
| 1725 | + speedOverGroundInKnots = speedOverGround == null ? null : speedOverGround.getKnots(); |
|
| 1726 | + } |
|
| 1727 | + result.currentSpeedOverGroundInKnots = speedOverGroundInKnots == null ? null : speedOverGroundInKnots; |
|
| 1728 | + Distance distanceTraveled = trackedLeg.getDistanceTraveled(timePoint); |
|
| 1729 | + result.distanceTraveledInMeters = distanceTraveled == null ? null : distanceTraveled.getMeters(); |
|
| 1730 | + Distance distanceTraveledConsideringGateStart = trackedLeg.getDistanceTraveledConsideringGateStart(timePoint); |
|
| 1731 | + result.distanceTraveledIncludingGateStartInMeters = distanceTraveledConsideringGateStart == null ? null : distanceTraveledConsideringGateStart.getMeters(); |
|
| 1732 | + final Duration estimatedTimeToNextMarkInSeconds = trackedLeg.getEstimatedTimeToNextMark(timePoint, WindPositionMode.EXACT, cache); |
|
| 1733 | + result.estimatedTimeToNextWaypointInSeconds = estimatedTimeToNextMarkInSeconds==null?null:estimatedTimeToNextMarkInSeconds.asSeconds(); |
|
| 1734 | + result.timeInMilliseconds = time.asMillis(); |
|
| 1735 | + result.finished = trackedLeg.hasFinishedLeg(timePoint); |
|
| 1736 | + final TimePoint legFinishTime = trackedLeg.getFinishTime(); |
|
| 1737 | + result.correctedTotalTime = trackedLeg.hasStartedLeg(timePoint) ? trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric().getCorrectedTime(trackedLeg.getCompetitor(), |
|
| 1738 | + trackedLeg.hasFinishedLeg(timePoint) ? legFinishTime : timePoint, cache) : null; |
|
| 1739 | + // fetch the leg gap in own corrected time from the ranking metric |
|
| 1740 | + final Duration gapToLeaderInOwnTime = trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric(). |
|
| 1741 | + getLegGapToLegLeaderInOwnTime(trackedLeg, timePoint, rankingInfo, cache); |
|
| 1742 | + result.gapToLeaderInSeconds = gapToLeaderInOwnTime == null ? null : gapToLeaderInOwnTime.asSeconds(); |
|
| 1743 | + if (result.gapToLeaderInSeconds != null) { |
|
| 1744 | + final Duration gapAtEndOfPreviousLeg = getGapAtEndOfPreviousLeg(trackedLeg, rankingInfo, cache); |
|
| 1745 | + if (gapAtEndOfPreviousLeg != null) { |
|
| 1746 | + result.gapChangeSinceLegStartInSeconds = result.gapToLeaderInSeconds - gapAtEndOfPreviousLeg.asSeconds(); |
|
| 1747 | + } |
|
| 1748 | + } |
|
| 1749 | + LinkedHashMap<Competitor, Integer> legRanks = legRanksCache.get(trackedLeg.getLeg()); |
|
| 1750 | + if (legRanks != null) { |
|
| 1751 | + result.rank = legRanks.get(trackedLeg.getCompetitor()); |
|
| 1752 | + } else { |
|
| 1753 | + result.rank = trackedLeg.getRank(timePoint, cache); |
|
| 1754 | + } |
|
| 1755 | + result.started = trackedLeg.hasStartedLeg(timePoint); |
|
| 1756 | + Speed velocityMadeGood; |
|
| 1757 | + if (trackedLeg.hasFinishedLeg(timePoint)) { |
|
| 1758 | + velocityMadeGood = trackedLeg.getAverageVelocityMadeGood(timePoint); |
|
| 1759 | + } else { |
|
| 1760 | + velocityMadeGood = trackedLeg.getVelocityMadeGood(timePoint, WindPositionMode.EXACT); |
|
| 1761 | + } |
|
| 1762 | + result.velocityMadeGoodInKnots = velocityMadeGood == null ? null : velocityMadeGood.getKnots(); |
|
| 1763 | + Distance windwardDistanceToGo = trackedLeg.getWindwardDistanceToGo(timePoint, WindPositionMode.LEG_MIDDLE); |
|
| 1764 | + result.windwardDistanceToGoInMeters = windwardDistanceToGo == null ? null : windwardDistanceToGo |
|
| 1765 | + .getMeters(); |
|
| 1766 | + final TimePoint startOfRace = trackedLeg.getTrackedLeg().getTrackedRace().getStartOfRace(); |
|
| 1767 | + if (startOfRace != null && trackedLeg.hasStartedLeg(timePoint)) { |
|
| 1768 | + // not using trackedLeg.getManeuvers(...) because it may not catch the mark passing maneuver starting this leg |
|
| 1769 | + // because that may have been detected as slightly before the mark passing time, hence associated with the previous leg |
|
| 1770 | + List<Maneuver> maneuvers = trackedLeg.getTrackedLeg().getTrackedRace() |
|
| 1771 | + .getManeuvers(trackedLeg.getCompetitor(), startOfRace, timePoint, waitForLatestAnalyses); |
|
| 1772 | + if (maneuvers != null) { |
|
| 1773 | + result.numberOfManeuvers = new HashMap<ManeuverType, Integer>(); |
|
| 1774 | + result.numberOfManeuvers.put(ManeuverType.TACK, 0); |
|
| 1775 | + result.numberOfManeuvers.put(ManeuverType.JIBE, 0); |
|
| 1776 | + result.numberOfManeuvers.put(ManeuverType.PENALTY_CIRCLE, 0); |
|
| 1777 | + Map<ManeuverType, Double> totalManeuverLossInMeters = new HashMap<ManeuverType, Double>(); |
|
| 1778 | + totalManeuverLossInMeters.put(ManeuverType.TACK, 0.0); |
|
| 1779 | + totalManeuverLossInMeters.put(ManeuverType.JIBE, 0.0); |
|
| 1780 | + totalManeuverLossInMeters.put(ManeuverType.PENALTY_CIRCLE, 0.0); |
|
| 1781 | + TimePoint startOfLeg = trackedLeg.getStartTime(); |
|
| 1782 | + for (Maneuver maneuver : maneuvers) { |
|
| 1783 | + // don't count maneuvers that were in previous legs |
|
| 1784 | + switch (maneuver.getType()) { |
|
| 1785 | + case TACK: |
|
| 1786 | + case JIBE: |
|
| 1787 | + case PENALTY_CIRCLE: |
|
| 1788 | + if (!maneuver.getTimePoint().before(startOfLeg) && (legFinishTime == null || legFinishTime.after(timePoint) || |
|
| 1789 | + maneuver.getTimePoint().before(legFinishTime))) { |
|
| 1790 | + if (maneuver.getManeuverLoss() != null) { |
|
| 1791 | + result.numberOfManeuvers.put(maneuver.getType(), |
|
| 1792 | + result.numberOfManeuvers.get(maneuver.getType()) + 1); |
|
| 1793 | + totalManeuverLossInMeters.put(maneuver.getType(), |
|
| 1794 | + totalManeuverLossInMeters.get(maneuver.getType()) |
|
| 1795 | + + maneuver.getManeuverLoss().getMeters()); |
|
| 1796 | + } |
|
| 1797 | + } |
|
| 1798 | + break; |
|
| 1799 | + case MARK_PASSING: |
|
| 1800 | + // analyze all mark passings, not only those after this leg's start, to catch the mark passing |
|
| 1801 | + // maneuver starting this leg, even if its time point is slightly before the mark passing starting this leg |
|
| 1802 | + MarkPassingManeuver mpm = (MarkPassingManeuver) maneuver; |
|
| 1803 | + if (mpm.getWaypointPassed() == trackedLeg.getLeg().getFrom()) { |
|
| 1804 | + result.sideToWhichMarkAtLegStartWasRounded = mpm.getSide(); |
|
| 1805 | + } |
|
| 1806 | + break; |
|
| 1807 | + default: |
|
| 1808 | + /* Do nothing here. |
|
| 1809 | + * Throwing an exception destroys the toggling (and maybe other behaviour) of the leaderboard. |
|
| 1810 | + */ |
|
| 1811 | + } |
|
| 1812 | + } |
|
| 1813 | + result.averageManeuverLossInMeters = new HashMap<ManeuverType, Double>(); |
|
| 1814 | + for (ManeuverType maneuverType : new ManeuverType[] { ManeuverType.TACK, ManeuverType.JIBE, |
|
| 1815 | + ManeuverType.PENALTY_CIRCLE }) { |
|
| 1816 | + if (result.numberOfManeuvers.get(maneuverType) != 0) { |
|
| 1817 | + result.averageManeuverLossInMeters.put( |
|
| 1818 | + maneuverType, |
|
| 1819 | + totalManeuverLossInMeters.get(maneuverType) |
|
| 1820 | + / result.numberOfManeuvers.get(maneuverType)); |
|
| 1821 | + } |
|
| 1822 | + } |
|
| 1823 | + } |
|
| 1824 | + } |
|
| 1825 | + } |
|
| 1826 | + return result; |
|
| 1827 | + } |
|
| 1828 | + |
|
| 1829 | + private Duration getGapAtEndOfPreviousLeg(TrackedLegOfCompetitor trackedLeg, final RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) { |
|
| 1830 | + final Duration result; |
|
| 1831 | + final Course course = trackedLeg.getTrackedLeg().getTrackedRace().getRace().getCourse(); |
|
| 1832 | + // if trackedLeg is the first leg, compute the gap at the start of this leg; otherwise, compute gap |
|
| 1833 | + // at the end of the previous leg |
|
| 1834 | + final TimePoint timePoint = trackedLeg.getStartTime(); |
|
| 1835 | + final TrackedLegOfCompetitor tloc; |
|
| 1836 | + if (course.getFirstWaypoint() == trackedLeg.getLeg().getFrom()) { |
|
| 1837 | + tloc = trackedLeg; |
|
| 1838 | + } else { |
|
| 1839 | + tloc = trackedLeg.getTrackedLeg().getTrackedRace().getTrackedLegFinishingAt(trackedLeg.getLeg().getFrom()) |
|
| 1840 | + .getTrackedLeg(trackedLeg.getCompetitor()); |
|
| 1841 | + } |
|
| 1842 | + result = trackedLeg.getTrackedLeg().getTrackedRace().getRankingMetric().getLegGapToLegLeaderInOwnTime(tloc, timePoint, rankingInfo, cache); |
|
| 1843 | + return result; |
|
| 1844 | + } |
|
| 1845 | + |
|
| 1846 | + private LeaderboardDTO getLiveLeaderboard(Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, |
|
| 1847 | + boolean addOverallDetails, TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException, ExecutionException { |
|
| 1848 | + LiveLeaderboardUpdater liveLeaderboardUpdater = getLiveLeaderboardUpdater(trackedRegattaRegistry, |
|
| 1849 | + baseDomainFactory); |
|
| 1850 | + return liveLeaderboardUpdater.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails); |
|
| 1851 | + } |
|
| 1852 | + |
|
| 1853 | + private LiveLeaderboardUpdater getLiveLeaderboardUpdater(TrackedRegattaRegistry trackedRegattaRegistry, |
|
| 1854 | + DomainFactory baseDomainFactory) { |
|
| 1855 | + LiveLeaderboardUpdater result = this.liveLeaderboardUpdater; |
|
| 1856 | + if (result == null) { |
|
| 1857 | + synchronized (this) { |
|
| 1858 | + result = this.liveLeaderboardUpdater; |
|
| 1859 | + if (result == null) { |
|
| 1860 | + this.liveLeaderboardUpdater = new LiveLeaderboardUpdater(this, trackedRegattaRegistry, baseDomainFactory); |
|
| 1861 | + result = this.liveLeaderboardUpdater; |
|
| 1862 | + } |
|
| 1863 | + } |
|
| 1864 | + } |
|
| 1865 | + return result; |
|
| 1866 | + } |
|
| 1867 | + |
|
| 1868 | + private LeaderboardDTOCache getLeaderboardDTOCache() { |
|
| 1869 | + LeaderboardDTOCache result = this.leaderboardDTOCache; |
|
| 1870 | + if (result == null) { |
|
| 1871 | + synchronized (this) { |
|
| 1872 | + result = this.leaderboardDTOCache; |
|
| 1873 | + if (result == null) { |
|
| 1874 | + // The leaderboard cache is invalidated upon all competitor and mark position changes; some analyzes |
|
| 1875 | + // are pretty expensive, such as the maneuver re-calculation. Waiting for the latest analysis after only a |
|
| 1876 | + // single fix was updated is too expensive if users use the replay feature while a race is still running. |
|
| 1877 | + // Therefore, using waitForLatestAnalyses==false seems appropriate here. |
|
| 1878 | + this.leaderboardDTOCache = new LeaderboardDTOCache(/* waitForLatestAnalyses */false, this); |
|
| 1879 | + result = this.leaderboardDTOCache; |
|
| 1880 | + } |
|
| 1881 | + } |
|
| 1882 | + } |
|
| 1883 | + return result; |
|
| 1884 | + } |
|
| 1885 | + |
|
| 1886 | + @Override |
|
| 1887 | + public LeaderboardDTO getLeaderboardDTO(TimePoint timePoint, |
|
| 1888 | + Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails, |
|
| 1889 | + TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory, boolean fillNetPointsUncorrected) throws NoWindException, |
|
| 1890 | + InterruptedException, ExecutionException { |
|
| 1891 | + LeaderboardDTO result = null; |
|
| 1892 | + if (timePoint == null) { |
|
| 1893 | + // timePoint==null means live mode; however, if we're after the end of all races and after all score |
|
| 1894 | + // corrections, don't use the live leaderboard updater which would keep re-calculating over and over again, but map |
|
| 1895 | + // this to a usual non-live call which uses the regular LeaderboardDTOCache which is invalidated properly |
|
| 1896 | + // when the tracked race associations or score corrections or tracked race contents changes: |
|
| 1897 | + final TimePoint nowMinusDelay = this.getNowMinusDelay(); |
|
| 1898 | + final TimePoint timePointOfLatestModification = this.getTimePointOfLatestModification(); |
|
| 1899 | + if (fillNetPointsUncorrected || (timePointOfLatestModification != null && !nowMinusDelay.before(timePointOfLatestModification))) { |
|
| 1900 | + // if there hasn't been any modification to the leaderboard since nowMinusDelay, use non-live mode |
|
| 1901 | + // and pull the result from the regular leaderboard cache: |
|
| 1902 | + timePoint = timePointOfLatestModification; |
|
| 1903 | + } else { |
|
| 1904 | + // don't use the regular leaderboard cache; the race still seems to be on; use the live leaderboard updater instead: |
|
| 1905 | + timePoint = null; |
|
| 1906 | + result = this.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, trackedRegattaRegistry, baseDomainFactory); |
|
| 1907 | + } |
|
| 1908 | + } |
|
| 1909 | + if (timePoint != null) { |
|
| 1910 | + if (fillNetPointsUncorrected) { |
|
| 1911 | + // explicitly filling the uncorrected net points requires uncached recalculation |
|
| 1912 | + result = computeDTO(timePoint, namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, /* waitForLatestAnalyses */ true, |
|
| 1913 | + trackedRegattaRegistry, baseDomainFactory, fillNetPointsUncorrected); |
|
| 1914 | + } else { |
|
| 1915 | + // in replay we'd like up-to-date results; they are still cached |
|
| 1916 | + // which is OK because the cache is invalidated whenever any of the tracked races attached to the |
|
| 1917 | + // leaderboard changes. |
|
| 1918 | + result = getLeaderboardDTOCache().getLeaderboardByName(timePoint, |
|
| 1919 | + namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, baseDomainFactory, |
|
| 1920 | + trackedRegattaRegistry); |
|
| 1921 | + } |
|
| 1922 | + } |
|
| 1923 | + return result; |
|
| 1924 | + } |
|
| 1925 | + |
|
| 1926 | + public String toString() { |
|
| 1927 | + return getName() + " " + (getDefaultCourseArea() != null ? getDefaultCourseArea().getName() : "<No course area defined>") + " " + (getScoringScheme() != null ? getScoringScheme().getType().name() : "<No scoring scheme set>"); |
|
| 1928 | + } |
|
| 1929 | + |
|
| 1930 | + @Override |
|
| 1931 | + public NumberOfCompetitorsInLeaderboardFetcher getNumberOfCompetitorsInLeaderboardFetcher() { |
|
| 1932 | + return new NumberOfCompetitorsFetcherImpl(); |
|
| 1933 | + } |
|
| 1934 | + |
|
| 1935 | + protected abstract LeaderboardType getLeaderboardType(); |
|
| 1936 | + |
|
| 1937 | +} |
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/FlexibleLeaderboardImpl.java
| ... | ... | @@ -1,336 +1,342 @@ |
| 1 | -package com.sap.sailing.domain.leaderboard.impl;
|
|
| 2 | -
|
|
| 3 | -import java.io.IOException;
|
|
| 4 | -import java.io.ObjectInputStream;
|
|
| 5 | -import java.io.ObjectOutputStream;
|
|
| 6 | -import java.io.ObjectStreamException;
|
|
| 7 | -import java.util.ArrayList;
|
|
| 8 | -import java.util.Arrays;
|
|
| 9 | -import java.util.Collections;
|
|
| 10 | -import java.util.HashMap;
|
|
| 11 | -import java.util.List;
|
|
| 12 | -import java.util.Map;
|
|
| 13 | -import java.util.logging.Logger;
|
|
| 14 | -
|
|
| 15 | -import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
|
|
| 16 | -import com.sap.sailing.domain.base.Competitor;
|
|
| 17 | -import com.sap.sailing.domain.base.CourseArea;
|
|
| 18 | -import com.sap.sailing.domain.base.Fleet;
|
|
| 19 | -import com.sap.sailing.domain.base.RaceColumn;
|
|
| 20 | -import com.sap.sailing.domain.base.RaceColumnListener;
|
|
| 21 | -import com.sap.sailing.domain.base.impl.AbstractRaceExecutionOrderProvider;
|
|
| 22 | -import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
|
|
| 23 | -import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn;
|
|
| 24 | -import com.sap.sailing.domain.leaderboard.ScoringScheme;
|
|
| 25 | -import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
|
|
| 26 | -import com.sap.sailing.domain.racelog.RaceLogStore;
|
|
| 27 | -import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore;
|
|
| 28 | -import com.sap.sailing.domain.regattalike.BaseRegattaLikeImpl;
|
|
| 29 | -import com.sap.sailing.domain.regattalike.FlexibleLeaderboardAsRegattaLikeIdentifier;
|
|
| 30 | -import com.sap.sailing.domain.regattalike.IsRegattaLike;
|
|
| 31 | -import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
|
|
| 32 | -import com.sap.sailing.domain.regattalike.RegattaLikeListener;
|
|
| 33 | -import com.sap.sailing.domain.regattalog.RegattaLogStore;
|
|
| 34 | -import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
|
|
| 35 | -import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider;
|
|
| 36 | -import com.sap.sailing.domain.tracking.TrackedRace;
|
|
| 37 | -import com.sap.sse.common.Duration;
|
|
| 38 | -
|
|
| 39 | -/**
|
|
| 40 | - * A leaderboard implementation that allows users to flexibly configure which columns exist. No constraints need to be observed regarding
|
|
| 41 | - * the columns belonging to the same regatta or even boat class.<p>
|
|
| 42 | - *
|
|
| 43 | - * The flexible leaderboard listens as {@link RaceColumnListener} on all its {@link RaceColumn}s and forwards all events to
|
|
| 44 | - * all {@link RaceColumnListener}s subscribed with this leaderboard.
|
|
| 45 | - *
|
|
| 46 | - * @author Axel Uhl (D043530)
|
|
| 47 | - *
|
|
| 48 | - */
|
|
| 49 | -public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements FlexibleLeaderboard {
|
|
| 50 | - private static Logger logger = Logger.getLogger(FlexibleLeaderboardImpl.class.getName());
|
|
| 51 | -
|
|
| 52 | - protected static final DefaultFleetImpl defaultFleet = new DefaultFleetImpl();
|
|
| 53 | - private static final long serialVersionUID = -5708971849158747846L;
|
|
| 54 | - private final List<FlexibleRaceColumn> races;
|
|
| 55 | - private final ScoringScheme scoringScheme;
|
|
| 56 | - private String name;
|
|
| 57 | - private transient RaceLogStore raceLogStore;
|
|
| 58 | - private CourseArea courseArea;
|
|
| 59 | - private RaceExecutionOrderProvider raceExecutionOrderProvider;
|
|
| 60 | -
|
|
| 61 | - /**
|
|
| 62 | - * @see RegattaLog for the reason why the leaderboard manages a {@code RegattaLog}
|
|
| 63 | - */
|
|
| 64 | - private final IsRegattaLike regattaLikeHelper;
|
|
| 65 | -
|
|
| 66 | - public FlexibleLeaderboardImpl(String name, ThresholdBasedResultDiscardingRule resultDiscardingRule,
|
|
| 67 | - ScoringScheme scoringScheme, CourseArea courseArea) {
|
|
| 68 | - this(EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE,
|
|
| 69 | - name, resultDiscardingRule, scoringScheme, courseArea);
|
|
| 70 | - }
|
|
| 71 | -
|
|
| 72 | - public FlexibleLeaderboardImpl(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore,
|
|
| 73 | - String name, ThresholdBasedResultDiscardingRule resultDiscardingRule,
|
|
| 74 | - ScoringScheme scoringScheme, CourseArea courseArea) {
|
|
| 75 | - super(resultDiscardingRule);
|
|
| 76 | - this.scoringScheme = scoringScheme;
|
|
| 77 | - if (name == null) {
|
|
| 78 | - throw new IllegalArgumentException("A leaderboard's name must not be null");
|
|
| 79 | - }
|
|
| 80 | - this.name = name;
|
|
| 81 | - this.races = new ArrayList<>();
|
|
| 82 | - this.raceLogStore = raceLogStore;
|
|
| 83 | - this.courseArea = courseArea;
|
|
| 84 | - this.regattaLikeHelper = new BaseRegattaLikeImpl(new FlexibleLeaderboardAsRegattaLikeIdentifier(this), regattaLogStore) {
|
|
| 85 | - private static final long serialVersionUID = 4082392360832548953L;
|
|
| 86 | -
|
|
| 87 | - @Override
|
|
| 88 | - public RaceColumn getRaceColumnByName(String raceColumnName) {
|
|
| 89 | - return getRaceColumnByName(raceColumnName);
|
|
| 90 | - }
|
|
| 91 | - };
|
|
| 92 | - this.raceExecutionOrderProvider = new RaceExecutionOrderCache();
|
|
| 93 | - }
|
|
| 94 | -
|
|
| 95 | - /**
|
|
| 96 | - * Deserialization has to be maintained in lock-step with {@link #writeObject(ObjectOutputStream) serialization}.
|
|
| 97 | - * When de-serializing, a possibly remote {@link #raceLogStore} is ignored because it is transient. Instead, an
|
|
| 98 | - * {@link EmptyRaceLogStore} is used for the de-serialized instance. A new {@link RaceLogInformation} is
|
|
| 99 | - * assembled for this empty race log and applied to all columns.
|
|
| 100 | - */
|
|
| 101 | - private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
|
|
| 102 | - ois.defaultReadObject();
|
|
| 103 | - raceLogStore = EmptyRaceLogStore.INSTANCE;
|
|
| 104 | - for (RaceColumn column : getRaceColumns()) {
|
|
| 105 | - column.setRaceLogInformation(raceLogStore, new FlexibleLeaderboardAsRegattaLikeIdentifier(this));
|
|
| 106 | - }
|
|
| 107 | - }
|
|
| 108 | -
|
|
| 109 | - protected Object readResolve() throws ObjectStreamException {
|
|
| 110 | - raceExecutionOrderProvider.triggerUpdate();
|
|
| 111 | - return this;
|
|
| 112 | - }
|
|
| 113 | -
|
|
| 114 | - @Override
|
|
| 115 | - public String getName() {
|
|
| 116 | - return name;
|
|
| 117 | - }
|
|
| 118 | -
|
|
| 119 | - /**
|
|
| 120 | - * @param newName must not be <code>null</code>
|
|
| 121 | - */
|
|
| 122 | - public void setName(String newName) {
|
|
| 123 | - if (newName == null) {
|
|
| 124 | - throw new IllegalArgumentException("A leaderboard's name must not be null");
|
|
| 125 | - }
|
|
| 126 | - this.name = newName;
|
|
| 127 | - }
|
|
| 128 | -
|
|
| 129 | - @Override
|
|
| 130 | - public RaceColumn addRace(TrackedRace race, String columnName, boolean medalRace) {
|
|
| 131 | - FlexibleRaceColumn column = addRaceColumn(columnName, medalRace, /* logAlreadyExistingColumn */false);
|
|
| 132 | - column.setTrackedRace(defaultFleet, race); // triggers listeners because this object was registered above as
|
|
| 133 | - // race column listener on the column
|
|
| 134 | - return column;
|
|
| 135 | - }
|
|
| 136 | -
|
|
| 137 | - @Override
|
|
| 138 | - public FlexibleRaceColumn addRaceColumn(String name, boolean medalRace) {
|
|
| 139 | - return addRaceColumn(name, medalRace, /* logAlreadyExistingColumn */true);
|
|
| 140 | - }
|
|
| 141 | -
|
|
| 142 | - private FlexibleRaceColumn addRaceColumn(String name, boolean medalRace, boolean logAlreadyExistingColumn) {
|
|
| 143 | - FlexibleRaceColumn column = getRaceColumnByName(name);
|
|
| 144 | - if (column != null) {
|
|
| 145 | - if (logAlreadyExistingColumn) {
|
|
| 146 | - final String msg = "Trying to create race column with duplicate name " + name + " in leaderboard " + getName();
|
|
| 147 | - logger.severe(msg);
|
|
| 148 | - }
|
|
| 149 | - } else {
|
|
| 150 | - column = createRaceColumn(name, medalRace);
|
|
| 151 | - column.addRaceColumnListener(this);
|
|
| 152 | - races.add(column);
|
|
| 153 | - column.setRaceLogInformation(raceLogStore, new FlexibleLeaderboardAsRegattaLikeIdentifier(this));
|
|
| 154 | - getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(column);
|
|
| 155 | - }
|
|
| 156 | - return column;
|
|
| 157 | - }
|
|
| 158 | -
|
|
| 159 | - @Override
|
|
| 160 | - public FlexibleRaceColumn getRaceColumnByName(String columnName) {
|
|
| 161 | - return (FlexibleRaceColumn) super.getRaceColumnByName(columnName);
|
|
| 162 | - }
|
|
| 163 | -
|
|
| 164 | - @Override
|
|
| 165 | - public Fleet getFleet(String fleetName) {
|
|
| 166 | - Fleet result;
|
|
| 167 | - if (fleetName == null) {
|
|
| 168 | - result = defaultFleet;
|
|
| 169 | - } else {
|
|
| 170 | - result = super.getFleet(fleetName);
|
|
| 171 | - }
|
|
| 172 | - return result;
|
|
| 173 | - }
|
|
| 174 | -
|
|
| 175 | - @Override
|
|
| 176 | - public void removeRaceColumn(String columnName) {
|
|
| 177 | - final FlexibleRaceColumn raceColumn = getRaceColumnByName(columnName);
|
|
| 178 | - if (raceColumn != null) {
|
|
| 179 | - for (Fleet fleet : raceColumn.getFleets()) {
|
|
| 180 | - raceLogStore.removeRaceLog(raceColumn.getRaceLogIdentifier(fleet));
|
|
| 181 | - if (raceColumn.getTrackedRace(fleet) != null) {
|
|
| 182 | - raceColumn.getTrackedRace(fleet).detachRaceExecutionOrderProvider(raceExecutionOrderProvider);
|
|
| 183 | - }
|
|
| 184 | - }
|
|
| 185 | - races.remove(raceColumn);
|
|
| 186 | - getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn);
|
|
| 187 | - raceColumn.removeRaceColumnListener(this);
|
|
| 188 | - }
|
|
| 189 | - }
|
|
| 190 | -
|
|
| 191 | - @Override
|
|
| 192 | - public Iterable<RaceColumn> getRaceColumns() {
|
|
| 193 | - final Iterable<RaceColumn> result;
|
|
| 194 | - if (races != null) {
|
|
| 195 | - result = Collections.unmodifiableCollection(new ArrayList<RaceColumn>(races));
|
|
| 196 | - } else {
|
|
| 197 | - result = null;
|
|
| 198 | - }
|
|
| 199 | - return result;
|
|
| 200 | - }
|
|
| 201 | -
|
|
| 202 | - protected RaceColumnImpl createRaceColumn(String column, boolean medalRace) {
|
|
| 203 | - return new RaceColumnImpl(column, medalRace, raceExecutionOrderProvider);
|
|
| 204 | - }
|
|
| 205 | -
|
|
| 206 | - protected Iterable<Fleet> turnNullOrEmptyFleetsIntoDefaultFleet(Fleet... fleets) {
|
|
| 207 | - Iterable<Fleet> theFleets;
|
|
| 208 | - if (fleets == null || fleets.length == 0) {
|
|
| 209 | - Fleet defaultfleetCasted = defaultFleet;
|
|
| 210 | - theFleets = Collections.singleton(defaultfleetCasted);
|
|
| 211 | - } else {
|
|
| 212 | - theFleets = Arrays.asList(fleets);
|
|
| 213 | - }
|
|
| 214 | - return theFleets;
|
|
| 215 | - }
|
|
| 216 | -
|
|
| 217 | - @Override
|
|
| 218 | - public void moveRaceColumnUp(String name) {
|
|
| 219 | - FlexibleRaceColumn race = null;
|
|
| 220 | - for (FlexibleRaceColumn r : races) {
|
|
| 221 | - if (r.getName().equals(name)) {
|
|
| 222 | - race = r;
|
|
| 223 | - }
|
|
| 224 | - }
|
|
| 225 | - if (race != null) {
|
|
| 226 | - int index = 0;
|
|
| 227 | - index = races.lastIndexOf(race);
|
|
| 228 | - index--;
|
|
| 229 | - if (index >= 0) {
|
|
| 230 | - races.remove(race);
|
|
| 231 | - races.add(index, race);
|
|
| 232 | - getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(race, index);
|
|
| 233 | - }
|
|
| 234 | - }
|
|
| 235 | - }
|
|
| 236 | -
|
|
| 237 | - @Override
|
|
| 238 | - public void moveRaceColumnDown(String name) {
|
|
| 239 | - FlexibleRaceColumn race = null;
|
|
| 240 | - for (FlexibleRaceColumn r : races) {
|
|
| 241 | - if (r.getName().equals(name)) {
|
|
| 242 | - race = r;
|
|
| 243 | - }
|
|
| 244 | - }
|
|
| 245 | - if (race != null) {
|
|
| 246 | - int index = 0;
|
|
| 247 | - index = races.lastIndexOf(race);
|
|
| 248 | - if (index != -1) {
|
|
| 249 | - index++;
|
|
| 250 | - if (index < races.size()) {
|
|
| 251 | - races.remove(race);
|
|
| 252 | - races.add(index, race);
|
|
| 253 | - getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(race, index);
|
|
| 254 | - }
|
|
| 255 | - }
|
|
| 256 | - }
|
|
| 257 | - }
|
|
| 258 | -
|
|
| 259 | - @Override
|
|
| 260 | - public void updateIsMedalRace(String raceName, boolean isMedalRace) {
|
|
| 261 | - FlexibleRaceColumn race = null;
|
|
| 262 | - for (FlexibleRaceColumn r : races) {
|
|
| 263 | - if (r.getName().equals(raceName))
|
|
| 264 | - race = r;
|
|
| 265 | - }
|
|
| 266 | - if (race != null) {
|
|
| 267 | - race.setIsMedalRace(isMedalRace);
|
|
| 268 | - }
|
|
| 269 | - }
|
|
| 270 | -
|
|
| 271 | - @Override
|
|
| 272 | - public ScoringScheme getScoringScheme() {
|
|
| 273 | - return scoringScheme;
|
|
| 274 | - }
|
|
| 275 | -
|
|
| 276 | - @Override
|
|
| 277 | - public CourseArea getDefaultCourseArea() {
|
|
| 278 | - return courseArea;
|
|
| 279 | - }
|
|
| 280 | -
|
|
| 281 | - @Override
|
|
| 282 | - public void setDefaultCourseArea(CourseArea newCourseArea) {
|
|
| 283 | - this.courseArea = newCourseArea;
|
|
| 284 | - }
|
|
| 285 | -
|
|
| 286 | - @Override
|
|
| 287 | - public IsRegattaLike getRegattaLike() {
|
|
| 288 | - return regattaLikeHelper;
|
|
| 289 | - }
|
|
| 290 | -
|
|
| 291 | - @Override
|
|
| 292 | - public RegattaLog getRegattaLog() {
|
|
| 293 | - return regattaLikeHelper.getRegattaLog();
|
|
| 294 | - }
|
|
| 295 | -
|
|
| 296 | - @Override
|
|
| 297 | - public RegattaLikeIdentifier getRegattaLikeIdentifier() {
|
|
| 298 | - return regattaLikeHelper.getRegattaLikeIdentifier();
|
|
| 299 | - }
|
|
| 300 | -
|
|
| 301 | - @Override
|
|
| 302 | - public void addListener(RegattaLikeListener listener) {
|
|
| 303 | - regattaLikeHelper.addListener(listener);
|
|
| 304 | - }
|
|
| 305 | -
|
|
| 306 | - @Override
|
|
| 307 | - public void removeListener(RegattaLikeListener listener) {
|
|
| 308 | - regattaLikeHelper.removeListener(listener);
|
|
| 309 | - }
|
|
| 310 | -
|
|
| 311 | - @Override
|
|
| 312 | - public Double getTimeOnTimeFactor(Competitor competitor) {
|
|
| 313 | - return regattaLikeHelper.getTimeOnTimeFactor(competitor);
|
|
| 314 | - }
|
|
| 315 | -
|
|
| 316 | - @Override
|
|
| 317 | - public Duration getTimeOnDistanceAllowancePerNauticalMile(Competitor competitor) {
|
|
| 318 | - return regattaLikeHelper.getTimeOnDistanceAllowancePerNauticalMile(competitor);
|
|
| 319 | - }
|
|
| 320 | -
|
|
| 321 | - private class RaceExecutionOrderCache extends AbstractRaceExecutionOrderProvider {
|
|
| 322 | - private static final long serialVersionUID = 652833386555762661L;
|
|
| 323 | -
|
|
| 324 | - public RaceExecutionOrderCache() {
|
|
| 325 | - super();
|
|
| 326 | - addRaceColumnListener(this);
|
|
| 327 | - }
|
|
| 328 | -
|
|
| 329 | - @Override
|
|
| 330 | - protected Map<Fleet, Iterable<? extends RaceColumn>> getRaceColumnsOfSeries() {
|
|
| 331 | - final Map<Fleet, Iterable<? extends RaceColumn>> result = new HashMap<>();
|
|
| 332 | - result.put(FlexibleLeaderboardImpl.defaultFleet, getRaceColumns());
|
|
| 333 | - return result;
|
|
| 334 | - }
|
|
| 335 | - }
|
|
| 336 | -}
|
|
| 1 | +package com.sap.sailing.domain.leaderboard.impl; |
|
| 2 | + |
|
| 3 | +import java.io.IOException; |
|
| 4 | +import java.io.ObjectInputStream; |
|
| 5 | +import java.io.ObjectOutputStream; |
|
| 6 | +import java.io.ObjectStreamException; |
|
| 7 | +import java.util.ArrayList; |
|
| 8 | +import java.util.Arrays; |
|
| 9 | +import java.util.Collections; |
|
| 10 | +import java.util.HashMap; |
|
| 11 | +import java.util.List; |
|
| 12 | +import java.util.Map; |
|
| 13 | +import java.util.logging.Logger; |
|
| 14 | + |
|
| 15 | +import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; |
|
| 16 | +import com.sap.sailing.domain.base.Competitor; |
|
| 17 | +import com.sap.sailing.domain.base.CourseArea; |
|
| 18 | +import com.sap.sailing.domain.base.Fleet; |
|
| 19 | +import com.sap.sailing.domain.base.RaceColumn; |
|
| 20 | +import com.sap.sailing.domain.base.RaceColumnListener; |
|
| 21 | +import com.sap.sailing.domain.base.impl.AbstractRaceExecutionOrderProvider; |
|
| 22 | +import com.sap.sailing.domain.common.LeaderboardType; |
|
| 23 | +import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; |
|
| 24 | +import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn; |
|
| 25 | +import com.sap.sailing.domain.leaderboard.ScoringScheme; |
|
| 26 | +import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; |
|
| 27 | +import com.sap.sailing.domain.racelog.RaceLogStore; |
|
| 28 | +import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore; |
|
| 29 | +import com.sap.sailing.domain.regattalike.BaseRegattaLikeImpl; |
|
| 30 | +import com.sap.sailing.domain.regattalike.FlexibleLeaderboardAsRegattaLikeIdentifier; |
|
| 31 | +import com.sap.sailing.domain.regattalike.IsRegattaLike; |
|
| 32 | +import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier; |
|
| 33 | +import com.sap.sailing.domain.regattalike.RegattaLikeListener; |
|
| 34 | +import com.sap.sailing.domain.regattalog.RegattaLogStore; |
|
| 35 | +import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore; |
|
| 36 | +import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider; |
|
| 37 | +import com.sap.sailing.domain.tracking.TrackedRace; |
|
| 38 | +import com.sap.sse.common.Duration; |
|
| 39 | + |
|
| 40 | +/** |
|
| 41 | + * A leaderboard implementation that allows users to flexibly configure which columns exist. No constraints need to be observed regarding |
|
| 42 | + * the columns belonging to the same regatta or even boat class.<p> |
|
| 43 | + * |
|
| 44 | + * The flexible leaderboard listens as {@link RaceColumnListener} on all its {@link RaceColumn}s and forwards all events to |
|
| 45 | + * all {@link RaceColumnListener}s subscribed with this leaderboard. |
|
| 46 | + * |
|
| 47 | + * @author Axel Uhl (D043530) |
|
| 48 | + * |
|
| 49 | + */ |
|
| 50 | +public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements FlexibleLeaderboard { |
|
| 51 | + private static Logger logger = Logger.getLogger(FlexibleLeaderboardImpl.class.getName()); |
|
| 52 | + |
|
| 53 | + protected static final DefaultFleetImpl defaultFleet = new DefaultFleetImpl(); |
|
| 54 | + private static final long serialVersionUID = -5708971849158747846L; |
|
| 55 | + private final List<FlexibleRaceColumn> races; |
|
| 56 | + private final ScoringScheme scoringScheme; |
|
| 57 | + private String name; |
|
| 58 | + private transient RaceLogStore raceLogStore; |
|
| 59 | + private CourseArea courseArea; |
|
| 60 | + private RaceExecutionOrderProvider raceExecutionOrderProvider; |
|
| 61 | + |
|
| 62 | + /** |
|
| 63 | + * @see RegattaLog for the reason why the leaderboard manages a {@code RegattaLog} |
|
| 64 | + */ |
|
| 65 | + private final IsRegattaLike regattaLikeHelper; |
|
| 66 | + |
|
| 67 | + public FlexibleLeaderboardImpl(String name, ThresholdBasedResultDiscardingRule resultDiscardingRule, |
|
| 68 | + ScoringScheme scoringScheme, CourseArea courseArea) { |
|
| 69 | + this(EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, |
|
| 70 | + name, resultDiscardingRule, scoringScheme, courseArea); |
|
| 71 | + } |
|
| 72 | + |
|
| 73 | + public FlexibleLeaderboardImpl(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, |
|
| 74 | + String name, ThresholdBasedResultDiscardingRule resultDiscardingRule, |
|
| 75 | + ScoringScheme scoringScheme, CourseArea courseArea) { |
|
| 76 | + super(resultDiscardingRule); |
|
| 77 | + this.scoringScheme = scoringScheme; |
|
| 78 | + if (name == null) { |
|
| 79 | + throw new IllegalArgumentException("A leaderboard's name must not be null"); |
|
| 80 | + } |
|
| 81 | + this.name = name; |
|
| 82 | + this.races = new ArrayList<>(); |
|
| 83 | + this.raceLogStore = raceLogStore; |
|
| 84 | + this.courseArea = courseArea; |
|
| 85 | + this.regattaLikeHelper = new BaseRegattaLikeImpl(new FlexibleLeaderboardAsRegattaLikeIdentifier(this), regattaLogStore) { |
|
| 86 | + private static final long serialVersionUID = 4082392360832548953L; |
|
| 87 | + |
|
| 88 | + @Override |
|
| 89 | + public RaceColumn getRaceColumnByName(String raceColumnName) { |
|
| 90 | + return getRaceColumnByName(raceColumnName); |
|
| 91 | + } |
|
| 92 | + }; |
|
| 93 | + this.raceExecutionOrderProvider = new RaceExecutionOrderCache(); |
|
| 94 | + } |
|
| 95 | + |
|
| 96 | + /** |
|
| 97 | + * Deserialization has to be maintained in lock-step with {@link #writeObject(ObjectOutputStream) serialization}. |
|
| 98 | + * When de-serializing, a possibly remote {@link #raceLogStore} is ignored because it is transient. Instead, an |
|
| 99 | + * {@link EmptyRaceLogStore} is used for the de-serialized instance. A new {@link RaceLogInformation} is |
|
| 100 | + * assembled for this empty race log and applied to all columns. |
|
| 101 | + */ |
|
| 102 | + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { |
|
| 103 | + ois.defaultReadObject(); |
|
| 104 | + raceLogStore = EmptyRaceLogStore.INSTANCE; |
|
| 105 | + for (RaceColumn column : getRaceColumns()) { |
|
| 106 | + column.setRaceLogInformation(raceLogStore, new FlexibleLeaderboardAsRegattaLikeIdentifier(this)); |
|
| 107 | + } |
|
| 108 | + } |
|
| 109 | + |
|
| 110 | + protected Object readResolve() throws ObjectStreamException { |
|
| 111 | + raceExecutionOrderProvider.triggerUpdate(); |
|
| 112 | + return this; |
|
| 113 | + } |
|
| 114 | + |
|
| 115 | + @Override |
|
| 116 | + public String getName() { |
|
| 117 | + return name; |
|
| 118 | + } |
|
| 119 | + |
|
| 120 | + /** |
|
| 121 | + * @param newName must not be <code>null</code> |
|
| 122 | + */ |
|
| 123 | + public void setName(String newName) { |
|
| 124 | + if (newName == null) { |
|
| 125 | + throw new IllegalArgumentException("A leaderboard's name must not be null"); |
|
| 126 | + } |
|
| 127 | + this.name = newName; |
|
| 128 | + } |
|
| 129 | + |
|
| 130 | + @Override |
|
| 131 | + public RaceColumn addRace(TrackedRace race, String columnName, boolean medalRace) { |
|
| 132 | + FlexibleRaceColumn column = addRaceColumn(columnName, medalRace, /* logAlreadyExistingColumn */false); |
|
| 133 | + column.setTrackedRace(defaultFleet, race); // triggers listeners because this object was registered above as |
|
| 134 | + // race column listener on the column |
|
| 135 | + return column; |
|
| 136 | + } |
|
| 137 | + |
|
| 138 | + @Override |
|
| 139 | + public FlexibleRaceColumn addRaceColumn(String name, boolean medalRace) { |
|
| 140 | + return addRaceColumn(name, medalRace, /* logAlreadyExistingColumn */true); |
|
| 141 | + } |
|
| 142 | + |
|
| 143 | + private FlexibleRaceColumn addRaceColumn(String name, boolean medalRace, boolean logAlreadyExistingColumn) { |
|
| 144 | + FlexibleRaceColumn column = getRaceColumnByName(name); |
|
| 145 | + if (column != null) { |
|
| 146 | + if (logAlreadyExistingColumn) { |
|
| 147 | + final String msg = "Trying to create race column with duplicate name " + name + " in leaderboard " + getName(); |
|
| 148 | + logger.severe(msg); |
|
| 149 | + } |
|
| 150 | + } else { |
|
| 151 | + column = createRaceColumn(name, medalRace); |
|
| 152 | + column.addRaceColumnListener(this); |
|
| 153 | + races.add(column); |
|
| 154 | + column.setRaceLogInformation(raceLogStore, new FlexibleLeaderboardAsRegattaLikeIdentifier(this)); |
|
| 155 | + getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(column); |
|
| 156 | + } |
|
| 157 | + return column; |
|
| 158 | + } |
|
| 159 | + |
|
| 160 | + @Override |
|
| 161 | + public FlexibleRaceColumn getRaceColumnByName(String columnName) { |
|
| 162 | + return (FlexibleRaceColumn) super.getRaceColumnByName(columnName); |
|
| 163 | + } |
|
| 164 | + |
|
| 165 | + @Override |
|
| 166 | + public Fleet getFleet(String fleetName) { |
|
| 167 | + Fleet result; |
|
| 168 | + if (fleetName == null) { |
|
| 169 | + result = defaultFleet; |
|
| 170 | + } else { |
|
| 171 | + result = super.getFleet(fleetName); |
|
| 172 | + } |
|
| 173 | + return result; |
|
| 174 | + } |
|
| 175 | + |
|
| 176 | + @Override |
|
| 177 | + public void removeRaceColumn(String columnName) { |
|
| 178 | + final FlexibleRaceColumn raceColumn = getRaceColumnByName(columnName); |
|
| 179 | + if (raceColumn != null) { |
|
| 180 | + for (Fleet fleet : raceColumn.getFleets()) { |
|
| 181 | + raceLogStore.removeRaceLog(raceColumn.getRaceLogIdentifier(fleet)); |
|
| 182 | + if (raceColumn.getTrackedRace(fleet) != null) { |
|
| 183 | + raceColumn.getTrackedRace(fleet).detachRaceExecutionOrderProvider(raceExecutionOrderProvider); |
|
| 184 | + } |
|
| 185 | + } |
|
| 186 | + races.remove(raceColumn); |
|
| 187 | + getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(raceColumn); |
|
| 188 | + raceColumn.removeRaceColumnListener(this); |
|
| 189 | + } |
|
| 190 | + } |
|
| 191 | + |
|
| 192 | + @Override |
|
| 193 | + public Iterable<RaceColumn> getRaceColumns() { |
|
| 194 | + final Iterable<RaceColumn> result; |
|
| 195 | + if (races != null) { |
|
| 196 | + result = Collections.unmodifiableCollection(new ArrayList<RaceColumn>(races)); |
|
| 197 | + } else { |
|
| 198 | + result = null; |
|
| 199 | + } |
|
| 200 | + return result; |
|
| 201 | + } |
|
| 202 | + |
|
| 203 | + protected RaceColumnImpl createRaceColumn(String column, boolean medalRace) { |
|
| 204 | + return new RaceColumnImpl(column, medalRace, raceExecutionOrderProvider); |
|
| 205 | + } |
|
| 206 | + |
|
| 207 | + protected Iterable<Fleet> turnNullOrEmptyFleetsIntoDefaultFleet(Fleet... fleets) { |
|
| 208 | + Iterable<Fleet> theFleets; |
|
| 209 | + if (fleets == null || fleets.length == 0) { |
|
| 210 | + Fleet defaultfleetCasted = defaultFleet; |
|
| 211 | + theFleets = Collections.singleton(defaultfleetCasted); |
|
| 212 | + } else { |
|
| 213 | + theFleets = Arrays.asList(fleets); |
|
| 214 | + } |
|
| 215 | + return theFleets; |
|
| 216 | + } |
|
| 217 | + |
|
| 218 | + @Override |
|
| 219 | + public void moveRaceColumnUp(String name) { |
|
| 220 | + FlexibleRaceColumn race = null; |
|
| 221 | + for (FlexibleRaceColumn r : races) { |
|
| 222 | + if (r.getName().equals(name)) { |
|
| 223 | + race = r; |
|
| 224 | + } |
|
| 225 | + } |
|
| 226 | + if (race != null) { |
|
| 227 | + int index = 0; |
|
| 228 | + index = races.lastIndexOf(race); |
|
| 229 | + index--; |
|
| 230 | + if (index >= 0) { |
|
| 231 | + races.remove(race); |
|
| 232 | + races.add(index, race); |
|
| 233 | + getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(race, index); |
|
| 234 | + } |
|
| 235 | + } |
|
| 236 | + } |
|
| 237 | + |
|
| 238 | + @Override |
|
| 239 | + public void moveRaceColumnDown(String name) { |
|
| 240 | + FlexibleRaceColumn race = null; |
|
| 241 | + for (FlexibleRaceColumn r : races) { |
|
| 242 | + if (r.getName().equals(name)) { |
|
| 243 | + race = r; |
|
| 244 | + } |
|
| 245 | + } |
|
| 246 | + if (race != null) { |
|
| 247 | + int index = 0; |
|
| 248 | + index = races.lastIndexOf(race); |
|
| 249 | + if (index != -1) { |
|
| 250 | + index++; |
|
| 251 | + if (index < races.size()) { |
|
| 252 | + races.remove(race); |
|
| 253 | + races.add(index, race); |
|
| 254 | + getRaceColumnListeners().notifyListenersAboutRaceColumnMoved(race, index); |
|
| 255 | + } |
|
| 256 | + } |
|
| 257 | + } |
|
| 258 | + } |
|
| 259 | + |
|
| 260 | + @Override |
|
| 261 | + public void updateIsMedalRace(String raceName, boolean isMedalRace) { |
|
| 262 | + FlexibleRaceColumn race = null; |
|
| 263 | + for (FlexibleRaceColumn r : races) { |
|
| 264 | + if (r.getName().equals(raceName)) |
|
| 265 | + race = r; |
|
| 266 | + } |
|
| 267 | + if (race != null) { |
|
| 268 | + race.setIsMedalRace(isMedalRace); |
|
| 269 | + } |
|
| 270 | + } |
|
| 271 | + |
|
| 272 | + @Override |
|
| 273 | + public ScoringScheme getScoringScheme() { |
|
| 274 | + return scoringScheme; |
|
| 275 | + } |
|
| 276 | + |
|
| 277 | + @Override |
|
| 278 | + public CourseArea getDefaultCourseArea() { |
|
| 279 | + return courseArea; |
|
| 280 | + } |
|
| 281 | + |
|
| 282 | + @Override |
|
| 283 | + public void setDefaultCourseArea(CourseArea newCourseArea) { |
|
| 284 | + this.courseArea = newCourseArea; |
|
| 285 | + } |
|
| 286 | + |
|
| 287 | + @Override |
|
| 288 | + public IsRegattaLike getRegattaLike() { |
|
| 289 | + return regattaLikeHelper; |
|
| 290 | + } |
|
| 291 | + |
|
| 292 | + @Override |
|
| 293 | + public RegattaLog getRegattaLog() { |
|
| 294 | + return regattaLikeHelper.getRegattaLog(); |
|
| 295 | + } |
|
| 296 | + |
|
| 297 | + @Override |
|
| 298 | + public RegattaLikeIdentifier getRegattaLikeIdentifier() { |
|
| 299 | + return regattaLikeHelper.getRegattaLikeIdentifier(); |
|
| 300 | + } |
|
| 301 | + |
|
| 302 | + @Override |
|
| 303 | + public void addListener(RegattaLikeListener listener) { |
|
| 304 | + regattaLikeHelper.addListener(listener); |
|
| 305 | + } |
|
| 306 | + |
|
| 307 | + @Override |
|
| 308 | + public void removeListener(RegattaLikeListener listener) { |
|
| 309 | + regattaLikeHelper.removeListener(listener); |
|
| 310 | + } |
|
| 311 | + |
|
| 312 | + @Override |
|
| 313 | + public Double getTimeOnTimeFactor(Competitor competitor) { |
|
| 314 | + return regattaLikeHelper.getTimeOnTimeFactor(competitor); |
|
| 315 | + } |
|
| 316 | + |
|
| 317 | + @Override |
|
| 318 | + public Duration getTimeOnDistanceAllowancePerNauticalMile(Competitor competitor) { |
|
| 319 | + return regattaLikeHelper.getTimeOnDistanceAllowancePerNauticalMile(competitor); |
|
| 320 | + } |
|
| 321 | + |
|
| 322 | + private class RaceExecutionOrderCache extends AbstractRaceExecutionOrderProvider { |
|
| 323 | + private static final long serialVersionUID = 652833386555762661L; |
|
| 324 | + |
|
| 325 | + public RaceExecutionOrderCache() { |
|
| 326 | + super(); |
|
| 327 | + addRaceColumnListener(this); |
|
| 328 | + } |
|
| 329 | + |
|
| 330 | + @Override |
|
| 331 | + protected Map<Fleet, Iterable<? extends RaceColumn>> getRaceColumnsOfSeries() { |
|
| 332 | + final Map<Fleet, Iterable<? extends RaceColumn>> result = new HashMap<>(); |
|
| 333 | + result.put(FlexibleLeaderboardImpl.defaultFleet, getRaceColumns()); |
|
| 334 | + return result; |
|
| 335 | + } |
|
| 336 | + } |
|
| 337 | + |
|
| 338 | + @Override |
|
| 339 | + protected LeaderboardType getLeaderboardType() { |
|
| 340 | + return LeaderboardType.FlexibleLeaderboard; |
|
| 341 | + } |
|
| 342 | +} |
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/RegattaLeaderboardImpl.java
| ... | ... | @@ -1,117 +1,123 @@ |
| 1 | -package com.sap.sailing.domain.leaderboard.impl;
|
|
| 2 | -
|
|
| 3 | -import java.util.ArrayList;
|
|
| 4 | -import java.util.List;
|
|
| 5 | -
|
|
| 6 | -import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
|
|
| 7 | -import com.sap.sailing.domain.base.Competitor;
|
|
| 8 | -import com.sap.sailing.domain.base.CourseArea;
|
|
| 9 | -import com.sap.sailing.domain.base.Fleet;
|
|
| 10 | -import com.sap.sailing.domain.base.RaceColumn;
|
|
| 11 | -import com.sap.sailing.domain.base.RaceColumnInSeries;
|
|
| 12 | -import com.sap.sailing.domain.base.RaceColumnListener;
|
|
| 13 | -import com.sap.sailing.domain.base.RaceDefinition;
|
|
| 14 | -import com.sap.sailing.domain.base.Regatta;
|
|
| 15 | -import com.sap.sailing.domain.base.Series;
|
|
| 16 | -import com.sap.sailing.domain.base.impl.RaceColumnInSeriesImpl;
|
|
| 17 | -import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
|
|
| 18 | -import com.sap.sailing.domain.leaderboard.ResultDiscardingRule;
|
|
| 19 | -import com.sap.sailing.domain.leaderboard.ScoringScheme;
|
|
| 20 | -import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
|
|
| 21 | -import com.sap.sailing.domain.regattalike.IsRegattaLike;
|
|
| 22 | -import com.sap.sailing.domain.tracking.TrackedRace;
|
|
| 23 | -
|
|
| 24 | -/**
|
|
| 25 | - * A leaderboard that is based on the definition of a {@link Regatta} with its {@link Series} and {@link Fleet}. The regatta
|
|
| 26 | - * leaderboard listens to its {@link Regatta} as a {@link RaceColumnListener} and forwards all link/unlink events for
|
|
| 27 | - * {@link TrackedRace}s being linked to / unlinked from race columns to all {@link RaceColumnListener}s subscribed with
|
|
| 28 | - * this leaderboard.
|
|
| 29 | - *
|
|
| 30 | - * @author Axel Uhl (D043530)
|
|
| 31 | - *
|
|
| 32 | - */
|
|
| 33 | -public class RegattaLeaderboardImpl extends AbstractLeaderboardImpl implements RegattaLeaderboard {
|
|
| 34 | - private static final long serialVersionUID = 2370461218294770084L;
|
|
| 35 | - private final Regatta regatta;
|
|
| 36 | -
|
|
| 37 | - public RegattaLeaderboardImpl(Regatta regatta, ThresholdBasedResultDiscardingRule resultDiscardingRule) {
|
|
| 38 | - super(resultDiscardingRule);
|
|
| 39 | - this.regatta = regatta;
|
|
| 40 | - regatta.addRaceColumnListener(this);
|
|
| 41 | - }
|
|
| 42 | -
|
|
| 43 | - /**
|
|
| 44 | - * Updates the display name of this regatta leaderboard so that the regatta's name is no longer used as the default name.
|
|
| 45 | - */
|
|
| 46 | - @Override
|
|
| 47 | - public void setName(String newName) {
|
|
| 48 | - setDisplayName(newName);
|
|
| 49 | - }
|
|
| 50 | -
|
|
| 51 | - @Override
|
|
| 52 | - public Regatta getRegatta() {
|
|
| 53 | - return regatta;
|
|
| 54 | - }
|
|
| 55 | -
|
|
| 56 | - @Override
|
|
| 57 | - public String getName() {
|
|
| 58 | - return getRegatta().getName();
|
|
| 59 | - }
|
|
| 60 | -
|
|
| 61 | - @Override
|
|
| 62 | - public Iterable<RaceColumn> getRaceColumns() {
|
|
| 63 | - List<RaceColumn> result = new ArrayList<RaceColumn>();
|
|
| 64 | - for (Series series : getRegatta().getSeries()) {
|
|
| 65 | - for (RaceColumn raceColumn : series.getRaceColumns()) {
|
|
| 66 | - result.add(raceColumn);
|
|
| 67 | - }
|
|
| 68 | - }
|
|
| 69 | - return result;
|
|
| 70 | - }
|
|
| 71 | -
|
|
| 72 | - @Override
|
|
| 73 | - public RaceColumnInSeries getRaceColumnByName(String columnName) {
|
|
| 74 | - return (RaceColumnInSeriesImpl) super.getRaceColumnByName(columnName);
|
|
| 75 | - }
|
|
| 76 | -
|
|
| 77 | - @Override
|
|
| 78 | - public ScoringScheme getScoringScheme() {
|
|
| 79 | - return regatta.getScoringScheme();
|
|
| 80 | - }
|
|
| 81 | -
|
|
| 82 | - @Override
|
|
| 83 | - public CourseArea getDefaultCourseArea() {
|
|
| 84 | - return regatta.getDefaultCourseArea();
|
|
| 85 | - }
|
|
| 86 | -
|
|
| 87 | - /**
|
|
| 88 | - * If the regatta' series {@link Regatta#definesSeriesDiscardThresholds() define} their own result discarding rules, this leaderboard uses
|
|
| 89 | - * a composite result discarding rule of type {@link PerSeriesResultDiscardingRuleImpl} that contains discards within each series according
|
|
| 90 | - * to the series' discarding rules. Otherwise, the default cross-leaderboard result discarding rule is obtained from the super class
|
|
| 91 | - * implementation.
|
|
| 92 | - */
|
|
| 93 | - @Override
|
|
| 94 | - public ResultDiscardingRule getResultDiscardingRule() {
|
|
| 95 | - if (regatta.definesSeriesDiscardThresholds()) {
|
|
| 96 | - return new PerSeriesResultDiscardingRuleImpl(regatta);
|
|
| 97 | - } else {
|
|
| 98 | - return super.getResultDiscardingRule();
|
|
| 99 | - }
|
|
| 100 | - }
|
|
| 101 | -
|
|
| 102 | - /**
|
|
| 103 | - * Delegates to {@link Regatta#getAllCompetitors()} which is expected to deliver all competitors from this
|
|
| 104 | - * leaderboard's regatta which includes those belonging to {@link RaceDefinition races}
|
|
| 105 | - * {@link Regatta#getAllRaces() belonging to the regatta} as well as competitors listed on the regatta's
|
|
| 106 | - * {@link RegattaLog}.
|
|
| 107 | - */
|
|
| 108 | - @Override
|
|
| 109 | - public Iterable<Competitor> getAllCompetitors() {
|
|
| 110 | - return regatta.getAllCompetitors();
|
|
| 111 | - }
|
|
| 112 | -
|
|
| 113 | - @Override
|
|
| 114 | - public IsRegattaLike getRegattaLike() {
|
|
| 115 | - return regatta;
|
|
| 116 | - }
|
|
| 117 | -}
|
|
| 1 | +package com.sap.sailing.domain.leaderboard.impl; |
|
| 2 | + |
|
| 3 | +import java.util.ArrayList; |
|
| 4 | +import java.util.List; |
|
| 5 | + |
|
| 6 | +import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; |
|
| 7 | +import com.sap.sailing.domain.base.Competitor; |
|
| 8 | +import com.sap.sailing.domain.base.CourseArea; |
|
| 9 | +import com.sap.sailing.domain.base.Fleet; |
|
| 10 | +import com.sap.sailing.domain.base.RaceColumn; |
|
| 11 | +import com.sap.sailing.domain.base.RaceColumnInSeries; |
|
| 12 | +import com.sap.sailing.domain.base.RaceColumnListener; |
|
| 13 | +import com.sap.sailing.domain.base.RaceDefinition; |
|
| 14 | +import com.sap.sailing.domain.base.Regatta; |
|
| 15 | +import com.sap.sailing.domain.base.Series; |
|
| 16 | +import com.sap.sailing.domain.base.impl.RaceColumnInSeriesImpl; |
|
| 17 | +import com.sap.sailing.domain.common.LeaderboardType; |
|
| 18 | +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; |
|
| 19 | +import com.sap.sailing.domain.leaderboard.ResultDiscardingRule; |
|
| 20 | +import com.sap.sailing.domain.leaderboard.ScoringScheme; |
|
| 21 | +import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; |
|
| 22 | +import com.sap.sailing.domain.regattalike.IsRegattaLike; |
|
| 23 | +import com.sap.sailing.domain.tracking.TrackedRace; |
|
| 24 | + |
|
| 25 | +/** |
|
| 26 | + * A leaderboard that is based on the definition of a {@link Regatta} with its {@link Series} and {@link Fleet}. The regatta |
|
| 27 | + * leaderboard listens to its {@link Regatta} as a {@link RaceColumnListener} and forwards all link/unlink events for |
|
| 28 | + * {@link TrackedRace}s being linked to / unlinked from race columns to all {@link RaceColumnListener}s subscribed with |
|
| 29 | + * this leaderboard. |
|
| 30 | + * |
|
| 31 | + * @author Axel Uhl (D043530) |
|
| 32 | + * |
|
| 33 | + */ |
|
| 34 | +public class RegattaLeaderboardImpl extends AbstractLeaderboardImpl implements RegattaLeaderboard { |
|
| 35 | + private static final long serialVersionUID = 2370461218294770084L; |
|
| 36 | + private final Regatta regatta; |
|
| 37 | + |
|
| 38 | + public RegattaLeaderboardImpl(Regatta regatta, ThresholdBasedResultDiscardingRule resultDiscardingRule) { |
|
| 39 | + super(resultDiscardingRule); |
|
| 40 | + this.regatta = regatta; |
|
| 41 | + regatta.addRaceColumnListener(this); |
|
| 42 | + } |
|
| 43 | + |
|
| 44 | + /** |
|
| 45 | + * Updates the display name of this regatta leaderboard so that the regatta's name is no longer used as the default name. |
|
| 46 | + */ |
|
| 47 | + @Override |
|
| 48 | + public void setName(String newName) { |
|
| 49 | + setDisplayName(newName); |
|
| 50 | + } |
|
| 51 | + |
|
| 52 | + @Override |
|
| 53 | + public Regatta getRegatta() { |
|
| 54 | + return regatta; |
|
| 55 | + } |
|
| 56 | + |
|
| 57 | + @Override |
|
| 58 | + public String getName() { |
|
| 59 | + return getRegatta().getName(); |
|
| 60 | + } |
|
| 61 | + |
|
| 62 | + @Override |
|
| 63 | + public Iterable<RaceColumn> getRaceColumns() { |
|
| 64 | + List<RaceColumn> result = new ArrayList<RaceColumn>(); |
|
| 65 | + for (Series series : getRegatta().getSeries()) { |
|
| 66 | + for (RaceColumn raceColumn : series.getRaceColumns()) { |
|
| 67 | + result.add(raceColumn); |
|
| 68 | + } |
|
| 69 | + } |
|
| 70 | + return result; |
|
| 71 | + } |
|
| 72 | + |
|
| 73 | + @Override |
|
| 74 | + public RaceColumnInSeries getRaceColumnByName(String columnName) { |
|
| 75 | + return (RaceColumnInSeriesImpl) super.getRaceColumnByName(columnName); |
|
| 76 | + } |
|
| 77 | + |
|
| 78 | + @Override |
|
| 79 | + public ScoringScheme getScoringScheme() { |
|
| 80 | + return regatta.getScoringScheme(); |
|
| 81 | + } |
|
| 82 | + |
|
| 83 | + @Override |
|
| 84 | + public CourseArea getDefaultCourseArea() { |
|
| 85 | + return regatta.getDefaultCourseArea(); |
|
| 86 | + } |
|
| 87 | + |
|
| 88 | + /** |
|
| 89 | + * If the regatta' series {@link Regatta#definesSeriesDiscardThresholds() define} their own result discarding rules, this leaderboard uses |
|
| 90 | + * a composite result discarding rule of type {@link PerSeriesResultDiscardingRuleImpl} that contains discards within each series according |
|
| 91 | + * to the series' discarding rules. Otherwise, the default cross-leaderboard result discarding rule is obtained from the super class |
|
| 92 | + * implementation. |
|
| 93 | + */ |
|
| 94 | + @Override |
|
| 95 | + public ResultDiscardingRule getResultDiscardingRule() { |
|
| 96 | + if (regatta.definesSeriesDiscardThresholds()) { |
|
| 97 | + return new PerSeriesResultDiscardingRuleImpl(regatta); |
|
| 98 | + } else { |
|
| 99 | + return super.getResultDiscardingRule(); |
|
| 100 | + } |
|
| 101 | + } |
|
| 102 | + |
|
| 103 | + /** |
|
| 104 | + * Delegates to {@link Regatta#getAllCompetitors()} which is expected to deliver all competitors from this |
|
| 105 | + * leaderboard's regatta which includes those belonging to {@link RaceDefinition races} |
|
| 106 | + * {@link Regatta#getAllRaces() belonging to the regatta} as well as competitors listed on the regatta's |
|
| 107 | + * {@link RegattaLog}. |
|
| 108 | + */ |
|
| 109 | + @Override |
|
| 110 | + public Iterable<Competitor> getAllCompetitors() { |
|
| 111 | + return regatta.getAllCompetitors(); |
|
| 112 | + } |
|
| 113 | + |
|
| 114 | + @Override |
|
| 115 | + public IsRegattaLike getRegattaLike() { |
|
| 116 | + return regatta; |
|
| 117 | + } |
|
| 118 | + |
|
| 119 | + @Override |
|
| 120 | + protected LeaderboardType getLeaderboardType() { |
|
| 121 | + return LeaderboardType.RegattaLeaderboard; |
|
| 122 | + } |
|
| 123 | +} |
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/FlexibleMetaLeaderboard.java
| ... | ... | @@ -1,64 +1,70 @@ |
| 1 | -package com.sap.sailing.domain.leaderboard.meta;
|
|
| 2 | -
|
|
| 3 | -import java.util.ArrayList;
|
|
| 4 | -import java.util.List;
|
|
| 5 | -
|
|
| 6 | -import com.sap.sailing.domain.base.CourseArea;
|
|
| 7 | -import com.sap.sailing.domain.leaderboard.Leaderboard;
|
|
| 8 | -import com.sap.sailing.domain.leaderboard.ScoringScheme;
|
|
| 9 | -import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
|
|
| 10 | -import com.sap.sse.concurrent.LockUtil;
|
|
| 11 | -import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
|
|
| 12 | -
|
|
| 13 | -public class FlexibleMetaLeaderboard extends AbstractMetaLeaderboard {
|
|
| 14 | - private static final long serialVersionUID = 789076326144062944L;
|
|
| 15 | -
|
|
| 16 | - private final List<Leaderboard> leaderboards;
|
|
| 17 | -
|
|
| 18 | - private final NamedReentrantReadWriteLock leaderboardsLock;
|
|
| 19 | -
|
|
| 20 | - public FlexibleMetaLeaderboard(String name, ScoringScheme scoringScheme,
|
|
| 21 | - ThresholdBasedResultDiscardingRule resultDiscardingRule) {
|
|
| 22 | - super(name, scoringScheme, resultDiscardingRule);
|
|
| 23 | - leaderboards = new ArrayList<Leaderboard>();
|
|
| 24 | - leaderboardsLock = new NamedReentrantReadWriteLock("leaderboards collection of "+FlexibleMetaLeaderboard.class.getSimpleName()+" "+getName(),
|
|
| 25 | - /* fair */ false);
|
|
| 26 | - }
|
|
| 27 | -
|
|
| 28 | - @Override
|
|
| 29 | - public Iterable<Leaderboard> getLeaderboards() {
|
|
| 30 | - LockUtil.lockForRead(leaderboardsLock);
|
|
| 31 | - try {
|
|
| 32 | - return new ArrayList<Leaderboard>(leaderboards);
|
|
| 33 | - } finally {
|
|
| 34 | - LockUtil.unlockAfterRead(leaderboardsLock);
|
|
| 35 | - }
|
|
| 36 | - }
|
|
| 37 | -
|
|
| 38 | - public void addLeaderboard(Leaderboard leaderboard) {
|
|
| 39 | - LockUtil.lockForWrite(leaderboardsLock);
|
|
| 40 | - try {
|
|
| 41 | - leaderboards.add(leaderboard);
|
|
| 42 | - registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard);
|
|
| 43 | - getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(getColumnForLeaderboard(leaderboard));
|
|
| 44 | - } finally {
|
|
| 45 | - LockUtil.unlockAfterWrite(leaderboardsLock);
|
|
| 46 | - }
|
|
| 47 | - }
|
|
| 48 | -
|
|
| 49 | - public void removeLeaderboard(Leaderboard leaderboard) {
|
|
| 50 | - LockUtil.lockForWrite(leaderboardsLock);
|
|
| 51 | - try {
|
|
| 52 | - leaderboards.remove(leaderboard);
|
|
| 53 | - getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(getColumnForLeaderboard(leaderboard));
|
|
| 54 | - unregisterScoreCorrectionChangeForwarder(leaderboard);
|
|
| 55 | - } finally {
|
|
| 56 | - LockUtil.unlockAfterWrite(leaderboardsLock);
|
|
| 57 | - }
|
|
| 58 | - }
|
|
| 59 | -
|
|
| 60 | - @Override
|
|
| 61 | - public CourseArea getDefaultCourseArea() {
|
|
| 62 | - return null;
|
|
| 63 | - }
|
|
| 64 | -}
|
|
| 1 | +package com.sap.sailing.domain.leaderboard.meta; |
|
| 2 | + |
|
| 3 | +import java.util.ArrayList; |
|
| 4 | +import java.util.List; |
|
| 5 | + |
|
| 6 | +import com.sap.sailing.domain.base.CourseArea; |
|
| 7 | +import com.sap.sailing.domain.common.LeaderboardType; |
|
| 8 | +import com.sap.sailing.domain.leaderboard.Leaderboard; |
|
| 9 | +import com.sap.sailing.domain.leaderboard.ScoringScheme; |
|
| 10 | +import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; |
|
| 11 | +import com.sap.sse.concurrent.LockUtil; |
|
| 12 | +import com.sap.sse.concurrent.NamedReentrantReadWriteLock; |
|
| 13 | + |
|
| 14 | +public class FlexibleMetaLeaderboard extends AbstractMetaLeaderboard { |
|
| 15 | + private static final long serialVersionUID = 789076326144062944L; |
|
| 16 | + |
|
| 17 | + private final List<Leaderboard> leaderboards; |
|
| 18 | + |
|
| 19 | + private final NamedReentrantReadWriteLock leaderboardsLock; |
|
| 20 | + |
|
| 21 | + public FlexibleMetaLeaderboard(String name, ScoringScheme scoringScheme, |
|
| 22 | + ThresholdBasedResultDiscardingRule resultDiscardingRule) { |
|
| 23 | + super(name, scoringScheme, resultDiscardingRule); |
|
| 24 | + leaderboards = new ArrayList<Leaderboard>(); |
|
| 25 | + leaderboardsLock = new NamedReentrantReadWriteLock("leaderboards collection of "+FlexibleMetaLeaderboard.class.getSimpleName()+" "+getName(), |
|
| 26 | + /* fair */ false); |
|
| 27 | + } |
|
| 28 | + |
|
| 29 | + @Override |
|
| 30 | + public Iterable<Leaderboard> getLeaderboards() { |
|
| 31 | + LockUtil.lockForRead(leaderboardsLock); |
|
| 32 | + try { |
|
| 33 | + return new ArrayList<Leaderboard>(leaderboards); |
|
| 34 | + } finally { |
|
| 35 | + LockUtil.unlockAfterRead(leaderboardsLock); |
|
| 36 | + } |
|
| 37 | + } |
|
| 38 | + |
|
| 39 | + public void addLeaderboard(Leaderboard leaderboard) { |
|
| 40 | + LockUtil.lockForWrite(leaderboardsLock); |
|
| 41 | + try { |
|
| 42 | + leaderboards.add(leaderboard); |
|
| 43 | + registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard); |
|
| 44 | + getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(getColumnForLeaderboard(leaderboard)); |
|
| 45 | + } finally { |
|
| 46 | + LockUtil.unlockAfterWrite(leaderboardsLock); |
|
| 47 | + } |
|
| 48 | + } |
|
| 49 | + |
|
| 50 | + public void removeLeaderboard(Leaderboard leaderboard) { |
|
| 51 | + LockUtil.lockForWrite(leaderboardsLock); |
|
| 52 | + try { |
|
| 53 | + leaderboards.remove(leaderboard); |
|
| 54 | + getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(getColumnForLeaderboard(leaderboard)); |
|
| 55 | + unregisterScoreCorrectionChangeForwarder(leaderboard); |
|
| 56 | + } finally { |
|
| 57 | + LockUtil.unlockAfterWrite(leaderboardsLock); |
|
| 58 | + } |
|
| 59 | + } |
|
| 60 | + |
|
| 61 | + @Override |
|
| 62 | + public CourseArea getDefaultCourseArea() { |
|
| 63 | + return null; |
|
| 64 | + } |
|
| 65 | + |
|
| 66 | + @Override |
|
| 67 | + protected LeaderboardType getLeaderboardType() { |
|
| 68 | + return LeaderboardType.FlexibleMetaLeaderboard; |
|
| 69 | + } |
|
| 70 | +} |
java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/LeaderboardGroupMetaLeaderboard.java
| ... | ... | @@ -1,92 +1,98 @@ |
| 1 | -package com.sap.sailing.domain.leaderboard.meta;
|
|
| 2 | -
|
|
| 3 | -import com.sap.sailing.domain.base.CourseArea;
|
|
| 4 | -import com.sap.sailing.domain.base.Fleet;
|
|
| 5 | -import com.sap.sailing.domain.base.RaceColumn;
|
|
| 6 | -import com.sap.sailing.domain.base.RaceColumnListener;
|
|
| 7 | -import com.sap.sailing.domain.base.impl.TrackedRaces;
|
|
| 8 | -import com.sap.sailing.domain.common.LeaderboardNameConstants;
|
|
| 9 | -import com.sap.sailing.domain.leaderboard.Leaderboard;
|
|
| 10 | -import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
|
|
| 11 | -import com.sap.sailing.domain.leaderboard.LeaderboardGroupListener;
|
|
| 12 | -import com.sap.sailing.domain.leaderboard.ScoringScheme;
|
|
| 13 | -import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
|
|
| 14 | -import com.sap.sailing.domain.tracking.TrackedRace;
|
|
| 15 | -
|
|
| 16 | -/**
|
|
| 17 | - * A meta leaderboard which considers all regular leaderboards of a {@link LeaderboardGroup}. To stay up to date, this
|
|
| 18 | - * meta leaderboard registers itself as a {@link LeaderboardGroupListener} on the leaderboard group it represents. This
|
|
| 19 | - * way, whenever a leaderboard is added to or removed from the group, this meta leaderboard can in turn inform its
|
|
| 20 | - * {@link RaceColumnListener}s about the impact this change has on the set of {@link TrackedRaces} and therefore the
|
|
| 21 | - * competitors attached to this meta leaderboard.
|
|
| 22 | - * <p>
|
|
| 23 | - *
|
|
| 24 | - * After an object of this type has been de-serialized, and after all objects referenced by it (leaderboard, leaderboard
|
|
| 25 | - * group) have been initialized, {@link #registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards()} must be called to
|
|
| 26 | - * ensure that score corrections are propagated as desired. This is because score correction listeners are "transient"
|
|
| 27 | - * and are as such not serialized. Note: calling {@link #registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards()}
|
|
| 28 | - * during <code>readObject</code> or <code>readResolve</code> is not possible because the object graph hasn't been fully
|
|
| 29 | - * initialized yet when those methods are called. Hence, the structures for registering the score correction listeners
|
|
| 30 | - * are not yet in place at that time.
|
|
| 31 | - *
|
|
| 32 | - * @author Axel Uhl (d043530)
|
|
| 33 | - *
|
|
| 34 | - */
|
|
| 35 | -public class LeaderboardGroupMetaLeaderboard extends AbstractMetaLeaderboard implements LeaderboardGroupListener, RaceColumnListener {
|
|
| 36 | - private static final long serialVersionUID = 8087872002175528002L;
|
|
| 37 | -
|
|
| 38 | - private final LeaderboardGroup leaderboardGroup;
|
|
| 39 | -
|
|
| 40 | - public LeaderboardGroupMetaLeaderboard(LeaderboardGroup leaderboardGroup, ScoringScheme scoringScheme,
|
|
| 41 | - ThresholdBasedResultDiscardingRule resultDiscardingRule) {
|
|
| 42 | - super(leaderboardGroup.getName() + " " + LeaderboardNameConstants.OVERALL, scoringScheme, resultDiscardingRule);
|
|
| 43 | - this.leaderboardGroup = leaderboardGroup;
|
|
| 44 | - leaderboardGroup.addLeaderboardGroupListener(this);
|
|
| 45 | - registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards();
|
|
| 46 | - }
|
|
| 47 | -
|
|
| 48 | - public void registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards() {
|
|
| 49 | - for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
|
|
| 50 | - registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard);
|
|
| 51 | - }
|
|
| 52 | - }
|
|
| 53 | -
|
|
| 54 | - @Override
|
|
| 55 | - public Iterable<Leaderboard> getLeaderboards() {
|
|
| 56 | - return leaderboardGroup.getLeaderboards();
|
|
| 57 | - }
|
|
| 58 | -
|
|
| 59 | - @Override
|
|
| 60 | - public void leaderboardAdded(LeaderboardGroup group, Leaderboard leaderboard) {
|
|
| 61 | - registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard);
|
|
| 62 | - for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
|
|
| 63 | - for (Fleet fleet : raceColumn.getFleets()) {
|
|
| 64 | - TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
|
|
| 65 | - if (trackedRace != null) {
|
|
| 66 | - getRaceColumnListeners().notifyListenersAboutTrackedRaceLinked(raceColumn, fleet, trackedRace);
|
|
| 67 | - }
|
|
| 68 | - }
|
|
| 69 | - }
|
|
| 70 | - getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(getColumnForLeaderboard(leaderboard));
|
|
| 71 | - }
|
|
| 72 | -
|
|
| 73 | - @Override
|
|
| 74 | - public void leaderboardRemoved(LeaderboardGroup group, Leaderboard leaderboard) {
|
|
| 75 | - leaderboard.removeRaceColumnListener(this);
|
|
| 76 | - unregisterScoreCorrectionChangeForwarder(leaderboard);
|
|
| 77 | - for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
|
|
| 78 | - for (Fleet fleet : raceColumn.getFleets()) {
|
|
| 79 | - TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
|
|
| 80 | - if (trackedRace != null) {
|
|
| 81 | - getRaceColumnListeners().notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace);
|
|
| 82 | - }
|
|
| 83 | - }
|
|
| 84 | - }
|
|
| 85 | - getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(getColumnForLeaderboard(leaderboard));
|
|
| 86 | - }
|
|
| 87 | -
|
|
| 88 | - @Override
|
|
| 89 | - public CourseArea getDefaultCourseArea() {
|
|
| 90 | - return null;
|
|
| 91 | - }
|
|
| 92 | -}
|
|
| 1 | +package com.sap.sailing.domain.leaderboard.meta; |
|
| 2 | + |
|
| 3 | +import com.sap.sailing.domain.base.CourseArea; |
|
| 4 | +import com.sap.sailing.domain.base.Fleet; |
|
| 5 | +import com.sap.sailing.domain.base.RaceColumn; |
|
| 6 | +import com.sap.sailing.domain.base.RaceColumnListener; |
|
| 7 | +import com.sap.sailing.domain.base.impl.TrackedRaces; |
|
| 8 | +import com.sap.sailing.domain.common.LeaderboardNameConstants; |
|
| 9 | +import com.sap.sailing.domain.common.LeaderboardType; |
|
| 10 | +import com.sap.sailing.domain.leaderboard.Leaderboard; |
|
| 11 | +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; |
|
| 12 | +import com.sap.sailing.domain.leaderboard.LeaderboardGroupListener; |
|
| 13 | +import com.sap.sailing.domain.leaderboard.ScoringScheme; |
|
| 14 | +import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; |
|
| 15 | +import com.sap.sailing.domain.tracking.TrackedRace; |
|
| 16 | + |
|
| 17 | +/** |
|
| 18 | + * A meta leaderboard which considers all regular leaderboards of a {@link LeaderboardGroup}. To stay up to date, this |
|
| 19 | + * meta leaderboard registers itself as a {@link LeaderboardGroupListener} on the leaderboard group it represents. This |
|
| 20 | + * way, whenever a leaderboard is added to or removed from the group, this meta leaderboard can in turn inform its |
|
| 21 | + * {@link RaceColumnListener}s about the impact this change has on the set of {@link TrackedRaces} and therefore the |
|
| 22 | + * competitors attached to this meta leaderboard. |
|
| 23 | + * <p> |
|
| 24 | + * |
|
| 25 | + * After an object of this type has been de-serialized, and after all objects referenced by it (leaderboard, leaderboard |
|
| 26 | + * group) have been initialized, {@link #registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards()} must be called to |
|
| 27 | + * ensure that score corrections are propagated as desired. This is because score correction listeners are "transient" |
|
| 28 | + * and are as such not serialized. Note: calling {@link #registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards()} |
|
| 29 | + * during <code>readObject</code> or <code>readResolve</code> is not possible because the object graph hasn't been fully |
|
| 30 | + * initialized yet when those methods are called. Hence, the structures for registering the score correction listeners |
|
| 31 | + * are not yet in place at that time. |
|
| 32 | + * |
|
| 33 | + * @author Axel Uhl (d043530) |
|
| 34 | + * |
|
| 35 | + */ |
|
| 36 | +public class LeaderboardGroupMetaLeaderboard extends AbstractMetaLeaderboard implements LeaderboardGroupListener, RaceColumnListener { |
|
| 37 | + private static final long serialVersionUID = 8087872002175528002L; |
|
| 38 | + |
|
| 39 | + private final LeaderboardGroup leaderboardGroup; |
|
| 40 | + |
|
| 41 | + public LeaderboardGroupMetaLeaderboard(LeaderboardGroup leaderboardGroup, ScoringScheme scoringScheme, |
|
| 42 | + ThresholdBasedResultDiscardingRule resultDiscardingRule) { |
|
| 43 | + super(leaderboardGroup.getName() + " " + LeaderboardNameConstants.OVERALL, scoringScheme, resultDiscardingRule); |
|
| 44 | + this.leaderboardGroup = leaderboardGroup; |
|
| 45 | + leaderboardGroup.addLeaderboardGroupListener(this); |
|
| 46 | + registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards(); |
|
| 47 | + } |
|
| 48 | + |
|
| 49 | + public void registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards() { |
|
| 50 | + for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) { |
|
| 51 | + registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard); |
|
| 52 | + } |
|
| 53 | + } |
|
| 54 | + |
|
| 55 | + @Override |
|
| 56 | + public Iterable<Leaderboard> getLeaderboards() { |
|
| 57 | + return leaderboardGroup.getLeaderboards(); |
|
| 58 | + } |
|
| 59 | + |
|
| 60 | + @Override |
|
| 61 | + public void leaderboardAdded(LeaderboardGroup group, Leaderboard leaderboard) { |
|
| 62 | + registerScoreCorrectionAndRaceColumnChangeForwarder(leaderboard); |
|
| 63 | + for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { |
|
| 64 | + for (Fleet fleet : raceColumn.getFleets()) { |
|
| 65 | + TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); |
|
| 66 | + if (trackedRace != null) { |
|
| 67 | + getRaceColumnListeners().notifyListenersAboutTrackedRaceLinked(raceColumn, fleet, trackedRace); |
|
| 68 | + } |
|
| 69 | + } |
|
| 70 | + } |
|
| 71 | + getRaceColumnListeners().notifyListenersAboutRaceColumnAddedToContainer(getColumnForLeaderboard(leaderboard)); |
|
| 72 | + } |
|
| 73 | + |
|
| 74 | + @Override |
|
| 75 | + public void leaderboardRemoved(LeaderboardGroup group, Leaderboard leaderboard) { |
|
| 76 | + leaderboard.removeRaceColumnListener(this); |
|
| 77 | + unregisterScoreCorrectionChangeForwarder(leaderboard); |
|
| 78 | + for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { |
|
| 79 | + for (Fleet fleet : raceColumn.getFleets()) { |
|
| 80 | + TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); |
|
| 81 | + if (trackedRace != null) { |
|
| 82 | + getRaceColumnListeners().notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace); |
|
| 83 | + } |
|
| 84 | + } |
|
| 85 | + } |
|
| 86 | + getRaceColumnListeners().notifyListenersAboutRaceColumnRemovedFromContainer(getColumnForLeaderboard(leaderboard)); |
|
| 87 | + } |
|
| 88 | + |
|
| 89 | + @Override |
|
| 90 | + public CourseArea getDefaultCourseArea() { |
|
| 91 | + return null; |
|
| 92 | + } |
|
| 93 | + |
|
| 94 | + @Override |
|
| 95 | + protected LeaderboardType getLeaderboardType() { |
|
| 96 | + return LeaderboardType.RegattaMetaLeaderboard; |
|
| 97 | + } |
|
| 98 | +} |
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java
| ... | ... | @@ -13,7 +13,6 @@ import java.util.ConcurrentModificationException; |
| 13 | 13 | import java.util.Date; |
| 14 | 14 | import java.util.HashMap; |
| 15 | 15 | import java.util.HashSet; |
| 16 | -import java.util.IdentityHashMap; |
|
| 17 | 16 | import java.util.Iterator; |
| 18 | 17 | import java.util.LinkedHashMap; |
| 19 | 18 | import java.util.LinkedList; |
| ... | ... | @@ -154,6 +153,7 @@ import com.sap.sse.common.Util.Pair; |
| 154 | 153 | import com.sap.sse.common.impl.MillisecondsTimePoint; |
| 155 | 154 | import com.sap.sse.concurrent.LockUtil; |
| 156 | 155 | import com.sap.sse.concurrent.NamedReentrantReadWriteLock; |
| 156 | +import com.sap.sse.util.IdentityWrapper; |
|
| 157 | 157 | import com.sap.sse.util.SmartFutureCache; |
| 158 | 158 | import com.sap.sse.util.SmartFutureCache.AbstractCacheUpdater; |
| 159 | 159 | import com.sap.sse.util.SmartFutureCache.EmptyUpdateInterval; |
| ... | ... | @@ -183,7 +183,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl |
| 183 | 183 | * By default, all wind sources are used, none are excluded. However, e.g., for performance reasons, particular wind |
| 184 | 184 | * sources such as the track-based estimation wind source, may be excluded by adding them to this set. |
| 185 | 185 | */ |
| 186 | - private final Set<WindSource> windSourcesToExclude; |
|
| 186 | + private final ConcurrentHashMap<WindSource, TrackedRaceImpl> windSourcesToExclude; |
|
| 187 | 187 | |
| 188 | 188 | /** |
| 189 | 189 | * Keeps the oldest timestamp that is fed into this tracked race, either from a boat fix, a mark fix, a race |
| ... | ... | @@ -269,7 +269,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl |
| 269 | 269 | */ |
| 270 | 270 | private transient SmartFutureCache<Competitor, com.sap.sse.common.Util.Triple<TimePoint, TimePoint, List<Maneuver>>, EmptyUpdateInterval> maneuverCache; |
| 271 | 271 | |
| 272 | - private transient Map<TimePoint, Future<Wind>> directionFromStartToNextMarkCache; |
|
| 272 | + private transient ConcurrentHashMap<TimePoint, Future<Wind>> directionFromStartToNextMarkCache; |
|
| 273 | 273 | |
| 274 | 274 | protected final MarkPassingCalculator markPassingCalculator; |
| 275 | 275 | |
| ... | ... | @@ -354,7 +354,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl |
| 354 | 354 | */ |
| 355 | 355 | private final NamedReentrantReadWriteLock loadingFromGPSFixStoreLock; |
| 356 | 356 | |
| 357 | - private final Map<Iterable<MarkPassing>, NamedReentrantReadWriteLock> locksForMarkPassings; |
|
| 357 | + private final ConcurrentHashMap<IdentityWrapper<Iterable<MarkPassing>>, NamedReentrantReadWriteLock> locksForMarkPassings; |
|
| 358 | 358 | |
| 359 | 359 | /** |
| 360 | 360 | * Caches wind requests for a few seconds to accelerate access in live mode |
| ... | ... | @@ -412,7 +412,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl |
| 412 | 412 | rankingMetric = rankingMetricConstructor.apply(this); |
| 413 | 413 | raceStates = new WeakHashMap<>(); |
| 414 | 414 | shortTimeWindCache = new ShortTimeWindCache(this, millisecondsOverWhichToAverageWind / 2); |
| 415 | - locksForMarkPassings = new IdentityHashMap<>(); |
|
| 415 | + locksForMarkPassings = new ConcurrentHashMap<IdentityWrapper<Iterable<MarkPassing>>, NamedReentrantReadWriteLock>(); |
|
| 416 | 416 | attachedRaceLogs = new ConcurrentHashMap<>(); |
| 417 | 417 | attachedRegattaLogs = new ConcurrentHashMap<>(); |
| 418 | 418 | attachedRaceExecutionOrderProviders = new ConcurrentHashMap<>(); |
| ... | ... | @@ -424,8 +424,8 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl |
| 424 | 424 | + race.getName(), /* fair */ false); |
| 425 | 425 | this.cacheInvalidationTimerLock = new Object(); |
| 426 | 426 | this.updateCount = 0; |
| 427 | - this.windSourcesToExclude = new HashSet<WindSource>(); |
|
| 428 | - this.directionFromStartToNextMarkCache = new HashMap<TimePoint, Future<Wind>>(); |
|
| 427 | + this.windSourcesToExclude = new ConcurrentHashMap<>(); |
|
| 428 | + this.directionFromStartToNextMarkCache = new ConcurrentHashMap<TimePoint, Future<Wind>>(); |
|
| 429 | 429 | this.millisecondsOverWhichToAverageSpeed = millisecondsOverWhichToAverageSpeed; |
| 430 | 430 | this.delayToLiveInMillis = delayToLiveInMillis; |
| 431 | 431 | this.startToNextMarkCacheInvalidationListeners = new ConcurrentHashMap<Mark, TrackedRaceImpl.StartToNextMarkCacheInvalidationListener>(); |
| ... | ... | @@ -624,7 +624,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl |
| 624 | 624 | gpsFixStore = EmptyGPSFixStore.INSTANCE; |
| 625 | 625 | competitorRankings = createCompetitorRankingsCache(); |
| 626 | 626 | competitorRankingsLocks = createCompetitorRankingsLockMap(); |
| 627 | - directionFromStartToNextMarkCache = new HashMap<TimePoint, Future<Wind>>(); |
|
| 627 | + directionFromStartToNextMarkCache = new ConcurrentHashMap<>(); |
|
| 628 | 628 | crossTrackErrorCache = new CrossTrackErrorCache(this); |
| 629 | 629 | crossTrackErrorCache.invalidate(); |
| 630 | 630 | maneuverCache = createManeuverCache(); |
| ... | ... | @@ -1146,11 +1146,11 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl |
| 1146 | 1146 | int indexOfWaypoint = getRace().getCourse().getIndexOfWaypoint(startOfLeg); |
| 1147 | 1147 | if (indexOfWaypoint == -1) { |
| 1148 | 1148 | throw new IllegalArgumentException("Waypoint " + startOfLeg + " not found in " + getRace().getCourse()); |
| 1149 | - } else if (indexOfWaypoint == Util.size(getRace().getCourse().getWaypoints()) - 1) { |
|
| 1149 | + } else if (indexOfWaypoint == getRace().getCourse().getNumberOfWaypoints() - 1) { |
|
| 1150 | 1150 | throw new IllegalArgumentException("Waypoint " + startOfLeg + " isn't start of any leg in " |
| 1151 | 1151 | + getRace().getCourse()); |
| 1152 | 1152 | } |
| 1153 | - return trackedLegs.get(race.getCourse().getLegs().get(indexOfWaypoint)); |
|
| 1153 | + return trackedLegs.get(race.getCourse().getLeg(indexOfWaypoint)); |
|
| 1154 | 1154 | } finally { |
| 1155 | 1155 | getRace().getCourse().unlockAfterRead(); |
| 1156 | 1156 | } |
| ... | ... | @@ -1679,24 +1679,20 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl |
| 1679 | 1679 | |
| 1680 | 1680 | @Override |
| 1681 | 1681 | public Set<WindSource> getWindSourcesToExclude() { |
| 1682 | - synchronized (windSourcesToExclude) { |
|
| 1683 | - return Collections.unmodifiableSet(windSourcesToExclude); |
|
| 1684 | - } |
|
| 1682 | + return Collections.unmodifiableSet(windSourcesToExclude.keySet()); |
|
| 1685 | 1683 | } |
| 1686 | 1684 | |
| 1687 | 1685 | @Override |
| 1688 | 1686 | public void setWindSourcesToExclude(Iterable<? extends WindSource> windSourcesToExclude) { |
| 1689 | - Set<WindSource> old = new HashSet<>(this.windSourcesToExclude); |
|
| 1690 | - synchronized (this.windSourcesToExclude) { |
|
| 1691 | - LockUtil.lockForRead(getSerializationLock()); |
|
| 1692 | - try { |
|
| 1693 | - this.windSourcesToExclude.clear(); |
|
| 1694 | - for (WindSource windSourceToExclude : windSourcesToExclude) { |
|
| 1695 | - this.windSourcesToExclude.add(windSourceToExclude); |
|
| 1696 | - } |
|
| 1697 | - } finally { |
|
| 1698 | - LockUtil.unlockAfterRead(getSerializationLock()); |
|
| 1687 | + Set<WindSource> old = new HashSet<>(getWindSourcesToExclude()); |
|
| 1688 | + LockUtil.lockForRead(getSerializationLock()); |
|
| 1689 | + try { |
|
| 1690 | + this.windSourcesToExclude.clear(); |
|
| 1691 | + for (WindSource windSourceToExclude : windSourcesToExclude) { |
|
| 1692 | + this.windSourcesToExclude.put(windSourceToExclude, this); |
|
| 1699 | 1693 | } |
| 1694 | + } finally { |
|
| 1695 | + LockUtil.unlockAfterRead(getSerializationLock()); |
|
| 1700 | 1696 | } |
| 1701 | 1697 | if (!old.equals(this.windSourcesToExclude)) { |
| 1702 | 1698 | clearAllCachesExceptManeuvers(); |
| ... | ... | @@ -1742,30 +1738,33 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl |
| 1742 | 1738 | public Wind getDirectionFromStartToNextMark(final TimePoint at) { |
| 1743 | 1739 | Future<Wind> future; |
| 1744 | 1740 | FutureTask<Wind> newFuture = null; |
| 1745 | - synchronized (directionFromStartToNextMarkCache) { |
|
| 1746 | - future = directionFromStartToNextMarkCache.get(at); |
|
| 1747 | - if (future == null) { |
|
| 1748 | - newFuture = new FutureTask<Wind>(new Callable<Wind>() { |
|
| 1749 | - @Override |
|
| 1750 | - public Wind call() { |
|
| 1751 | - Wind result; |
|
| 1752 | - Leg firstLeg = getRace().getCourse().getFirstLeg(); |
|
| 1753 | - if (firstLeg != null) { |
|
| 1754 | - Position firstLegEnd = getApproximatePosition(firstLeg.getTo(), at); |
|
| 1755 | - Position firstLegStart = getApproximatePosition(firstLeg.getFrom(), at); |
|
| 1756 | - if (firstLegStart != null && firstLegEnd != null) { |
|
| 1757 | - result = new WindImpl(firstLegStart, at, new KnotSpeedWithBearingImpl(0.0, |
|
| 1758 | - firstLegEnd.getBearingGreatCircle(firstLegStart))); |
|
| 1741 | + future = directionFromStartToNextMarkCache.get(at); |
|
| 1742 | + if (future == null) { |
|
| 1743 | + synchronized (directionFromStartToNextMarkCache) { |
|
| 1744 | + future = directionFromStartToNextMarkCache.get(at); |
|
| 1745 | + if (future == null) { |
|
| 1746 | + newFuture = new FutureTask<Wind>(new Callable<Wind>() { |
|
| 1747 | + @Override |
|
| 1748 | + public Wind call() { |
|
| 1749 | + Wind result; |
|
| 1750 | + Leg firstLeg = getRace().getCourse().getFirstLeg(); |
|
| 1751 | + if (firstLeg != null) { |
|
| 1752 | + Position firstLegEnd = getApproximatePosition(firstLeg.getTo(), at); |
|
| 1753 | + Position firstLegStart = getApproximatePosition(firstLeg.getFrom(), at); |
|
| 1754 | + if (firstLegStart != null && firstLegEnd != null) { |
|
| 1755 | + result = new WindImpl(firstLegStart, at, new KnotSpeedWithBearingImpl(0.0, |
|
| 1756 | + firstLegEnd.getBearingGreatCircle(firstLegStart))); |
|
| 1757 | + } else { |
|
| 1758 | + result = null; |
|
| 1759 | + } |
|
| 1759 | 1760 | } else { |
| 1760 | 1761 | result = null; |
| 1761 | 1762 | } |
| 1762 | - } else { |
|
| 1763 | - result = null; |
|
| 1763 | + return result; |
|
| 1764 | 1764 | } |
| 1765 | - return result; |
|
| 1766 | - } |
|
| 1767 | - }); |
|
| 1768 | - directionFromStartToNextMarkCache.put(at, newFuture); |
|
| 1765 | + }); |
|
| 1766 | + directionFromStartToNextMarkCache.put(at, newFuture); |
|
| 1767 | + } |
|
| 1769 | 1768 | } |
| 1770 | 1769 | } |
| 1771 | 1770 | if (newFuture != null) { |
| ... | ... | @@ -2062,15 +2061,19 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl |
| 2062 | 2061 | } |
| 2063 | 2062 | |
| 2064 | 2063 | protected NamedReentrantReadWriteLock getMarkPassingsLock(Iterable<MarkPassing> markPassings) { |
| 2065 | - synchronized (locksForMarkPassings) { |
|
| 2066 | - NamedReentrantReadWriteLock lock = locksForMarkPassings.get(markPassings); |
|
| 2067 | - if (lock == null) { |
|
| 2068 | - lock = new NamedReentrantReadWriteLock("mark passings lock for tracked race " + getRace().getName(), /* fair */ |
|
| 2069 | - false); |
|
| 2070 | - locksForMarkPassings.put(markPassings, lock); |
|
| 2064 | + final IdentityWrapper<Iterable<MarkPassing>> markPassingsIdentity = new IdentityWrapper<>(markPassings); |
|
| 2065 | + NamedReentrantReadWriteLock lock = locksForMarkPassings.get(markPassingsIdentity); |
|
| 2066 | + if (lock == null) { |
|
| 2067 | + synchronized (locksForMarkPassings) { |
|
| 2068 | + lock = locksForMarkPassings.get(markPassingsIdentity); |
|
| 2069 | + if (lock == null) { |
|
| 2070 | + lock = new NamedReentrantReadWriteLock( |
|
| 2071 | + "mark passings lock for tracked race " + getRace().getName(), /* fair */false); |
|
| 2072 | + locksForMarkPassings.put(markPassingsIdentity, lock); |
|
| 2073 | + } |
|
| 2071 | 2074 | } |
| 2072 | - return lock; |
|
| 2073 | 2075 | } |
| 2076 | + return lock; |
|
| 2074 | 2077 | } |
| 2075 | 2078 | |
| 2076 | 2079 | private void updateStartToNextMarkCacheInvalidationCacheListenersAfterWaypointRemoved(int zeroBasedIndex, |
java/com.sap.sailing.feature.p2build/raceanalysis.product
| ... | ... | @@ -50,6 +50,7 @@ |
| 50 | 50 | <plugin id="com.sap.sailing.monitoring" autoStart="true" startLevel="6" />
|
| 51 | 51 | <plugin id="com.sap.sailing.news" autoStart="true" startLevel="3" />
|
| 52 | 52 | <plugin id="com.sap.sailing.polars" autoStart="true" startLevel="4" />
|
| 53 | + <plugin id="com.sap.sailing.polars.datamining" autoStart="true" startLevel="4" />
|
|
| 53 | 54 | <plugin id="com.sap.sailing.resultimport" autoStart="true" startLevel="3" />
|
| 54 | 55 | <plugin id="com.sap.sailing.sailwave.resultimport" autoStart="true" startLevel="3" />
|
| 55 | 56 | <plugin id="com.sap.sailing.server" autoStart="true" startLevel="4" />
|
java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java
| ... | ... | @@ -95,7 +95,7 @@ public class GwtTestCaseColumnToggling extends GWTTestCase { |
| 95 | 95 | + TracTracConnectionConstants.PORT_TUNNEL_STORED : "tcp://" + TracTracConnectionConstants.HOST_NAME
|
| 96 | 96 | + ":" + TracTracConnectionConstants.PORT_STORED,
|
| 97 | 97 | COURSE_DESIGN_UPDATE_URI,
|
| 98 | - false, false, /* simulateWithStartTimeNow */ false, /* ignoreTracTracMarkPassings */ false, TRACTRAC_USERNAME, TRACTRAC_PASSWORD, new AsyncCallback<Void>() {
|
|
| 98 | + false, false, /* offsetToStartTimeOfSimulatedRace */ null, /* ignoreTracTracMarkPassings */ false, TRACTRAC_USERNAME, TRACTRAC_PASSWORD, new AsyncCallback<Void>() {
|
|
| 99 | 99 | |
| 100 | 100 | @Override
|
| 101 | 101 | public void onFailure(Throwable caught) {
|
java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/LeaderboardPanelMock.java
| ... | ... | @@ -20,7 +20,7 @@ public class LeaderboardPanelMock extends LeaderboardPanel { |
| 20 | 20 | String leaderboardName, ErrorReporter errorReporter,
|
| 21 | 21 | StringMessages stringMessages) {
|
| 22 | 22 | super(sailingService, new AsyncActionsExecutor(), LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(
|
| 23 | - /* racesToShow */null, /* namesOfRacesToShow */ null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true), new CompetitorSelectionModel(
|
|
| 23 | + /* racesToShow */null, /* namesOfRacesToShow */ null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true, /* showCompetitorSailIdColumn */ true, /* showCompetitorFullNameColumn */ true), new CompetitorSelectionModel(
|
|
| 24 | 24 | /* hasMultiSelection */true), leaderboardName, errorReporter, stringMessages, new UserAgentDetails("gecko1_8"), /* showRaceDetails */ true);
|
| 25 | 25 | }
|
| 26 | 26 |
java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/TestColumnSwapping.java
| ... | ... | @@ -1,162 +1,162 @@ |
| 1 | -package com.sap.sailing.gwt.ui.test;
|
|
| 2 | -
|
|
| 3 | -import static org.junit.Assert.assertArrayEquals;
|
|
| 4 | -import static org.junit.Assert.assertEquals;
|
|
| 5 | -import static org.junit.Assert.assertNotNull;
|
|
| 6 | -import static org.junit.Assert.assertTrue;
|
|
| 7 | -import static org.junit.Assert.fail;
|
|
| 8 | -
|
|
| 9 | -import java.util.ArrayList;
|
|
| 10 | -import java.util.Collection;
|
|
| 11 | -import java.util.Date;
|
|
| 12 | -import java.util.List;
|
|
| 13 | -import java.util.UUID;
|
|
| 14 | -
|
|
| 15 | -import org.junit.Before;
|
|
| 16 | -import org.junit.Test;
|
|
| 17 | -
|
|
| 18 | -import com.sap.sailing.domain.common.ScoringSchemeType;
|
|
| 19 | -import com.sap.sailing.domain.common.dto.FleetDTO;
|
|
| 20 | -import com.sap.sailing.domain.common.dto.LeaderboardDTO;
|
|
| 21 | -import com.sap.sailing.domain.common.dto.RaceColumnDTO;
|
|
| 22 | -import com.sap.sailing.gwt.ui.server.SailingServiceImpl;
|
|
| 23 | -
|
|
| 24 | -public class TestColumnSwapping {
|
|
| 25 | -
|
|
| 26 | - private LeaderboardDTO lb = null;
|
|
| 27 | - private static final String LEADERBOARDNAME = "test";
|
|
| 28 | - private static final String DEFAULT_FLEET_NAME = "Default";
|
|
| 29 | - private static final FleetDTO DEFAULT_FLEET = new FleetDTO(DEFAULT_FLEET_NAME, /* ordering */ 0, /* color */ null);
|
|
| 30 | - private SailingServiceImpl service;
|
|
| 31 | - private LeaderboardDTO leaderboardOriginalDTO;
|
|
| 32 | - private LeaderboardDTO leaderboardDTO;
|
|
| 33 | - private Collection<String> leglist;
|
|
| 34 | - private Date leaderboardCreationDate;
|
|
| 35 | - private SailingServiceImpl sailingService = null;
|
|
| 36 | -
|
|
| 37 | - // Test-Data
|
|
| 38 | - private final String TEST_LEADERBOARD_NAME = "test_board";
|
|
| 39 | - private final String[] races = { "1", "2", "3" };
|
|
| 40 | - private final boolean[] isMedalRace = { false, false, true };
|
|
| 41 | -
|
|
| 42 | - @Before
|
|
| 43 | - public void prepareColumnSwapping() {
|
|
| 44 | - service = new SailingServiceImplMock();
|
|
| 45 | - int[] disc = { 5, 8, 9, 0, 7, 5, 43 };
|
|
| 46 | - service.removeLeaderboard(LEADERBOARDNAME);
|
|
| 47 | - service.createFlexibleLeaderboard(LEADERBOARDNAME, null, disc, ScoringSchemeType.LOW_POINT, null);
|
|
| 48 | - service.addColumnToLeaderboard("Race1", LEADERBOARDNAME, true);
|
|
| 49 | - service.addColumnToLeaderboard("Race2", LEADERBOARDNAME, true);
|
|
| 50 | - service.addColumnToLeaderboard("Race3", LEADERBOARDNAME, true);
|
|
| 51 | - leglist = new ArrayList<String>();
|
|
| 52 | - leglist.add("Race1");
|
|
| 53 | - leglist.add("Race2");
|
|
| 54 | - leglist.add("Race3");
|
|
| 55 | - leaderboardCreationDate = new Date();
|
|
| 56 | - try {
|
|
| 57 | - // get Leaderboard with name and current date
|
|
| 58 | - leaderboardOriginalDTO = new LeaderboardDTO(null, null, ScoringSchemeType.LOW_POINT, /* higherScoreIsBetter */ false, new LeaderboardDTO.UUIDGenerator() {
|
|
| 59 | - @Override
|
|
| 60 | - public String generateRandomUUID() {
|
|
| 61 | - return UUID.randomUUID().toString();
|
|
| 62 | - }
|
|
| 63 | - }, /* hasOverallDetails */ false);
|
|
| 64 | - leaderboardOriginalDTO.addRace("Race1", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null);
|
|
| 65 | - leaderboardOriginalDTO.addRace("Race3", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null);
|
|
| 66 | - leaderboardOriginalDTO.addRace("Race2", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null);
|
|
| 67 | - } catch (Exception e) {
|
|
| 68 | - e.printStackTrace();
|
|
| 69 | - }
|
|
| 70 | - }
|
|
| 71 | -
|
|
| 72 | - @Test
|
|
| 73 | - public void testColumnSwapping() {
|
|
| 74 | - testColumnSwappingFabian();
|
|
| 75 | - testLeaderBoardDTOMethods();
|
|
| 76 | - testSailingService();
|
|
| 77 | - }
|
|
| 78 | -
|
|
| 79 | - @Test
|
|
| 80 | - public void testSailingService() {
|
|
| 81 | - sailingService = new SailingServiceImplMock();
|
|
| 82 | - assertNotNull("Sailingservice != NULL", sailingService);
|
|
| 83 | - int td[] = { 5, 8 };
|
|
| 84 | - sailingService.removeLeaderboard(TEST_LEADERBOARD_NAME);
|
|
| 85 | - sailingService.createFlexibleLeaderboard(TEST_LEADERBOARD_NAME, null, td, ScoringSchemeType.LOW_POINT, null);
|
|
| 86 | - for (int i = 0; i < races.length; i++)
|
|
| 87 | - sailingService.addColumnToLeaderboard(races[i], TEST_LEADERBOARD_NAME, isMedalRace[i]);
|
|
| 88 | - sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]);
|
|
| 89 | - sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]);
|
|
| 90 | - sailingService.moveLeaderboardColumnUp(TEST_LEADERBOARD_NAME, races[2]);
|
|
| 91 | - sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[0], true);
|
|
| 92 | - sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[2], false);
|
|
| 93 | - try {
|
|
| 94 | - for (int i = 0; i < races.length; i++)
|
|
| 95 | - sailingService.addColumnToLeaderboard(races[i], TEST_LEADERBOARD_NAME, isMedalRace[i]);
|
|
| 96 | - sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]);
|
|
| 97 | - sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]);
|
|
| 98 | - sailingService.moveLeaderboardColumnUp(TEST_LEADERBOARD_NAME, races[2]);
|
|
| 99 | - sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[0], true);
|
|
| 100 | - sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[2], false);
|
|
| 101 | - lb = sailingService.getLeaderboardByName(TEST_LEADERBOARD_NAME, new Date(), /* races to load */ null, /* addOverallDetails */ true, /* previous leaderboard ID */ null,
|
|
| 102 | - /* fillNetPointsUncorrected */ false).getLeaderboardDTO(/* previousVersion */ null);
|
|
| 103 | - } catch (Exception e) {
|
|
| 104 | - // e.printStackTrace();
|
|
| 105 | - fail(e.getLocalizedMessage());
|
|
| 106 | - }
|
|
| 107 | - assertNotNull("LeaderboardDTO != NULL", lb);
|
|
| 108 | -
|
|
| 109 | - // asserted data
|
|
| 110 | - String[] raceNames = new String[] { "3", "2", "1" };
|
|
| 111 | - boolean[] medalRace = { true, false, false };
|
|
| 112 | -
|
|
| 113 | - for (int i = 0; i < lb.getRaceList().size(); i++) {
|
|
| 114 | - assertEquals("Race[" + i + "] == " + raceNames[i], raceNames[i], lb.getRaceList().get(i).getName());
|
|
| 115 | - assertTrue("Race[" + i + "] is " + (medalRace[i] ? "" : "no ") + "medalrace.",
|
|
| 116 | - lb.raceIsMedalRace(races[i]) == medalRace[i]);
|
|
| 117 | - }
|
|
| 118 | - }
|
|
| 119 | -
|
|
| 120 | - @Test
|
|
| 121 | - public void testLeaderBoardDTOMethods() {
|
|
| 122 | - lb = new LeaderboardDTO(null, null, ScoringSchemeType.LOW_POINT, /* higherScoreIsBetter */ false, new LeaderboardDTO.UUIDGenerator() {
|
|
| 123 | - @Override
|
|
| 124 | - public String generateRandomUUID() {
|
|
| 125 | - return UUID.randomUUID().toString();
|
|
| 126 | - }
|
|
| 127 | - }, /* hasOverallDetails */ false);
|
|
| 128 | - assertNotNull("Leaderboard != NULL", lb);
|
|
| 129 | - lb.addRace("1", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, false, null, null);
|
|
| 130 | - lb.addRace("2", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, false, null, null);
|
|
| 131 | - lb.addRace("3", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null);
|
|
| 132 | - lb.moveRaceDown("1");
|
|
| 133 | - String[] s = new String[] { "2", "1", "3" };
|
|
| 134 | - for (int i = 0; i < lb.getRaceList().size(); i++) {
|
|
| 135 | - assertEquals("Race[" + i + "] = " + s[i], s[i], lb.getRaceList().get(i).getName());
|
|
| 136 | - }
|
|
| 137 | - }
|
|
| 138 | -
|
|
| 139 | - @Test
|
|
| 140 | - public void testColumnSwappingFabian() {
|
|
| 141 | - service.moveLeaderboardColumnUp(LEADERBOARDNAME, "Race3");
|
|
| 142 | - try {
|
|
| 143 | - leaderboardDTO = service.getLeaderboardByName(LEADERBOARDNAME, leaderboardCreationDate, leglist, /* addOverallDetails */
|
|
| 144 | - true, /* previous leaderboard ID */null, /* fillNetPointsUncorrected */false).getLeaderboardDTO(/* previousVersion */null);
|
|
| 145 | - } catch (Exception e) {
|
|
| 146 | - // TODO Auto-generated catch block
|
|
| 147 | - e.printStackTrace();
|
|
| 148 | - }
|
|
| 149 | -
|
|
| 150 | - // check if leaderboardDTO an dleaderboardOriginalDTO same
|
|
| 151 | - List<RaceColumnDTO> leaderboardList = leaderboardDTO.getRaceList();
|
|
| 152 | - List<RaceColumnDTO> leaderboardOriginalList = leaderboardOriginalDTO.getRaceList();
|
|
| 153 | -
|
|
| 154 | - // ??????? assert races in list
|
|
| 155 | - assertArrayEquals(leaderboardList.toArray(), leaderboardOriginalList.toArray());
|
|
| 156 | -
|
|
| 157 | - for (RaceColumnDTO raceDTO : leaderboardOriginalList) {
|
|
| 158 | - assert leaderboardDTO.raceIsMedalRace(raceDTO.getRaceColumnName()) == leaderboardOriginalDTO.raceIsMedalRace(raceDTO.getRaceColumnName());
|
|
| 159 | - assert leaderboardDTO.raceIsTracked(raceDTO.getRaceColumnName()) == leaderboardOriginalDTO.raceIsTracked(raceDTO.getRaceColumnName());
|
|
| 160 | - }
|
|
| 161 | - }
|
|
| 162 | -}
|
|
| 1 | +package com.sap.sailing.gwt.ui.test; |
|
| 2 | + |
|
| 3 | +import static org.junit.Assert.assertArrayEquals; |
|
| 4 | +import static org.junit.Assert.assertEquals; |
|
| 5 | +import static org.junit.Assert.assertNotNull; |
|
| 6 | +import static org.junit.Assert.assertTrue; |
|
| 7 | +import static org.junit.Assert.fail; |
|
| 8 | + |
|
| 9 | +import java.util.ArrayList; |
|
| 10 | +import java.util.Collection; |
|
| 11 | +import java.util.Date; |
|
| 12 | +import java.util.List; |
|
| 13 | +import java.util.UUID; |
|
| 14 | + |
|
| 15 | +import org.junit.Before; |
|
| 16 | +import org.junit.Test; |
|
| 17 | + |
|
| 18 | +import com.sap.sailing.domain.common.ScoringSchemeType; |
|
| 19 | +import com.sap.sailing.domain.common.dto.FleetDTO; |
|
| 20 | +import com.sap.sailing.domain.common.dto.LeaderboardDTO; |
|
| 21 | +import com.sap.sailing.domain.common.dto.RaceColumnDTO; |
|
| 22 | +import com.sap.sailing.gwt.ui.server.SailingServiceImpl; |
|
| 23 | + |
|
| 24 | +public class TestColumnSwapping { |
|
| 25 | + |
|
| 26 | + private LeaderboardDTO lb = null; |
|
| 27 | + private static final String LEADERBOARDNAME = "test"; |
|
| 28 | + private static final String DEFAULT_FLEET_NAME = "Default"; |
|
| 29 | + private static final FleetDTO DEFAULT_FLEET = new FleetDTO(DEFAULT_FLEET_NAME, /* ordering */ 0, /* color */ null); |
|
| 30 | + private SailingServiceImpl service; |
|
| 31 | + private LeaderboardDTO leaderboardOriginalDTO; |
|
| 32 | + private LeaderboardDTO leaderboardDTO; |
|
| 33 | + private Collection<String> leglist; |
|
| 34 | + private Date leaderboardCreationDate; |
|
| 35 | + private SailingServiceImpl sailingService = null; |
|
| 36 | + |
|
| 37 | + // Test-Data |
|
| 38 | + private final String TEST_LEADERBOARD_NAME = "test_board"; |
|
| 39 | + private final String[] races = { "1", "2", "3" }; |
|
| 40 | + private final boolean[] isMedalRace = { false, false, true }; |
|
| 41 | + |
|
| 42 | + @Before |
|
| 43 | + public void prepareColumnSwapping() { |
|
| 44 | + service = new SailingServiceImplMock(); |
|
| 45 | + int[] disc = { 5, 8, 9, 0, 7, 5, 43 }; |
|
| 46 | + service.removeLeaderboard(LEADERBOARDNAME); |
|
| 47 | + service.createFlexibleLeaderboard(LEADERBOARDNAME, null, disc, ScoringSchemeType.LOW_POINT, null); |
|
| 48 | + service.addColumnToLeaderboard("Race1", LEADERBOARDNAME, true); |
|
| 49 | + service.addColumnToLeaderboard("Race2", LEADERBOARDNAME, true); |
|
| 50 | + service.addColumnToLeaderboard("Race3", LEADERBOARDNAME, true); |
|
| 51 | + leglist = new ArrayList<String>(); |
|
| 52 | + leglist.add("Race1"); |
|
| 53 | + leglist.add("Race2"); |
|
| 54 | + leglist.add("Race3"); |
|
| 55 | + leaderboardCreationDate = new Date(); |
|
| 56 | + try { |
|
| 57 | + // get Leaderboard with name and current date |
|
| 58 | + leaderboardOriginalDTO = new LeaderboardDTO(null, null, ScoringSchemeType.LOW_POINT, /* higherScoreIsBetter */ false, new LeaderboardDTO.UUIDGenerator() { |
|
| 59 | + @Override |
|
| 60 | + public String generateRandomUUID() { |
|
| 61 | + return UUID.randomUUID().toString(); |
|
| 62 | + } |
|
| 63 | + }, /* hasOverallDetails */ false); |
|
| 64 | + leaderboardOriginalDTO.addRace("Race1", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null, false); |
|
| 65 | + leaderboardOriginalDTO.addRace("Race3", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null, false); |
|
| 66 | + leaderboardOriginalDTO.addRace("Race2", /* explicitFactor */ null, 2., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null, false); |
|
| 67 | + } catch (Exception e) { |
|
| 68 | + e.printStackTrace(); |
|
| 69 | + } |
|
| 70 | + } |
|
| 71 | + |
|
| 72 | + @Test |
|
| 73 | + public void testColumnSwapping() { |
|
| 74 | + testColumnSwappingFabian(); |
|
| 75 | + testLeaderBoardDTOMethods(); |
|
| 76 | + testSailingService(); |
|
| 77 | + } |
|
| 78 | + |
|
| 79 | + @Test |
|
| 80 | + public void testSailingService() { |
|
| 81 | + sailingService = new SailingServiceImplMock(); |
|
| 82 | + assertNotNull("Sailingservice != NULL", sailingService); |
|
| 83 | + int td[] = { 5, 8 }; |
|
| 84 | + sailingService.removeLeaderboard(TEST_LEADERBOARD_NAME); |
|
| 85 | + sailingService.createFlexibleLeaderboard(TEST_LEADERBOARD_NAME, null, td, ScoringSchemeType.LOW_POINT, null); |
|
| 86 | + for (int i = 0; i < races.length; i++) |
|
| 87 | + sailingService.addColumnToLeaderboard(races[i], TEST_LEADERBOARD_NAME, isMedalRace[i]); |
|
| 88 | + sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]); |
|
| 89 | + sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]); |
|
| 90 | + sailingService.moveLeaderboardColumnUp(TEST_LEADERBOARD_NAME, races[2]); |
|
| 91 | + sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[0], true); |
|
| 92 | + sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[2], false); |
|
| 93 | + try { |
|
| 94 | + for (int i = 0; i < races.length; i++) |
|
| 95 | + sailingService.addColumnToLeaderboard(races[i], TEST_LEADERBOARD_NAME, isMedalRace[i]); |
|
| 96 | + sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]); |
|
| 97 | + sailingService.moveLeaderboardColumnDown(TEST_LEADERBOARD_NAME, races[0]); |
|
| 98 | + sailingService.moveLeaderboardColumnUp(TEST_LEADERBOARD_NAME, races[2]); |
|
| 99 | + sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[0], true); |
|
| 100 | + sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[2], false); |
|
| 101 | + lb = sailingService.getLeaderboardByName(TEST_LEADERBOARD_NAME, new Date(), /* races to load */ null, /* addOverallDetails */ true, /* previous leaderboard ID */ null, |
|
| 102 | + /* fillNetPointsUncorrected */ false).getLeaderboardDTO(/* previousVersion */ null); |
|
| 103 | + } catch (Exception e) { |
|
| 104 | + // e.printStackTrace(); |
|
| 105 | + fail(e.getLocalizedMessage()); |
|
| 106 | + } |
|
| 107 | + assertNotNull("LeaderboardDTO != NULL", lb); |
|
| 108 | + |
|
| 109 | + // asserted data |
|
| 110 | + String[] raceNames = new String[] { "3", "2", "1" }; |
|
| 111 | + boolean[] medalRace = { true, false, false }; |
|
| 112 | + |
|
| 113 | + for (int i = 0; i < lb.getRaceList().size(); i++) { |
|
| 114 | + assertEquals("Race[" + i + "] == " + raceNames[i], raceNames[i], lb.getRaceList().get(i).getName()); |
|
| 115 | + assertTrue("Race[" + i + "] is " + (medalRace[i] ? "" : "no ") + "medalrace.", |
|
| 116 | + lb.raceIsMedalRace(races[i]) == medalRace[i]); |
|
| 117 | + } |
|
| 118 | + } |
|
| 119 | + |
|
| 120 | + @Test |
|
| 121 | + public void testLeaderBoardDTOMethods() { |
|
| 122 | + lb = new LeaderboardDTO(null, null, ScoringSchemeType.LOW_POINT, /* higherScoreIsBetter */ false, new LeaderboardDTO.UUIDGenerator() { |
|
| 123 | + @Override |
|
| 124 | + public String generateRandomUUID() { |
|
| 125 | + return UUID.randomUUID().toString(); |
|
| 126 | + } |
|
| 127 | + }, /* hasOverallDetails */ false); |
|
| 128 | + assertNotNull("Leaderboard != NULL", lb); |
|
| 129 | + lb.addRace("1", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, false, null, null, false); |
|
| 130 | + lb.addRace("2", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, false, null, null, false); |
|
| 131 | + lb.addRace("3", /* explicitFactor */ null, 1., /* regattaName */ null, /* seriesName */ null, DEFAULT_FLEET, true, null, null, false); |
|
| 132 | + lb.moveRaceDown("1"); |
|
| 133 | + String[] s = new String[] { "2", "1", "3" }; |
|
| 134 | + for (int i = 0; i < lb.getRaceList().size(); i++) { |
|
| 135 | + assertEquals("Race[" + i + "] = " + s[i], s[i], lb.getRaceList().get(i).getName()); |
|
| 136 | + } |
|
| 137 | + } |
|
| 138 | + |
|
| 139 | + @Test |
|
| 140 | + public void testColumnSwappingFabian() { |
|
| 141 | + service.moveLeaderboardColumnUp(LEADERBOARDNAME, "Race3"); |
|
| 142 | + try { |
|
| 143 | + leaderboardDTO = service.getLeaderboardByName(LEADERBOARDNAME, leaderboardCreationDate, leglist, /* addOverallDetails */ |
|
| 144 | + true, /* previous leaderboard ID */null, /* fillNetPointsUncorrected */false).getLeaderboardDTO(/* previousVersion */null); |
|
| 145 | + } catch (Exception e) { |
|
| 146 | + // TODO Auto-generated catch block |
|
| 147 | + e.printStackTrace(); |
|
| 148 | + } |
|
| 149 | + |
|
| 150 | + // check if leaderboardDTO an dleaderboardOriginalDTO same |
|
| 151 | + List<RaceColumnDTO> leaderboardList = leaderboardDTO.getRaceList(); |
|
| 152 | + List<RaceColumnDTO> leaderboardOriginalList = leaderboardOriginalDTO.getRaceList(); |
|
| 153 | + |
|
| 154 | + // ??????? assert races in list |
|
| 155 | + assertArrayEquals(leaderboardList.toArray(), leaderboardOriginalList.toArray()); |
|
| 156 | + |
|
| 157 | + for (RaceColumnDTO raceDTO : leaderboardOriginalList) { |
|
| 158 | + assert leaderboardDTO.raceIsMedalRace(raceDTO.getRaceColumnName()) == leaderboardOriginalDTO.raceIsMedalRace(raceDTO.getRaceColumnName()); |
|
| 159 | + assert leaderboardDTO.raceIsTracked(raceDTO.getRaceColumnName()) == leaderboardOriginalDTO.raceIsTracked(raceDTO.getRaceColumnName()); |
|
| 160 | + } |
|
| 161 | + } |
|
| 162 | +} |
java/com.sap.sailing.gwt.ui/Leaderboard.css
| ... | ... | @@ -98,13 +98,13 @@ body, table td, select, button { |
| 98 | 98 | padding: 49px 0 0 80px; |
| 99 | 99 | width: 300px; |
| 100 | 100 | color: #7c7c7c; |
| 101 | - background: url(images/important-message.png) left center no-repeat; |
|
| 101 | + background: url(images/important-message.png) left center no-repeat; |
|
| 102 | 102 | } |
| 103 | 103 | |
| 104 | 104 | .LeaderboardHeader { |
| 105 | 105 | position: relative; |
| 106 | 106 | margin: 0 auto; |
| 107 | - width: 400px; |
|
| 107 | + width: 100%; |
|
| 108 | 108 | text-align: center; |
| 109 | 109 | font-family: 'Open Sans', Arial, Verdana, sans-serif; |
| 110 | 110 | font-size: 18px; |
java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF
| ... | ... | @@ -49,9 +49,10 @@ Require-Bundle: com.sap.sailing.domain, |
| 49 | 49 | org.apache.shiro.core;bundle-version="1.2.2", |
| 50 | 50 | com.sap.sse.gwt.theme, |
| 51 | 51 | com.sap.sailing.news, |
| 52 | - com.sap.sailing.polars.datamining.shared;bundle-version="1.0.0", |
|
| 52 | + com.sap.sailing.polars.datamining.shared, |
|
| 53 | 53 | com.sap.sailing.polars.datamining, |
| 54 | - com.sap.sse.datamining.annotations;bundle-version="1.0.0" |
|
| 54 | + com.sap.sse.datamining.annotations, |
|
| 55 | + com.sap.sse.shared.android |
|
| 55 | 56 | Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator |
| 56 | 57 | Bundle-ActivationPolicy: lazy |
| 57 | 58 | Import-Package: javax.servlet;version="3.1.0", |
java/com.sap.sailing.gwt.ui/RaceBoard.css
| ... | ... | @@ -6,6 +6,13 @@ |
| 6 | 6 | height: 0; |
| 7 | 7 | clear: both; |
| 8 | 8 | visibility: hidden; |
| 9 | + |
|
| 10 | + RegattaRaceInformation-Header |
|
| 11 | + RaceName-Label |
|
| 12 | + RaceSeriesAndFleet-Label |
|
| 13 | + |
|
| 14 | + RegattaAndRaceTime-Header??? |
|
| 15 | + RegattaName-Anchor |
|
| 9 | 16 | */ |
| 10 | 17 | |
| 11 | 18 | |
| ... | ... | @@ -87,6 +94,14 @@ table { |
| 87 | 94 | padding-top: 10px; |
| 88 | 95 | padding-left: 10px; |
| 89 | 96 | } |
| 97 | +.CombinedWindPanelParentDiv.indentsmall, |
|
| 98 | +.TrueNorthIndicatorPanelParentDiv.indentsmall { |
|
| 99 | + padding-left: 30px; |
|
| 100 | +} |
|
| 101 | +.CombinedWindPanelParentDiv.indentbig, |
|
| 102 | +.TrueNorthIndicatorPanelParentDiv.indentbig { |
|
| 103 | + padding-left: 45px; |
|
| 104 | +} |
|
| 90 | 105 | |
| 91 | 106 | .MapSimulationLegendParentDiv { |
| 92 | 107 | padding-bottom: 10px; |
| ... | ... | @@ -573,7 +588,8 @@ img.openColumn { |
| 573 | 588 | url(images/viewicons/@1x/img/SAP_RV_Leaderboard_GHOSTED.png), |
| 574 | 589 | url(images/arrows_top_black_small.png); |
| 575 | 590 | background-repeat: no-repeat, no-repeat; |
| 576 | - background-position: 4% 50%, 96% 50% |
|
| 591 | + background-position: 4% 50%, 96% 50%; |
|
| 592 | + background-position: 5px center, calc(100% - 6px) center; |
|
| 577 | 593 | } |
| 578 | 594 | |
| 579 | 595 | .gwt-SplitLayoutPanel-EastToggleButton-Closed-leaderboard { |
| ... | ... | @@ -582,7 +598,8 @@ img.openColumn { |
| 582 | 598 | url(images/viewicons/@1x/img/SAP_RV_Leaderboard_INACTIVE.png), |
| 583 | 599 | url(images/arrows_bottom_black_small.png); |
| 584 | 600 | background-repeat: no-repeat, no-repeat; |
| 585 | - background-position: 4% 50%, 96% 50% |
|
| 601 | + background-position: 4% 50%, 96% 50%; |
|
| 602 | + background-position: 5px center, calc(100% - 6px) center; |
|
| 586 | 603 | } |
| 587 | 604 | |
| 588 | 605 | .gwt-SplitLayoutPanel-NorthSouthToggleButton-Closed-media, |
| ... | ... | @@ -664,6 +681,32 @@ img.openColumn { |
| 664 | 681 | margin-top: 5px; |
| 665 | 682 | } |
| 666 | 683 | |
| 684 | +.compactHeader .RegattaRaceInformation-Header { |
|
| 685 | + left: 110px; |
|
| 686 | +} |
|
| 687 | +.compactHeader .RegattaRaceInformation-Header .RaceName-Label { |
|
| 688 | + display: block; |
|
| 689 | + font-size: 16px; |
|
| 690 | + padding: 2px 0; |
|
| 691 | +} |
|
| 692 | +.compactHeader .RegattaRaceInformation-Header .RaceSeriesAndFleet-Label { |
|
| 693 | + display: block; |
|
| 694 | + margin-top: 5px; |
|
| 695 | + white-space: nowrap; |
|
| 696 | +} |
|
| 697 | +.compactHeader .RegattaAndRaceTime-Header { |
|
| 698 | + top: 11px; |
|
| 699 | +} |
|
| 700 | +.compactHeader .RegattaAndRaceTime-Header .RegattaName-Anchor { |
|
| 701 | + width: 115px; |
|
| 702 | + overflow: hidden; |
|
| 703 | + text-overflow: ellipsis; |
|
| 704 | + display: inline-block; |
|
| 705 | + white-space: nowrap; |
|
| 706 | + padding: 2px 0 2px 20px; |
|
| 707 | + background-position: 4px -1px; |
|
| 708 | +} |
|
| 709 | + |
|
| 667 | 710 | .VideoPopup-Close-Button { |
| 668 | 711 | border: none; |
| 669 | 712 | } |
java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch
| ... | ... | @@ -17,7 +17,7 @@ |
| 17 | 17 | <listEntry value="com.sap.sailing.gwt.AutoPlay"/>
|
| 18 | 18 | <listEntry value="com.sap.sailing.gwt.ui.AdminConsole"/>
|
| 19 | 19 | <listEntry value="com.sap.sailing.gwt.home.Home"/>
|
| 20 | -<listEntry value="com.sap.sailing.gwt.ui.VideoPopup"/>
|
|
| 20 | +<listEntry value="com.sap.sailing.gwt.AutoPlay"/>
|
|
| 21 | 21 | </listAttribute>
|
| 22 | 22 | <booleanAttribute key="com.google.gwt.eclipse.core.SUPERDEVMODE_ENABLED" value="true"/>
|
| 23 | 23 | <stringAttribute key="com.google.gwt.eclipse.core.URL" value="/gwt/Home.html"/>
|
| ... | ... | @@ -73,7 +73,7 @@ |
| 73 | 73 | <booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
|
| 74 | 74 | <stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
|
| 75 | 75 | <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
|
| 76 | -<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-superDevMode -incremental -workDir "${project_loc:com.sap.sailing.gwt.ui}/.tmp/gwt-work" -war "${project_loc:com.sap.sailing.gwt.ui}" -noserver -remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -logLevel INFO -codeServerPort 9876 -startupUrl /gwt/Home.html com.sap.sailing.gwt.home.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/EmbeddedMapAndWindChart com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup com.sap.sailing.gwt.home.Home com.sap.sailing.gwt.ui.AdminConsole com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.ui.YoutubePopup com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.Leaderboard com.sap.sailing.gwt.regattaoverview.RegattaOverview com.sap.sailing.gwt.ui.Spectator com.sap.sailing.gwt.ui.LeaderboardEditing com.sap.sailing.gwt.ui.DataMining com.sap.sailing.gwt.ui.RaceBoard com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart"/>
|
|
| 76 | +<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-superDevMode -incremental -workDir "${project_loc:com.sap.sailing.gwt.ui}/.tmp/gwt-work" -war "${project_loc:com.sap.sailing.gwt.ui}" -noserver -remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -logLevel INFO -codeServerPort 9876 -startupUrl /gwt/Home.html com.sap.sailing.gwt.home.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/EmbeddedMapAndWindChart com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup -startupUrl /gwt/AutoPlay.html com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.home.Home com.sap.sailing.gwt.ui.AdminConsole com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.ui.YoutubePopup com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.Leaderboard com.sap.sailing.gwt.regattaoverview.RegattaOverview com.sap.sailing.gwt.ui.Spectator com.sap.sailing.gwt.ui.LeaderboardEditing com.sap.sailing.gwt.ui.DataMining com.sap.sailing.gwt.ui.RaceBoard com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart"/>
|
|
| 77 | 77 | <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/>
|
| 78 | 78 | <stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xms1024m -Xmx3072m"/>
|
| 79 | 79 | </launchConfiguration>
|
java/com.sap.sailing.gwt.ui/SailingGWT.launch
| ... | ... | @@ -16,7 +16,7 @@ |
| 16 | 16 | <listEntry value="com.sap.sailing.gwt.AutoPlay"/>
|
| 17 | 17 | <listEntry value="com.sap.sailing.gwt.ui.AdminConsole"/>
|
| 18 | 18 | <listEntry value="com.sap.sailing.gwt.home.Home"/>
|
| 19 | -<listEntry value="com.sap.sailing.gwt.ui.VideoPopup"/>
|
|
| 19 | +<listEntry value="com.sap.sailing.gwt.AutoPlay"/>
|
|
| 20 | 20 | </listAttribute>
|
| 21 | 21 | <stringAttribute key="com.google.gwt.eclipse.core.URL" value="/gwt/Home.html"/>
|
| 22 | 22 | <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
| ... | ... | @@ -70,7 +70,7 @@ |
| 70 | 70 | <booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
|
| 71 | 71 | <stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
|
| 72 | 72 | <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
|
| 73 | -<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-nosuperDevMode -incremental -war "${project_loc:com.sap.sailing.gwt.ui}" -noserver -remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -logLevel INFO -codeServerPort auto -startupUrl /gwt/Home.html com.sap.sailing.gwt.home.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup com.sap.sailing.gwt.home.Home com.sap.sailing.gwt.ui.AdminConsole com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.ui.YoutubePopup com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.Leaderboard com.sap.sailing.gwt.regattaoverview.RegattaOverview com.sap.sailing.gwt.ui.Spectator com.sap.sailing.gwt.ui.LeaderboardEditing com.sap.sailing.gwt.ui.DataMining com.sap.sailing.gwt.ui.RaceBoard"/>
|
|
| 73 | +<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-nosuperDevMode -incremental -war "${project_loc:com.sap.sailing.gwt.ui}" -noserver -remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -logLevel INFO -codeServerPort auto -startupUrl /gwt/Home.html com.sap.sailing.gwt.home.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup -startupUrl /gwt/AutoPlay.html com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.home.Home com.sap.sailing.gwt.ui.AdminConsole com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.ui.YoutubePopup com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.Leaderboard com.sap.sailing.gwt.regattaoverview.RegattaOverview com.sap.sailing.gwt.ui.Spectator com.sap.sailing.gwt.ui.LeaderboardEditing com.sap.sailing.gwt.ui.DataMining com.sap.sailing.gwt.ui.RaceBoard"/>
|
|
| 74 | 74 | <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/>
|
| 75 | 75 | <stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx2048m -Dgwt-usearchives=false -Dgwt.persistentunitcache=false"/>
|
| 76 | 76 | </launchConfiguration>
|
java/com.sap.sailing.gwt.ui/pom.xml
| ... | ... | @@ -11,19 +11,6 @@ |
| 11 | 11 | <artifactId>com.sap.sailing.gwt.ui</artifactId> |
| 12 | 12 | <packaging>eclipse-plugin</packaging> |
| 13 | 13 | |
| 14 | - <repositories> |
|
| 15 | - <repository> |
|
| 16 | - <id>sonatype</id> |
|
| 17 | - <url>http://oss.sonatype.org/content/repositories/snapshots</url> |
|
| 18 | - <snapshots> |
|
| 19 | - <enabled>true</enabled> |
|
| 20 | - </snapshots> |
|
| 21 | - <releases> |
|
| 22 | - <enabled>false</enabled> |
|
| 23 | - </releases> |
|
| 24 | - </repository> |
|
| 25 | - </repositories> |
|
| 26 | - |
|
| 27 | 14 | <dependencies> |
| 28 | 15 | <!-- The following dependency needs re-declaration with the desired version |
| 29 | 16 | because gwt-maps-api declares it with no version specifier which may be resolved |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/place/player/AutoPlayController.java
| ... | ... | @@ -107,7 +107,7 @@ public class AutoPlayController implements RaceTimesInfoProviderListener { |
| 107 | 107 | asyncActionsExecutor = new AsyncActionsExecutor(); |
| 108 | 108 | showWindChart = false; |
| 109 | 109 | leaderboard = null; |
| 110 | - leaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true); |
|
| 110 | + leaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true, /* showCompetitorSailIdColumn */ true, /* showCompetitorFullNameColumn */ true); |
|
| 111 | 111 | |
| 112 | 112 | leaderboardTimer = new Timer(PlayModes.Live, /* delayBetweenAutoAdvancesInMilliseconds */1000l); |
| 113 | 113 | leaderboardTimer.setLivePlayDelayInMillis(delayToLiveInMillis); |
| ... | ... | @@ -170,10 +170,10 @@ public class AutoPlayController implements RaceTimesInfoProviderListener { |
| 170 | 170 | RaceSelectionModel raceSelectionModel = new RaceSelectionModel(); |
| 171 | 171 | List<RegattaAndRaceIdentifier> singletonList = Collections.singletonList(raceToShow); |
| 172 | 172 | raceSelectionModel.setSelection(singletonList); |
| 173 | - |
|
| 174 | 173 | RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, mediaService, userService, asyncActionsExecutor, |
| 175 | - competitorsAndTheirBoats, raceboardTimer, raceSelectionModel, leaderboardName, null, /* event */null, raceboardViewConfig, |
|
| 176 | - errorReporter, StringMessages.INSTANCE, userAgent, raceTimesInfoProvider, /* showMapControls */false); |
|
| 174 | + competitorsAndTheirBoats, raceboardTimer, raceSelectionModel, leaderboardName, null, /* event */ null, raceboardViewConfig, |
|
| 175 | + errorReporter, StringMessages.INSTANCE, userAgent, raceTimesInfoProvider, /* showMapControls */ false, |
|
| 176 | + /* isScreenLargeEnoughToOfferChartSupport */ true); |
|
| 177 | 177 | return raceBoardPanel; |
| 178 | 178 | } |
| 179 | 179 |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/GetEventViewAction.java
| ... | ... | @@ -22,8 +22,8 @@ import com.sap.sailing.gwt.home.server.EventActionUtil; |
| 22 | 22 | import com.sap.sailing.gwt.home.server.LeaderboardContext; |
| 23 | 23 | import com.sap.sailing.gwt.home.server.EventActionUtil.LeaderboardCallback; |
| 24 | 24 | import com.sap.sailing.gwt.server.HomeServiceUtil; |
| 25 | -import com.sap.sse.common.media.ImageDescriptor; |
|
| 26 | 25 | import com.sap.sse.common.media.MediaTagConstants; |
| 26 | +import com.sap.sse.shared.media.ImageDescriptor; |
|
| 27 | 27 | |
| 28 | 28 | public class GetEventViewAction implements SailingAction<EventViewDTO>, IsClientCacheable { |
| 29 | 29 | private static final Logger logger = Logger.getLogger(GetEventViewAction.class.getName()); |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/eventoverview/GetEventOverviewStageAction.java
| ... | ... | @@ -20,7 +20,7 @@ import com.sap.sse.common.TimePoint; |
| 20 | 20 | import com.sap.sse.common.impl.MillisecondsDurationImpl; |
| 21 | 21 | import com.sap.sse.common.impl.MillisecondsTimePoint; |
| 22 | 22 | import com.sap.sse.common.media.MediaTagConstants; |
| 23 | -import com.sap.sse.common.media.VideoDescriptor; |
|
| 23 | +import com.sap.sse.shared.media.VideoDescriptor; |
|
| 24 | 24 | |
| 25 | 25 | public class GetEventOverviewStageAction implements SailingAction<ResultWithTTL<EventOverviewStageDTO>>, IsClientCacheable { |
| 26 | 26 |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/news/RaceCompetitorNewsEntryDTO.java
| ... | ... | @@ -2,6 +2,7 @@ package com.sap.sailing.gwt.home.communication.event.news; |
| 2 | 2 | |
| 3 | 3 | import java.util.Date; |
| 4 | 4 | |
| 5 | +import com.google.gwt.core.shared.GwtIncompatible; |
|
| 5 | 6 | import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; |
| 6 | 7 | import com.sap.sailing.gwt.ui.client.StringMessages; |
| 7 | 8 | |
| ... | ... | @@ -18,6 +19,7 @@ public class RaceCompetitorNewsEntryDTO extends AbstractRaceNewsEntryDTO { |
| 18 | 19 | private RaceCompetitorNewsEntryDTO() { |
| 19 | 20 | } |
| 20 | 21 | |
| 22 | + @GwtIncompatible |
|
| 21 | 23 | public RaceCompetitorNewsEntryDTO(String leaderboardName, String leaderboardGroupName, |
| 22 | 24 | RegattaAndRaceIdentifier regattaAndRaceIdentifier, String raceTitle, String boatClass, Date timestamp, |
| 23 | 25 | String competitorName, Type type) { |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/fakeseries/GetEventSeriesViewAction.java
| ... | ... | @@ -16,8 +16,8 @@ import com.sap.sailing.gwt.home.communication.SailingDispatchContext; |
| 16 | 16 | import com.sap.sailing.gwt.home.communication.event.EventMetadataDTO; |
| 17 | 17 | import com.sap.sailing.gwt.home.communication.fakeseries.EventSeriesViewDTO.EventSeriesState; |
| 18 | 18 | import com.sap.sailing.gwt.server.HomeServiceUtil; |
| 19 | -import com.sap.sse.common.media.ImageDescriptor; |
|
| 20 | 19 | import com.sap.sse.common.media.MediaTagConstants; |
| 20 | +import com.sap.sse.shared.media.ImageDescriptor; |
|
| 21 | 21 | |
| 22 | 22 | public class GetEventSeriesViewAction implements SailingAction<EventSeriesViewDTO>, IsClientCacheable { |
| 23 | 23 |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/media/GetMediaForEventAction.java
| ... | ... | @@ -11,9 +11,9 @@ import com.sap.sailing.gwt.home.communication.SailingDispatchContext; |
| 11 | 11 | import com.sap.sailing.gwt.home.communication.event.EventReferenceDTO; |
| 12 | 12 | import com.sap.sailing.gwt.server.HomeServiceUtil; |
| 13 | 13 | import com.sap.sailing.gwt.ui.shared.media.MediaConstants; |
| 14 | -import com.sap.sse.common.media.ImageDescriptor; |
|
| 15 | 14 | import com.sap.sse.common.media.MimeType; |
| 16 | -import com.sap.sse.common.media.VideoDescriptor; |
|
| 15 | +import com.sap.sse.shared.media.ImageDescriptor; |
|
| 16 | +import com.sap.sse.shared.media.VideoDescriptor; |
|
| 17 | 17 | |
| 18 | 18 | public class GetMediaForEventAction implements SailingAction<MediaDTO>, IsClientCacheable { |
| 19 | 19 |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/start/GetStartViewAction.java
| ... | ... | @@ -22,9 +22,9 @@ import com.sap.sailing.gwt.server.RecentEventsCalculator; |
| 22 | 22 | import com.sap.sailing.gwt.ui.shared.media.MediaConstants; |
| 23 | 23 | import com.sap.sse.common.Util; |
| 24 | 24 | import com.sap.sse.common.Util.Pair; |
| 25 | -import com.sap.sse.common.media.ImageDescriptor; |
|
| 26 | 25 | import com.sap.sse.common.media.MimeType; |
| 27 | -import com.sap.sse.common.media.VideoDescriptor; |
|
| 26 | +import com.sap.sse.shared.media.ImageDescriptor; |
|
| 27 | +import com.sap.sse.shared.media.VideoDescriptor; |
|
| 28 | 28 | |
| 29 | 29 | public class GetStartViewAction implements SailingAction<StartViewDTO>, IsClientCacheable { |
| 30 | 30 | private static final int MAX_RECENT_EVENTS = 3; |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/utils/EventParamUtils.java
| ... | ... | @@ -108,7 +108,7 @@ public class EventParamUtils { |
| 108 | 108 | .createNewDefaultSettings(null, null, |
| 109 | 109 | /* overallDetails */overallDetails, null, |
| 110 | 110 | /* autoExpandFirstRace */false, refreshIntervalMillis, numberOfLastRacesToShow, |
| 111 | - raceColumnSelectionStrategy); |
|
| 111 | + raceColumnSelectionStrategy, /* showCompetitorSailIdColumns */ true, /*showCompetitorFullNameColumn*/ true); |
|
| 112 | 112 | } |
| 113 | 113 | return result; |
| 114 | 114 | } |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventActivity.java
| ... | ... | @@ -44,7 +44,6 @@ import com.sap.sailing.gwt.home.shared.places.fakeseries.SeriesDefaultPlace; |
| 44 | 44 | import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory; |
| 45 | 45 | import com.sap.sailing.gwt.ui.client.StringMessages; |
| 46 | 46 | import com.sap.sailing.gwt.ui.client.refresh.ErrorAndBusyClientFactory; |
| 47 | -import com.sap.sailing.gwt.ui.raceboard.RaceBoardViewConfiguration; |
|
| 48 | 47 | import com.sap.sailing.gwt.ui.shared.util.NullSafeComparableComparator; |
| 49 | 48 | |
| 50 | 49 | public abstract class AbstractEventActivity<PLACE extends AbstractEventPlace> extends AbstractActivity implements Presenter { |
| ... | ... | @@ -248,7 +247,6 @@ public abstract class AbstractEventActivity<PLACE extends AbstractEventPlace> ex |
| 248 | 247 | } |
| 249 | 248 | linkParams.put("raceName", trackedRaceName); |
| 250 | 249 | linkParams.put("regattaName", regattaName); |
| 251 | - linkParams.put(RaceBoardViewConfiguration.PARAM_VIEW_MODE, "simple"); |
|
| 252 | 250 | // TODO this must only be forwarded if there is a logged-on user |
| 253 | 251 | // linkParams.put(RaceBoardViewConfiguration.PARAM_CAN_REPLAY_DURING_LIVE_RACES, "true"); |
| 254 | 252 | return linkParams; |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/ExperimentalFeatures.java
| ... | ... | @@ -54,7 +54,7 @@ public class ExperimentalFeatures { |
| 54 | 54 | /** |
| 55 | 55 | * Enables/disables the link to the race board on mobile races view (competition format), in case of a tracked race |
| 56 | 56 | */ |
| 57 | - public static final boolean ENABLE_RACE_VIEWER_LINK_ON_MOBILE = false; |
|
| 57 | + public static final boolean ENABLE_RACE_VIEWER_LINK_ON_MOBILE = true; |
|
| 58 | 58 | /** |
| 59 | 59 | * Provide selection to filter regattas by boat category on mobile multiregatta event overview |
| 60 | 60 | */ |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/regattaoverview/client/RegattaOverviewPanel.java
| ... | ... | @@ -237,7 +237,7 @@ public class RegattaOverviewPanel extends SimplePanel { |
| 237 | 237 | if (leaderboardsTabPanel != null) { |
| 238 | 238 | if (showLeaderboard) { |
| 239 | 239 | final CompetitorSelectionModel competitorSelectionProvider = new CompetitorSelectionModel(/* hasMultiSelection */ true); |
| 240 | - final LeaderboardSettings leaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true); |
|
| 240 | + final LeaderboardSettings leaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true, /* showCompetitorSailIdColumn */ true, /* showCompetitorFullNameColumn */ true); |
|
| 241 | 241 | sailingService.getLeaderboardsByEvent(eventDTO, new MarkedAsyncCallback<List<StrippedLeaderboardDTO>>( |
| 242 | 242 | new AsyncCallback<List<StrippedLeaderboardDTO>>() { |
| 243 | 243 | @Override |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/server/HomeServiceUtil.java
| ... | ... | @@ -44,12 +44,12 @@ import com.sap.sse.common.TimePoint; |
| 44 | 44 | import com.sap.sse.common.Util; |
| 45 | 45 | import com.sap.sse.common.Util.Pair; |
| 46 | 46 | import com.sap.sse.common.impl.MillisecondsTimePoint; |
| 47 | -import com.sap.sse.common.media.ImageDescriptor; |
|
| 48 | -import com.sap.sse.common.media.MediaDescriptor; |
|
| 49 | 47 | import com.sap.sse.common.media.MediaTagConstants; |
| 50 | -import com.sap.sse.common.media.VideoDescriptor; |
|
| 51 | 48 | import com.sap.sse.gwt.client.media.ImageDTO; |
| 52 | 49 | import com.sap.sse.gwt.client.media.VideoDTO; |
| 50 | +import com.sap.sse.shared.media.ImageDescriptor; |
|
| 51 | +import com.sap.sse.shared.media.MediaDescriptor; |
|
| 52 | +import com.sap.sse.shared.media.VideoDescriptor; |
|
| 53 | 53 | |
| 54 | 54 | public final class HomeServiceUtil { |
| 55 | 55 | public interface EventVisitor { |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AddWindFixDialog.java
| ... | ... | @@ -40,18 +40,18 @@ public class AddWindFixDialog extends DataEntryDialog<WindDTO> { |
| 40 | 40 | if(windDTO.trueWindSpeedInKnots == null) {
|
| 41 | 41 | errorMessage = stringMessages.pleaseEnterAValue();
|
| 42 | 42 | } else if(windDTO.trueWindSpeedInKnots != null && (windDTO.trueWindSpeedInKnots < 0.0 || windDTO.trueWindSpeedInKnots > 100.0)) {
|
| 43 | - errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.speedInKnots(), "0", "100");
|
|
| 43 | + errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.speedInKnots(), 0, 100);
|
|
| 44 | 44 | } else if(windDTO.trueWindFromDeg == null){
|
| 45 | 45 | errorMessage = stringMessages.pleaseEnterAValue();
|
| 46 | 46 | } else if(windDTO.trueWindFromDeg != null && (windDTO.trueWindFromDeg < 0.0 || windDTO.trueWindFromDeg > 360.0)){
|
| 47 | - errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.fromDeg(), "0", "360");
|
|
| 47 | + errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.fromDeg(), 0, 360);
|
|
| 48 | 48 | } else if(windDTO.measureTimepoint == null) {
|
| 49 | 49 | errorMessage = stringMessages.pleaseEnterAValue();
|
| 50 | 50 | } else if(windDTO.position != null) {
|
| 51 | 51 | if(windDTO.position.getLatDeg() < -90.0 || windDTO.position.getLatDeg() > 90.0){
|
| 52 | - errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.latitude(), "-90", "90");
|
|
| 52 | + errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.latitude(), -90, 90);
|
|
| 53 | 53 | } else if(windDTO.position.getLngDeg() < -180.0 || windDTO.position.getLngDeg() > 180.0){
|
| 54 | - errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.longitude(), "-180", "180");
|
|
| 54 | + errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.longitude(), -180, 180);
|
|
| 55 | 55 | }
|
| 56 | 56 | }
|
| 57 | 57 |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogCourseManagementWidget.java
| ... | ... | @@ -126,6 +126,7 @@ public class RaceLogCourseManagementWidget extends CourseManagementWidget { |
| 126 | 126 | @Override |
| 127 | 127 | public void onSuccess(RaceCourseDTO result) { |
| 128 | 128 | updateWaypointsAndControlPoints(result); |
| 129 | + marks.refresh(result.getMarks()); |
|
| 129 | 130 | } |
| 130 | 131 | |
| 131 | 132 | @Override |
| ... | ... | @@ -133,18 +134,5 @@ public class RaceLogCourseManagementWidget extends CourseManagementWidget { |
| 133 | 134 | errorReporter.reportError("Could not load course: " + caught.getMessage()); |
| 134 | 135 | } |
| 135 | 136 | }); |
| 136 | - |
|
| 137 | - sailingService.getMarksInRaceLog(leaderboardName, raceColumnName, fleetName, |
|
| 138 | - new AsyncCallback<Iterable<MarkDTO>>() { |
|
| 139 | - @Override |
|
| 140 | - public void onSuccess(Iterable<MarkDTO> result) { |
|
| 141 | - marks.refresh(result); |
|
| 142 | - } |
|
| 143 | - |
|
| 144 | - @Override |
|
| 145 | - public void onFailure(Throwable caught) { |
|
| 146 | - errorReporter.reportError("Could not load marks: " + caught.getMessage()); |
|
| 147 | - } |
|
| 148 | - }); |
|
| 149 | 137 | } |
| 150 | 138 | } |
| ... | ... | \ No newline at end of file |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java
| ... | ... | @@ -225,7 +225,7 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane |
| 225 | 225 | declinationCheckbox.setValue(true);
|
| 226 | 226 | trackableRacesPanel.add(declinationCheckbox);
|
| 227 | 227 | |
| 228 | - final CheckBox simulateWithStartTimeNow = new CheckBox(stringMessages.simulateWithStartTimeNow());
|
|
| 228 | + final CheckBox simulateWithStartTimeNow = new CheckBox(stringMessages.simulateAsLiveRace());
|
|
| 229 | 229 | simulateWithStartTimeNow.setWordWrap(false);
|
| 230 | 230 | simulateWithStartTimeNow.setValue(false);
|
| 231 | 231 | trackableRacesPanel.add(simulateWithStartTimeNow);
|
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java
| ... | ... | @@ -24,6 +24,7 @@ import com.google.gwt.user.client.ui.CaptionPanel; |
| 24 | 24 | import com.google.gwt.user.client.ui.CheckBox;
|
| 25 | 25 | import com.google.gwt.user.client.ui.FlexTable;
|
| 26 | 26 | import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
|
| 27 | +import com.google.gwt.user.client.ui.FlowPanel;
|
|
| 27 | 28 | import com.google.gwt.user.client.ui.Grid;
|
| 28 | 29 | import com.google.gwt.user.client.ui.HTMLTable.ColumnFormatter;
|
| 29 | 30 | import com.google.gwt.user.client.ui.HorizontalPanel;
|
| ... | ... | @@ -44,6 +45,7 @@ import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn; |
| 44 | 45 | import com.sap.sailing.gwt.ui.shared.RegattaDTO;
|
| 45 | 46 | import com.sap.sailing.gwt.ui.shared.TracTracConfigurationDTO;
|
| 46 | 47 | import com.sap.sailing.gwt.ui.shared.TracTracRaceRecordDTO;
|
| 48 | +import com.sap.sse.common.Duration;
|
|
| 47 | 49 | import com.sap.sse.common.util.NaturalComparator;
|
| 48 | 50 | import com.sap.sse.gwt.client.ErrorReporter;
|
| 49 | 51 | import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
|
| ... | ... | @@ -83,7 +85,8 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { |
| 83 | 85 | |
| 84 | 86 | private LabeledAbstractFilterablePanel<TracTracRaceRecordDTO> racesFilterablePanel;
|
| 85 | 87 | private CellTable<TracTracRaceRecordDTO> racesTable;
|
| 86 | -
|
|
| 88 | + private static final String ZERO_AS_STRING = "0";
|
|
| 89 | +
|
|
| 87 | 90 | public TracTracEventManagementPanel(final SailingServiceAsync sailingService, ErrorReporter errorReporter,
|
| 88 | 91 | RegattaRefresher regattaRefresher, StringMessages stringMessages) {
|
| 89 | 92 | super(sailingService, regattaRefresher, errorReporter, new RaceSelectionModel(), true, stringMessages);
|
| ... | ... | @@ -229,17 +232,12 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { |
| 229 | 232 | |
| 230 | 233 | protected HorizontalPanel createRacesPanel() {
|
| 231 | 234 | HorizontalPanel racesPanel = new HorizontalPanel();
|
| 232 | -
|
|
| 233 | 235 | CaptionPanel trackableRacesPanel = createTrackableRacesPanel();
|
| 234 | -
|
|
| 235 | 236 | racesPanel.add(trackableRacesPanel);
|
| 236 | 237 | racesPanel.setCellWidth(trackableRacesPanel, "50%");
|
| 237 | -
|
|
| 238 | 238 | CaptionPanel trackedRacesPanel = createTrackedRacesPanel();
|
| 239 | -
|
|
| 240 | 239 | racesPanel.add(trackedRacesPanel);
|
| 241 | 240 | racesPanel.setCellWidth(trackedRacesPanel, "50%");
|
| 242 | -
|
|
| 243 | 241 | return racesPanel;
|
| 244 | 242 | }
|
| 245 | 243 | |
| ... | ... | @@ -276,10 +274,32 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { |
| 276 | 274 | correctWindCheckBox.setWordWrap(false);
|
| 277 | 275 | correctWindCheckBox.setValue(Boolean.TRUE);
|
| 278 | 276 | |
| 279 | - final CheckBox simulateWithStartTimeNowCheckBox = new CheckBox(stringMessages.simulateWithStartTimeNow());
|
|
| 277 | + final TextBox offsetToStartTimeOfSimulatedRaceTextBox = new TextBox();
|
|
| 278 | + offsetToStartTimeOfSimulatedRaceTextBox.setWidth("40px");
|
|
| 279 | + offsetToStartTimeOfSimulatedRaceTextBox.setEnabled(false);
|
|
| 280 | + offsetToStartTimeOfSimulatedRaceTextBox.setValue(ZERO_AS_STRING);
|
|
| 281 | +
|
|
| 282 | + final CheckBox simulateWithStartTimeNowCheckBox = new CheckBox(stringMessages.simulateAsLiveRace());
|
|
| 280 | 283 | simulateWithStartTimeNowCheckBox.ensureDebugId("SimulateWithStartTimeNowCheckBox");
|
| 281 | 284 | simulateWithStartTimeNowCheckBox.setWordWrap(false);
|
| 282 | 285 | simulateWithStartTimeNowCheckBox.setValue(Boolean.FALSE);
|
| 286 | + simulateWithStartTimeNowCheckBox.addClickHandler(new ClickHandler() {
|
|
| 287 | + @Override
|
|
| 288 | + public void onClick(ClickEvent event) {
|
|
| 289 | + offsetToStartTimeOfSimulatedRaceTextBox.setEnabled(simulateWithStartTimeNowCheckBox.getValue());
|
|
| 290 | + offsetToStartTimeOfSimulatedRaceTextBox.setFocus(simulateWithStartTimeNowCheckBox.getValue());
|
|
| 291 | + }
|
|
| 292 | + });
|
|
| 293 | +
|
|
| 294 | + final FlowPanel simulateAsLiveRacePanel = new FlowPanel();
|
|
| 295 | + simulateAsLiveRacePanel.add(simulateWithStartTimeNowCheckBox);
|
|
| 296 | +
|
|
| 297 | + final Label offsetToStartLabel = new Label(stringMessages.simulateWithOffset());
|
|
| 298 | +
|
|
| 299 | + final HorizontalPanel simulateWithOffsetPanel = new HorizontalPanel();
|
|
| 300 | + simulateWithOffsetPanel.add(offsetToStartLabel);
|
|
| 301 | + simulateWithOffsetPanel.add(offsetToStartTimeOfSimulatedRaceTextBox);
|
|
| 302 | +
|
|
| 283 | 303 | final CheckBox ignoreTracTracMarkPassingsCheckbox = new CheckBox(stringMessages.useInternalAlgorithm());
|
| 284 | 304 | ignoreTracTracMarkPassingsCheckbox.setWordWrap(false);
|
| 285 | 305 | ignoreTracTracMarkPassingsCheckbox.setValue(Boolean.FALSE);
|
| ... | ... | @@ -287,8 +307,9 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { |
| 287 | 307 | layoutTable.setWidget(1, 0, trackSettingsLabel);
|
| 288 | 308 | layoutTable.setWidget(1, 1, trackWindCheckBox);
|
| 289 | 309 | layoutTable.setWidget(2, 1, correctWindCheckBox);
|
| 290 | - layoutTable.setWidget(3, 1, simulateWithStartTimeNowCheckBox);
|
|
| 291 | - layoutTable.setWidget(4, 1, ignoreTracTracMarkPassingsCheckbox);
|
|
| 310 | + layoutTable.setWidget(3, 1, simulateAsLiveRacePanel);
|
|
| 311 | + layoutTable.setWidget(4, 1, simulateWithOffsetPanel);
|
|
| 312 | + layoutTable.setWidget(5, 1, ignoreTracTracMarkPassingsCheckbox);
|
|
| 292 | 313 | |
| 293 | 314 | // Filter
|
| 294 | 315 | Label racesFilterLabel = new Label(stringMessages.filterRacesByName() + ":");
|
| ... | ... | @@ -310,8 +331,8 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { |
| 310 | 331 | }
|
| 311 | 332 | };
|
| 312 | 333 | racesFilterablePanel.getTextBox().ensureDebugId("TrackableRacesFilterTextBox");
|
| 313 | - layoutTable.setWidget(5, 0, racesFilterLabel);
|
|
| 314 | - layoutTable.setWidget(5, 1, racesFilterablePanel);
|
|
| 334 | + layoutTable.setWidget(6, 0, racesFilterLabel);
|
|
| 335 | + layoutTable.setWidget(6, 1, racesFilterablePanel);
|
|
| 315 | 336 | |
| 316 | 337 | // Races
|
| 317 | 338 | TextColumn<TracTracRaceRecordDTO> regattaNameColumn = new TextColumn<TracTracRaceRecordDTO>() {
|
| ... | ... | @@ -357,7 +378,6 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { |
| 357 | 378 | }
|
| 358 | 379 | };
|
| 359 | 380 | raceVisibilityColumn.setSortable(true);
|
| 360 | -
|
|
| 361 | 381 | SelectionCheckboxColumn<TracTracRaceRecordDTO> selectionCheckboxColumn = new SelectionCheckboxColumn<TracTracRaceRecordDTO>(tableResources.cellTableStyle().cellTableCheckboxSelected(),
|
| 362 | 382 | tableResources.cellTableStyle().cellTableCheckboxDeselected(), tableResources.cellTableStyle().cellTableCheckboxColumnCell()) {
|
| 363 | 383 | @Override
|
| ... | ... | @@ -381,26 +401,24 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { |
| 381 | 401 | raceNameColumn, boatClassColumn, raceStartTrackingColumn, raceStatusColumn));
|
| 382 | 402 | racesTable.setSelectionModel(selectionCheckboxColumn.getSelectionModel(), selectionCheckboxColumn.getSelectionManager());
|
| 383 | 403 | racesTable.setWidth("100%");
|
| 384 | -
|
|
| 385 | 404 | raceList.addDataDisplay(racesTable);
|
| 386 | -
|
|
| 387 | 405 | layoutTable.setWidget(6, 0, racesTable);
|
| 388 | 406 | cellFormatter.setColSpan(6, 0, 2);
|
| 389 | -
|
|
| 390 | 407 | Button startTrackingButton = new Button(stringMessages.startTracking());
|
| 391 | 408 | startTrackingButton.ensureDebugId("StartTrackingButton");
|
| 392 | 409 | startTrackingButton.addClickHandler(new ClickHandler() {
|
| 393 | 410 | @Override
|
| 394 | 411 | public void onClick(ClickEvent event) {
|
| 412 | + Duration offsetToStartTimeOfSimulatedRace = null;
|
|
| 413 | + if(simulateWithStartTimeNowCheckBox.getValue().booleanValue()) {
|
|
| 414 | + offsetToStartTimeOfSimulatedRace = getMillisecondsDurationFromMinutesAsString(offsetToStartTimeOfSimulatedRaceTextBox.getValue());
|
|
| 415 | + }
|
|
| 395 | 416 | trackSelectedRaces(trackWindCheckBox.getValue(), correctWindCheckBox.getValue(),
|
| 396 | - simulateWithStartTimeNowCheckBox.getValue(), ignoreTracTracMarkPassingsCheckbox.getValue());
|
|
| 417 | + offsetToStartTimeOfSimulatedRace, ignoreTracTracMarkPassingsCheckbox.getValue());
|
|
| 397 | 418 | }
|
| 398 | 419 | });
|
| 399 | -
|
|
| 400 | 420 | layoutTable.setWidget(7, 1, startTrackingButton);
|
| 401 | -
|
|
| 402 | 421 | trackableRacesPanel.setContentWidget(layoutTable);
|
| 403 | -
|
|
| 404 | 422 | return trackableRacesPanel;
|
| 405 | 423 | }
|
| 406 | 424 | |
| ... | ... | @@ -462,6 +480,17 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { |
| 462 | 480 | |
| 463 | 481 | return boatClassNames.substring(0, boatClassNames.length() - 2);
|
| 464 | 482 | }
|
| 483 | +
|
|
| 484 | + private Duration getMillisecondsDurationFromMinutesAsString(String minutesAsString) {
|
|
| 485 | + Duration result = null;
|
|
| 486 | + if (minutesAsString != null) {
|
|
| 487 | + Double minutesAsDouble = Double.parseDouble(minutesAsString);
|
|
| 488 | + if (minutesAsDouble != null) {
|
|
| 489 | + result = Duration.ONE_MINUTE.times(minutesAsDouble.longValue());
|
|
| 490 | + }
|
|
| 491 | + }
|
|
| 492 | + return result;
|
|
| 493 | + }
|
|
| 465 | 494 | |
| 466 | 495 | private void fillConfigurations() {
|
| 467 | 496 | this.sailingService.getPreviousTracTracConfigurations(new MarkedAsyncCallback<List<TracTracConfigurationDTO>>(
|
| ... | ... | @@ -551,7 +580,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { |
| 551 | 580 | }));
|
| 552 | 581 | }
|
| 553 | 582 | |
| 554 | - private void trackSelectedRaces(boolean trackWind, boolean correctWind, final boolean simulateWithStartTimeNow, boolean ignoreTracTracMarkPassings) {
|
|
| 583 | + private void trackSelectedRaces(boolean trackWind, boolean correctWind, final Duration offsetToStartTimeOfSimulatedRace, boolean ignoreTracTracMarkPassings) {
|
|
| 555 | 584 | String liveURI = liveURITextBox.getValue();
|
| 556 | 585 | String storedURI = storedURITextBox.getValue();
|
| 557 | 586 | String courseDesignUpdateURI = tracTracUpdateURITextBox.getValue();
|
| ... | ... | @@ -577,7 +606,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { |
| 577 | 606 | }
|
| 578 | 607 | if (checkBoatClassOK(selectedRegatta, selectedRaces)) {
|
| 579 | 608 | sailingService.trackWithTracTrac(regattaIdentifier, selectedRaces, liveURI, storedURI,
|
| 580 | - courseDesignUpdateURI, trackWind, correctWind, simulateWithStartTimeNow, ignoreTracTracMarkPassings, tractracUsername,
|
|
| 609 | + courseDesignUpdateURI, trackWind, correctWind, offsetToStartTimeOfSimulatedRace, ignoreTracTracMarkPassings, tractracUsername,
|
|
| 581 | 610 | tractracPassword, new MarkedAsyncCallback<Void>(new AsyncCallback<Void>() {
|
| 582 | 611 | @Override
|
| 583 | 612 | public void onFailure(Throwable caught) {
|
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanel.java
| ... | ... | @@ -30,8 +30,8 @@ public class RaceTimePanel extends TimePanel<RaceTimePanelSettings> implements R |
| 30 | 30 | private boolean redrawAllMarkersPendingForMinMaxBeingInitialized;
|
| 31 | 31 | |
| 32 | 32 | public RaceTimePanel(Timer timer, TimeRangeWithZoomProvider timeRangeProvider, StringMessages stringMessages,
|
| 33 | - RaceTimesInfoProvider raceTimesInfoProvider, boolean canReplayWhileLiveIsPossible) {
|
|
| 34 | - super(timer, timeRangeProvider, stringMessages, canReplayWhileLiveIsPossible);
|
|
| 33 | + RaceTimesInfoProvider raceTimesInfoProvider, boolean canReplayWhileLiveIsPossible, boolean isScreenLargeEnoughToOfferChartSupport) {
|
|
| 34 | + super(timer, timeRangeProvider, stringMessages, canReplayWhileLiveIsPossible, isScreenLargeEnoughToOfferChartSupport);
|
|
| 35 | 35 | this.raceTimesInfoProvider = raceTimesInfoProvider;
|
| 36 | 36 | selectedRace = null;
|
| 37 | 37 | autoAdjustPlayMode = true;
|
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java
| ... | ... | @@ -86,6 +86,7 @@ import com.sap.sailing.gwt.ui.shared.TrackFileImportDeviceIdentifierDTO; |
| 86 | 86 | import com.sap.sailing.gwt.ui.shared.VenueDTO; |
| 87 | 87 | import com.sap.sailing.gwt.ui.shared.WindDTO; |
| 88 | 88 | import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO; |
| 89 | +import com.sap.sse.common.Duration; |
|
| 89 | 90 | import com.sap.sse.common.NoCorrespondingServiceRegisteredException; |
| 90 | 91 | import com.sap.sse.common.TimePoint; |
| 91 | 92 | import com.sap.sse.common.Util; |
| ... | ... | @@ -116,7 +117,7 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS |
| 116 | 117 | |
| 117 | 118 | void trackWithTracTrac(RegattaIdentifier regattaToAddTo, Iterable<TracTracRaceRecordDTO> rrs, String liveURI, |
| 118 | 119 | String storedURI, String courseDesignUpdateURI, boolean trackWind, boolean correctWindByDeclination, |
| 119 | - boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, String tracTracUsername, String tracTracPassword) throws Exception; |
|
| 120 | + Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, String tracTracUsername, String tracTracPassword) throws Exception; |
|
| 120 | 121 | |
| 121 | 122 | void trackWithSwissTiming(RegattaIdentifier regattaToAddTo, Iterable<SwissTimingRaceRecordDTO> rrs, String hostname, int port, |
| 122 | 123 | boolean trackWind, boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm) throws Exception; |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java
| ... | ... | @@ -78,6 +78,7 @@ import com.sap.sailing.gwt.ui.shared.TrackFileImportDeviceIdentifierDTO; |
| 78 | 78 | import com.sap.sailing.gwt.ui.shared.VenueDTO; |
| 79 | 79 | import com.sap.sailing.gwt.ui.shared.WindDTO; |
| 80 | 80 | import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO; |
| 81 | +import com.sap.sse.common.Duration; |
|
| 81 | 82 | import com.sap.sse.common.TimePoint; |
| 82 | 83 | import com.sap.sse.common.Util; |
| 83 | 84 | import com.sap.sse.common.Util.Pair; |
| ... | ... | @@ -114,10 +115,10 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan |
| 114 | 115 | * @param liveURI |
| 115 | 116 | * may be <code>null</code> or the empty string in which case the server will use the |
| 116 | 117 | * {@link TracTracRaceRecordDTO#liveURI} from the <code>rr</code> race record. |
| 117 | - * @param simulateWithStartTimeNow |
|
| 118 | - * if <code>true</code>, the connector will adjust the time stamps of all events received such that the |
|
| 118 | + * @param offsetToStartTimeOfSimulatedRace |
|
| 119 | + * if not <code>null</code>, the connector will adjust the time stamps of all events received such that the |
|
| 119 | 120 | * first mark passing for the first waypoint will be set to "now." It will delay the forwarding of all |
| 120 | - * events received such that they seem to be sent in "real-time." So, more or less the time points |
|
| 121 | + * events received such that they seem to be sent in "real-time" plus the <code>offsetToStartTimeOfSimulatedRace</code>. So, more or less the time points |
|
| 121 | 122 | * attached to the events sent to the receivers will again approximate the wall time. |
| 122 | 123 | * @param useInternalMarkPassingAlgorithm |
| 123 | 124 | * whether or not to ignore the TracTrac-provided mark passings; if <code>true</code>, a separate mark |
| ... | ... | @@ -128,7 +129,7 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan |
| 128 | 129 | */ |
| 129 | 130 | void trackWithTracTrac(RegattaIdentifier regattaToAddTo, Iterable<TracTracRaceRecordDTO> rrs, String liveURI, |
| 130 | 131 | String storedURI, String courseDesignUpdateURI, boolean trackWind, boolean correctWindByDeclination, |
| 131 | - boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, String tracTracUsername, |
|
| 132 | + Duration offsetToStartTimeOfSimulatedRace, boolean useInternalMarkPassingAlgorithm, String tracTracUsername, |
|
| 132 | 133 | String tracTracPassword, AsyncCallback<Void> callback); |
| 133 | 134 | |
| 134 | 135 | void trackWithSwissTiming(RegattaIdentifier regattaToAddTo, Iterable<SwissTimingRaceRecordDTO> rrs, |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java
| ... | ... | @@ -361,7 +361,8 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c |
| 361 | 361 | String successfullyUpdatedScores(); |
| 362 | 362 | String errorUpdatingScoresForLeaderboard(String leaderboardName, String message); |
| 363 | 363 | String maneuverTypesToShowWhenCompetitorIsClicked(); |
| 364 | - String simulateWithStartTimeNow(); |
|
| 364 | + String simulateAsLiveRace(); |
|
| 365 | + String simulateWithOffset(); |
|
| 365 | 366 | String boatClassDoesNotMatchSelectedRegatta(String boatClass); |
| 366 | 367 | String regattaExistForSelectedBoatClass(); |
| 367 | 368 | String reload(); |
| ... | ... | @@ -436,7 +437,8 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c |
| 436 | 437 | String overallDetailsToShow(); |
| 437 | 438 | String hhmmssUnit(); |
| 438 | 439 | String actionAddWindData(); |
| 439 | - String valueMustBeBetweenMinMax(String name, String minValue, String maxValue); |
|
| 440 | + String valueMustBeBetweenMinMax(String name, int minValue, int maxValue); |
|
| 441 | + String valueMustBeGreaterThan(String name, String minValue); |
|
| 440 | 442 | String optional(); |
| 441 | 443 | String pleaseEnterAValue(); |
| 442 | 444 | String latitude(); |
| ... | ... | @@ -503,6 +505,8 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c |
| 503 | 505 | String sideToWhichMarkAtLegStartWasRounded(); |
| 504 | 506 | String raceIsLive(String raceName); |
| 505 | 507 | String racesAreLive(String raceNames); |
| 508 | + String regattaIsLive(String regattaName); |
|
| 509 | + String regattasAreLive(String regattaNames); |
|
| 506 | 510 | String scoringSchemeHighPointFirstGetsOne(); |
| 507 | 511 | String scoringSchemeHighPointFirstGetsTen(); |
| 508 | 512 | String knotsUnit(); |
| ... | ... | @@ -1444,4 +1448,6 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c |
| 1444 | 1448 | String requiresValidRegatta(); |
| 1445 | 1449 | String couldNotObtainRace(String regattaLikeName, String raceColumnName, String fleetName, String technicalErrorMessage); |
| 1446 | 1450 | String errorTryingToCreateEmbeddedMap(String message); |
| 1451 | + String transparentBufferLineOnHover(); |
|
| 1452 | + String bufferLineStrokeWeight(); |
|
| 1447 | 1453 | } |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties
| ... | ... | @@ -371,7 +371,8 @@ apply=Apply |
| 371 | 371 | successfullyUpdatedScores=Successfully updated scores |
| 372 | 372 | errorUpdatingScoresForLeaderboard=Error updating scores for leaderboard {0}: {1} |
| 373 | 373 | maneuverTypesToShowWhenCompetitorIsClicked=Maneuver types to show when competitor is clicked |
| 374 | -simulateWithStartTimeNow=Simulate with "now" as start time |
|
| 374 | +simulateAsLiveRace=Simulate as live race |
|
| 375 | +simulateWithOffset=Offset before start in minutes: |
|
| 375 | 376 | boatClassDoesNotMatchSelectedRegatta=The selected races contain boat classes which are not the same as the boat class ''{0}'' of the selected regatta. No races will be loaded. |
| 376 | 377 | regattaExistForSelectedBoatClass=There is at least one regatta for the selected boat classes. Really create default regatta(s) for those race(s)? |
| 377 | 378 | reload=Reload |
| ... | ... | @@ -456,6 +457,7 @@ totalAverageSpeedOverGroundTooltip=Average speed over ground across the regatta. |
| 456 | 457 | hhmmssUnit=hh:mm:ss |
| 457 | 458 | actionAddWindData=Add wind data |
| 458 | 459 | valueMustBeBetweenMinMax=The value of ''{0}'' must be between {1} and {2}. |
| 460 | +valueMustBeGreaterThan=The value of ''{0}'' must be greater than {1}. |
|
| 459 | 461 | optional=optional |
| 460 | 462 | pleaseEnterAValue=Please enter a value. |
| 461 | 463 | latitude=Latitude |
| ... | ... | @@ -527,6 +529,8 @@ sideToWhichMarkAtLegStartWasRounded=Mark rounded to |
| 527 | 529 | sideToWhichMarkAtLegStartWasRoundedTooltip=Side to which the mark at leg start was rounded. |
| 528 | 530 | raceIsLive=Race {0} is live |
| 529 | 531 | racesAreLive=Races {0} are live |
| 532 | +regattaIsLive=Regatta {0} is live |
|
| 533 | +regattasAreLive=Regattas {0} are live |
|
| 530 | 534 | knotsUnit=kts |
| 531 | 535 | knotsValue={0,number,#0.0} kts |
| 532 | 536 | knotsRange={0,number,#0.0} - {1,number,#0.0} kts |
| ... | ... | @@ -1410,4 +1414,6 @@ selectALeaderboardGroup=Select a leaderboard group... |
| 1410 | 1414 | pleaseSelect=Please select |
| 1411 | 1415 | requiresValidRegatta=This page requires a valid regatta, race column and fleet name to identify the race to show. |
| 1412 | 1416 | couldNotObtainRace=Could not obtain a race with name {1} for fleet {2} for a regatta with name {0}: {3} |
| 1413 | -errorTryingToCreateEmbeddedMap=Error trying to create the embedded map: {0} |
|
| ... | ... | \ No newline at end of file |
| 0 | +errorTryingToCreateEmbeddedMap=Error trying to create the embedded map: {0} |
|
| 1 | +transparentBufferLineOnHover=Transparent buffer lines on hover of helplines |
|
| 2 | +bufferLineStrokeWeight=Buffer line stroke weight |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties
| ... | ... | @@ -372,7 +372,8 @@ apply=Übernehmen |
| 372 | 372 | successfullyUpdatedScores=Ergebnisse erfolgreich aktualisiert |
| 373 | 373 | errorUpdatingScoresForLeaderboard=Fehler beim Aktualisieren der Ergebnisse für {0}: {1} |
| 374 | 374 | maneuverTypesToShowWhenCompetitorIsClicked=Anzuzeigende Manövertypen für angeklickte Teilnehmer |
| 375 | -simulateWithStartTimeNow=Simulieren mit "jetzt" als Startzeit |
|
| 375 | +simulateAsLiveRace=Simuliere als Live Rennen |
|
| 376 | +simulateWithOffset=Zeit bis zum Start in Minuten: |
|
| 376 | 377 | boatClassDoesNotMatchSelectedRegatta=Die ausgewählten Rennen enthalten eine Bootsklasse, die nicht der Bootsklasse der ausgewählten Regatta ''{0}'' entspricht. Es werden keine Rennen geladen. |
| 377 | 378 | regattaExistForSelectedBoatClass=Es gibt mindestens eine Regatta zu den ausgewählten Bootsklassen. Dennoch Default-Regatta für diese(s) Rennen anlegen? |
| 378 | 379 | reload=Neu laden |
| ... | ... | @@ -456,7 +457,8 @@ totalAverageSpeedOverGround=\u2205 FüG |
| 456 | 457 | totalAverageSpeedOverGroundTooltip=Durchschnittliche Fahrt über Grund über die gesamte Regatta. |
| 457 | 458 | hhmmssUnit=hh:mm:ss |
| 458 | 459 | actionAddWindData=Winddaten hinzufügen |
| 459 | -valueMustBeBetweenMinMax=Der Wert von ''{0}'' muß zwischen {1} und {2} sein. |
|
| 460 | +valueMustBeBetweenMinMax=Der Wert von ''{0}'' muss zwischen {1} und {2} sein. |
|
| 461 | +valueMustBeGreaterThan=Der Wert von ''{0}'' muss größer als {1} sein. |
|
| 460 | 462 | optional=optional |
| 461 | 463 | pleaseEnterAValue=Bitte geben Sie einen Wert ein. |
| 462 | 464 | latitude=Breite |
| ... | ... | @@ -528,6 +530,8 @@ sideToWhichMarkAtLegStartWasRounded=Tonne gerundet an |
| 528 | 530 | sideToWhichMarkAtLegStartWasRoundedTooltip=Seite, an der die Bahnmarke am Schenkelstart gerundet wurde |
| 529 | 531 | raceIsLive=Rennen {0} ist live |
| 530 | 532 | racesAreLive=Rennen {0} sind live |
| 533 | +regattaIsLive=Regatta {0} ist live |
|
| 534 | +regattasAreLive=Regatten {0} sind live |
|
| 531 | 535 | scoringSchemeHighPointFirstGetsOne=High Point System, Gewinner erhält 1 Punkt |
| 532 | 536 | scoringSchemeHighPointFirstGetsTen=High Point System, Gewinner erhält 10 Punkte |
| 533 | 537 | knotsUnit=kn |
| ... | ... | @@ -1401,4 +1405,6 @@ selectALeaderboardGroup=Bitte eine Ranglisten-Gruppe auswählen... |
| 1401 | 1405 | pleaseSelect=Bitte auswählen |
| 1402 | 1406 | requiresValidRegatta=Diese Seite benötigt eine gültige Regatta, Rennspalte und einen gültigen Gruppennamen um die anzuzeigende Wettfahrt zu identifizieren. |
| 1403 | 1407 | couldNotObtainRace=Wettfahrt namens {1} für Gruppe {2} in einer Regatta names {0} konnte nicht gefunden werden: {3} |
| 1404 | -errorTryingToCreateEmbeddedMap=Fehler beim Erstellen der eingebetteten Karte: {0} |
|
| ... | ... | \ No newline at end of file |
| 0 | +errorTryingToCreateEmbeddedMap=Fehler beim Erstellen der eingebetteten Karte: {0} |
|
| 1 | +transparentBufferLineOnHover=Transparente Pufferlinien bei Maus über Hilfslinien |
|
| 2 | +bufferLineStrokeWeight=Linienbreite der Pufferlinien |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties
| ... | ... | @@ -364,7 +364,8 @@ apply=Применить |
| 364 | 364 | successfullyUpdatedScores=Очки обновлены успешно |
| 365 | 365 | errorUpdatingScoresForLeaderboard=Ошибка обновления очков для таблицы лидеров {0}: {1} |
| 366 | 366 | maneuverTypesToShowWhenCompetitorIsClicked=Типы маневров для отображения при нажатии на участника |
| 367 | -simulateWithStartTimeNow=Моделирования с "сейчас" в качестве времени начала |
|
| 367 | +simulateAsLiveRace=Simulate as live race |
|
| 368 | +simulateWithOffset=Offset before start in minutes: |
|
| 368 | 369 | boatClassDoesNotMatchSelectedRegatta=Выбранные гонки содержат классы судов не соответствующие классу судов ''{0}'' выбранной регаты. |
| 369 | 370 | regattaExistForSelectedBoatClass=Есть только одна регата для выбранных классов судов. |
| 370 | 371 | reload=Перезагрузить |
| ... | ... | @@ -440,6 +441,7 @@ totalAverageSpeedOverGroundTooltip=Средняя скорость относи |
| 440 | 441 | hhmmssUnit=чч:мм:сс |
| 441 | 442 | actionAddWindData=Добавить данные о ветре |
| 442 | 443 | valueMustBeBetweenMinMax=Значение ''{0}''должно быть в пределах от {1} до {2}. |
| 444 | +valueMustBeGreaterThan=The value of ''{0}'' must be greater than {1}. |
|
| 443 | 445 | optional=Дополнительно |
| 444 | 446 | pleaseEnterAValue=Введите, пожалуйста, значение. |
| 445 | 447 | latitude=Широта |
| ... | ... | @@ -511,6 +513,8 @@ sideToWhichMarkAtLegStartWasRounded=Отметка округленная до |
| 511 | 513 | sideToWhichMarkAtLegStartWasRoundedTooltip=Сторона в которую округляется отметка на старте этапа. |
| 512 | 514 | raceIsLive=Гонка {0} в прямом эфире |
| 513 | 515 | racesAreLive=Гонки {0} в прямом эфире |
| 516 | +regattaIsLive=Регата {0} в прямом эфире |
|
| 517 | +regattasAreLive=Регаты {0} в прямом эфире |
|
| 514 | 518 | knotsUnit=Узлы |
| 515 | 519 | knotsValue={0,number,#0.0} Узлы |
| 516 | 520 | knotsRange={0,number,#0.0} - {1,number,#0.0} Узлы |
| ... | ... | @@ -1340,4 +1344,6 @@ selectALeaderboardGroup=Select a leaderboard group... |
| 1340 | 1344 | pleaseSelect=Please select |
| 1341 | 1345 | requiresValidRegatta=This page requires a valid regatta, race column and fleet name to identify the race to show. |
| 1342 | 1346 | couldNotObtainRace=Could not obtain a race with name {1} for fleet {2} for a regatta with name {0}: {3} |
| 1343 | -errorTryingToCreateEmbeddedMap=Error trying to create the embedded map: {0} |
|
| ... | ... | \ No newline at end of file |
| 0 | +errorTryingToCreateEmbeddedMap=Error trying to create the embedded map: {0} |
|
| 1 | +transparentBufferLineOnHover=Transparent buffer lines on hover of helplines |
|
| 2 | +bufferLineStrokeWeight=Buffer line stroke weight |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties
| ... | ... | @@ -538,6 +538,7 @@ connectors=Connectors |
| 538 | 538 | dampeningInterval=Dampening Interval |
| 539 | 539 | trackBasedEstimationWindSourceTypeTooltip=Calculated data from the competitor tracks |
| 540 | 540 | valueMustBeBetweenMinMax=The value of ''{0}'' must be between {1} and {2}. |
| 541 | +valueMustBeGreaterThan=The value of ''{0}'' must be greater than {1}. |
|
| 541 | 542 | orMultipleEmails=or multiple E-Mails separated by comma |
| 542 | 543 | searchEvents=Search for events |
| 543 | 544 | explainNoConnectionsFromReplicas=No connections from a replica found. You can connect replicas by using their AdminConsole. |
| ... | ... | @@ -844,7 +845,10 @@ explainReplicationHostname=Hostname where the replication master is running on. |
| 844 | 845 | igtimiAccounts=Igtimi Accounts |
| 845 | 846 | windChart=Wind Chart |
| 846 | 847 | events=活动 |
| 848 | +raceIsLive=Race {0} is live |
|
| 847 | 849 | racesAreLive=Races {0} are live |
| 850 | +regattaIsLive=Regatta {0} is live |
|
| 851 | +regattasAreLive=Regattas {0} are live |
|
| 848 | 852 | operatorNotContains=Not Contains |
| 849 | 853 | windwardDistanceToCompetitorFarthestAheadInMetersTooltip=迎/顺风航段时离开上风/下风第一名的平行距离或横风时的实际距离name 名字 |
| 850 | 854 | errorAddingResultImportUrl=Error adding result import URL: {0} |
| ... | ... | @@ -1066,13 +1070,13 @@ polarSheetMinimumConfidenceMeasure=Minimum confidence measure |
| 1066 | 1070 | operator=operator |
| 1067 | 1071 | tractracUsername=TracTrac username |
| 1068 | 1072 | webWindSourceTypeName=Manual |
| 1069 | -raceIsLive=Race {0} is live |
|
| 1070 | 1073 | sapSailingAnalytics=航行分析 |
| 1071 | 1074 | trackWind=Track Wind |
| 1072 | 1075 | rawFixes=All fixes (including outliers) |
| 1073 | 1076 | showCompetitorFullNameColumn=Show Competitor Full Name |
| 1074 | 1077 | disableRaceFilter=Disable filter |
| 1075 | -simulateWithStartTimeNow=Simulate with "now" as start time |
|
| 1078 | +simulateAsLiveRace=Simulate as live race |
|
| 1079 | +simulateWithOffset=Offset before start in minutes: |
|
| 1076 | 1080 | polarSheetOutlierDetectionRadiusTooltip=The radius used in the distance based outlierndetection algorithm. |
| 1077 | 1081 | noWindFixesAvailable=No Wind-Fixes available. |
| 1078 | 1082 | correctScoreFor=Correct score for competitor {0} in race {1} |
| ... | ... | @@ -1355,4 +1359,6 @@ selectALeaderboardGroup=Select a leaderboard group... |
| 1355 | 1359 | pleaseSelect=Please select |
| 1356 | 1360 | requiresValidRegatta=This page requires a valid regatta, race column and fleet name to identify the race to show. |
| 1357 | 1361 | couldNotObtainRace=Could not obtain a race with name {1} for fleet {2} for a regatta with name {0}: {3} |
| 1358 | -errorTryingToCreateEmbeddedMap=Error trying to create the embedded map: {0} |
|
| ... | ... | \ No newline at end of file |
| 0 | +errorTryingToCreateEmbeddedMap=Error trying to create the embedded map: {0} |
|
| 1 | +transparentBufferLineOnHover=Transparent buffer lines on hover of helplines |
|
| 2 | +bufferLineStrokeWeight=Buffer line stroke weight |
|
| ... | ... | \ No newline at end of file |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.java
| ... | ... | @@ -89,7 +89,13 @@ public class TimePanel<T extends TimePanelSettings> extends SimplePanel implemen |
| 89 | 89 | private static ClientResources resources = GWT.create(ClientResources.class);
|
| 90 | 90 | protected static TimePanelCss timePanelCss = TimePanelCssResources.INSTANCE.css();
|
| 91 | 91 | |
| 92 | - public TimePanel(Timer timer, TimeRangeWithZoomProvider timeRangeProvider, StringMessages stringMessages, boolean canReplayWhileLiveIsPossible) {
|
|
| 92 | + /**
|
|
| 93 | + * @param isScreenLargeEnoughToOfferChartSupport
|
|
| 94 | + * if <code>true</code>, the right padding will be set such that the time panel lines up with charts such
|
|
| 95 | + * as the competitor chart or the wind chart shown above it
|
|
| 96 | + */
|
|
| 97 | + public TimePanel(Timer timer, TimeRangeWithZoomProvider timeRangeProvider, StringMessages stringMessages,
|
|
| 98 | + boolean canReplayWhileLiveIsPossible, boolean isScreenLargeEnoughToOfferChartSupport) {
|
|
| 93 | 99 | this.timer = timer;
|
| 94 | 100 | this.timeRangeProvider = timeRangeProvider;
|
| 95 | 101 | this.stringMessages = stringMessages;
|
| ... | ... | @@ -105,7 +111,9 @@ public class TimePanel<T extends TimePanelSettings> extends SimplePanel implemen |
| 105 | 111 | timePanelSliderFlowWrapper = new FlowPanel();
|
| 106 | 112 | timePanelSlider.setStyleName("timePanelSlider");
|
| 107 | 113 | timePanelSlider.getElement().getStyle().setPaddingLeft(66, Unit.PX);
|
| 108 | - timePanelSlider.getElement().getStyle().setPaddingRight(66, Unit.PX);
|
|
| 114 | + if (isScreenLargeEnoughToOfferChartSupport) {
|
|
| 115 | + timePanelSlider.getElement().getStyle().setPaddingRight(66, Unit.PX);
|
|
| 116 | + }
|
|
| 109 | 117 | timePanelSliderFlowWrapper.add(timePanelSlider);
|
| 110 | 118 | |
| 111 | 119 | playSpeedImg = resources.timesliderPlaySpeedIcon();
|
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/GalleryImageHolder.java
| ... | ... | @@ -10,7 +10,7 @@ import com.google.gwt.event.shared.HandlerRegistration; |
| 10 | 10 | import com.google.gwt.uibinder.client.UiBinder; |
| 11 | 11 | import com.google.gwt.uibinder.client.UiField; |
| 12 | 12 | import com.google.gwt.user.client.ui.Widget; |
| 13 | -import com.sap.sailing.gwt.home.communication.media.SailingImageDTO; |
|
| 13 | +import com.sap.sse.gwt.client.media.ImageDTO; |
|
| 14 | 14 | |
| 15 | 15 | public class GalleryImageHolder extends Widget implements HasClickHandlers { |
| 16 | 16 | |
| ... | ... | @@ -22,7 +22,7 @@ public class GalleryImageHolder extends Widget implements HasClickHandlers { |
| 22 | 22 | @UiField |
| 23 | 23 | DivElement imageHolderUi; |
| 24 | 24 | |
| 25 | - public GalleryImageHolder(SailingImageDTO video) { |
|
| 25 | + public GalleryImageHolder(ImageDTO video) { |
|
| 26 | 26 | setElement(uiBinder.createAndBindUi(this)); |
| 27 | 27 | imageHolderUi.getStyle().setBackgroundImage("url(\"" + video.getSourceRef() + "\")"); |
| 28 | 28 |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/VideoThumbnail.java
| ... | ... | @@ -11,9 +11,9 @@ import com.google.gwt.event.shared.HandlerRegistration; |
| 11 | 11 | import com.google.gwt.uibinder.client.UiBinder; |
| 12 | 12 | import com.google.gwt.uibinder.client.UiField; |
| 13 | 13 | import com.google.gwt.user.client.ui.Widget; |
| 14 | -import com.sap.sailing.gwt.home.communication.media.SailingVideoDTO; |
|
| 15 | 14 | import com.sap.sailing.gwt.ui.common.client.YoutubeApi; |
| 16 | 15 | import com.sap.sse.common.media.MediaSubType; |
| 16 | +import com.sap.sse.gwt.client.media.VideoDTO; |
|
| 17 | 17 | |
| 18 | 18 | public class VideoThumbnail extends Widget implements HasClickHandlers { |
| 19 | 19 | |
| ... | ... | @@ -28,7 +28,7 @@ public class VideoThumbnail extends Widget implements HasClickHandlers { |
| 28 | 28 | @UiField |
| 29 | 29 | ImageElement thumbnailUi; |
| 30 | 30 | |
| 31 | - public VideoThumbnail(SailingVideoDTO video) { |
|
| 31 | + public VideoThumbnail(VideoDTO video) { |
|
| 32 | 32 | setElement(uiBinder.createAndBindUi(this)); |
| 33 | 33 | getElement().addClassName("videoThumbnail"); |
| 34 | 34 | captionUi.setInnerText(video.getTitle()); |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/Hoverline.java
| ... | ... | @@ -0,0 +1,57 @@ |
| 1 | +package com.sap.sailing.gwt.ui.client.shared.racemap;
|
|
| 2 | +
|
|
| 3 | +import com.google.gwt.event.shared.HandlerRegistration;
|
|
| 4 | +import com.google.gwt.maps.client.events.click.ClickMapHandler;
|
|
| 5 | +import com.google.gwt.maps.client.events.mouseout.MouseOutMapEvent;
|
|
| 6 | +import com.google.gwt.maps.client.events.mouseout.MouseOutMapHandler;
|
|
| 7 | +import com.google.gwt.maps.client.events.mouseover.MouseOverMapEvent;
|
|
| 8 | +import com.google.gwt.maps.client.events.mouseover.MouseOverMapHandler;
|
|
| 9 | +import com.google.gwt.maps.client.overlays.Polyline;
|
|
| 10 | +import com.google.gwt.maps.client.overlays.PolylineOptions;
|
|
| 11 | +
|
|
| 12 | +public class Hoverline {
|
|
| 13 | + private static final double TRANSPARENT = 0;
|
|
| 14 | + private static final double VISIBLE = 0.2d;
|
|
| 15 | +
|
|
| 16 | + private final Polyline hoverline;
|
|
| 17 | + private final PolylineOptions options;
|
|
| 18 | +
|
|
| 19 | + public Hoverline(final Polyline polyline, PolylineOptions polylineOptions, final RaceMap map) {
|
|
| 20 | + this.options = PolylineOptions.newInstance();
|
|
| 21 | + this.options.setClickable(polylineOptions.getClickable());
|
|
| 22 | + this.options.setGeodesic(polylineOptions.getGeodesic());
|
|
| 23 | + this.options.setMap(polyline.getMap());
|
|
| 24 | + this.options.setPath(polyline.getPath());
|
|
| 25 | + this.options.setStrokeColor(polylineOptions.getStrokeColor());
|
|
| 26 | + this.options.setZindex(polylineOptions.getZindex());
|
|
| 27 | + this.hoverline = Polyline.newInstance(this.options);
|
|
| 28 | + this.hoverline.setVisible(false);
|
|
| 29 | + polyline.addMouseOverHandler(new MouseOverMapHandler() {
|
|
| 30 | + @Override
|
|
| 31 | + public void onEvent(MouseOverMapEvent event) {
|
|
| 32 | + options.setStrokeOpacity(map.getSettings().getTransparentHoverlines() ? TRANSPARENT : VISIBLE);
|
|
| 33 | + options.setStrokeWeight(map.getSettings().getHoverlineStrokeWeight());
|
|
| 34 | + options.setVisible(true);
|
|
| 35 | + hoverline.setOptions(options);
|
|
| 36 | + }
|
|
| 37 | + });
|
|
| 38 | + hoverline.addMouseOutMoveHandler(new MouseOutMapHandler() {
|
|
| 39 | + @Override
|
|
| 40 | + public void onEvent(MouseOutMapEvent event) {
|
|
| 41 | + hoverline.setVisible(false);
|
|
| 42 | + }
|
|
| 43 | + });
|
|
| 44 | + }
|
|
| 45 | +
|
|
| 46 | + public HandlerRegistration addClickHandler(ClickMapHandler handler) {
|
|
| 47 | + return this.hoverline.addClickHandler(handler);
|
|
| 48 | + }
|
|
| 49 | +
|
|
| 50 | + public HandlerRegistration addMouseOutMoveHandler(MouseOutMapHandler handler) {
|
|
| 51 | + return this.hoverline.addMouseOutMoveHandler(handler);
|
|
| 52 | + }
|
|
| 53 | +
|
|
| 54 | + public HandlerRegistration addMouseOverHandler(MouseOverMapHandler handler) {
|
|
| 55 | + return this.hoverline.addMouseOverHandler(handler);
|
|
| 56 | + }
|
|
| 57 | +} |
|
| ... | ... | \ No newline at end of file |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java
| ... | ... | @@ -1,2601 +1,2636 @@ |
| 1 | -package com.sap.sailing.gwt.ui.client.shared.racemap;
|
|
| 2 | -
|
|
| 3 | -import java.util.ArrayList;
|
|
| 4 | -import java.util.Collection;
|
|
| 5 | -import java.util.Collections;
|
|
| 6 | -import java.util.Comparator;
|
|
| 7 | -import java.util.Date;
|
|
| 8 | -import java.util.HashMap;
|
|
| 9 | -import java.util.HashSet;
|
|
| 10 | -import java.util.Iterator;
|
|
| 11 | -import java.util.LinkedHashMap;
|
|
| 12 | -import java.util.List;
|
|
| 13 | -import java.util.Map;
|
|
| 14 | -import java.util.Map.Entry;
|
|
| 15 | -import java.util.Set;
|
|
| 16 | -
|
|
| 17 | -import com.google.gwt.canvas.dom.client.CssColor;
|
|
| 18 | -import com.google.gwt.core.client.GWT;
|
|
| 19 | -import com.google.gwt.core.client.Scheduler;
|
|
| 20 | -import com.google.gwt.core.client.Scheduler.ScheduledCommand;
|
|
| 21 | -import com.google.gwt.dom.client.Style;
|
|
| 22 | -import com.google.gwt.dom.client.Style.Unit;
|
|
| 23 | -import com.google.gwt.event.dom.client.ClickEvent;
|
|
| 24 | -import com.google.gwt.event.dom.client.ClickHandler;
|
|
| 25 | -import com.google.gwt.i18n.client.NumberFormat;
|
|
| 26 | -import com.google.gwt.maps.client.LoadApi;
|
|
| 27 | -import com.google.gwt.maps.client.LoadApi.LoadLibrary;
|
|
| 28 | -import com.google.gwt.maps.client.MapOptions;
|
|
| 29 | -import com.google.gwt.maps.client.MapWidget;
|
|
| 30 | -import com.google.gwt.maps.client.base.LatLng;
|
|
| 31 | -import com.google.gwt.maps.client.base.LatLngBounds;
|
|
| 32 | -import com.google.gwt.maps.client.controls.ControlPosition;
|
|
| 33 | -import com.google.gwt.maps.client.controls.MapTypeControlOptions;
|
|
| 34 | -import com.google.gwt.maps.client.controls.MapTypeStyle;
|
|
| 35 | -import com.google.gwt.maps.client.controls.PanControlOptions;
|
|
| 36 | -import com.google.gwt.maps.client.controls.ZoomControlOptions;
|
|
| 37 | -import com.google.gwt.maps.client.events.bounds.BoundsChangeMapEvent;
|
|
| 38 | -import com.google.gwt.maps.client.events.bounds.BoundsChangeMapHandler;
|
|
| 39 | -import com.google.gwt.maps.client.events.click.ClickMapEvent;
|
|
| 40 | -import com.google.gwt.maps.client.events.click.ClickMapHandler;
|
|
| 41 | -import com.google.gwt.maps.client.events.dragend.DragEndMapEvent;
|
|
| 42 | -import com.google.gwt.maps.client.events.dragend.DragEndMapHandler;
|
|
| 43 | -import com.google.gwt.maps.client.events.idle.IdleMapEvent;
|
|
| 44 | -import com.google.gwt.maps.client.events.idle.IdleMapHandler;
|
|
| 45 | -import com.google.gwt.maps.client.events.mouseout.MouseOutMapEvent;
|
|
| 46 | -import com.google.gwt.maps.client.events.mouseout.MouseOutMapHandler;
|
|
| 47 | -import com.google.gwt.maps.client.events.mouseover.MouseOverMapEvent;
|
|
| 48 | -import com.google.gwt.maps.client.events.mouseover.MouseOverMapHandler;
|
|
| 49 | -import com.google.gwt.maps.client.events.zoom.ZoomChangeMapEvent;
|
|
| 50 | -import com.google.gwt.maps.client.events.zoom.ZoomChangeMapHandler;
|
|
| 51 | -import com.google.gwt.maps.client.maptypes.MapTypeStyleFeatureType;
|
|
| 52 | -import com.google.gwt.maps.client.mvc.MVCArray;
|
|
| 53 | -import com.google.gwt.maps.client.overlays.InfoWindow;
|
|
| 54 | -import com.google.gwt.maps.client.overlays.InfoWindowOptions;
|
|
| 55 | -import com.google.gwt.maps.client.overlays.Marker;
|
|
| 56 | -import com.google.gwt.maps.client.overlays.MarkerOptions;
|
|
| 57 | -import com.google.gwt.maps.client.overlays.Polygon;
|
|
| 58 | -import com.google.gwt.maps.client.overlays.PolygonOptions;
|
|
| 59 | -import com.google.gwt.maps.client.overlays.Polyline;
|
|
| 60 | -import com.google.gwt.maps.client.overlays.PolylineOptions;
|
|
| 61 | -import com.google.gwt.resources.client.ImageResource;
|
|
| 62 | -import com.google.gwt.user.client.Window;
|
|
| 63 | -import com.google.gwt.user.client.rpc.AsyncCallback;
|
|
| 64 | -import com.google.gwt.user.client.ui.AbsolutePanel;
|
|
| 65 | -import com.google.gwt.user.client.ui.Button;
|
|
| 66 | -import com.google.gwt.user.client.ui.FlowPanel;
|
|
| 67 | -import com.google.gwt.user.client.ui.Image;
|
|
| 68 | -import com.google.gwt.user.client.ui.Label;
|
|
| 69 | -import com.google.gwt.user.client.ui.RequiresResize;
|
|
| 70 | -import com.google.gwt.user.client.ui.VerticalPanel;
|
|
| 71 | -import com.google.gwt.user.client.ui.Widget;
|
|
| 72 | -import com.sap.sailing.domain.common.Bearing;
|
|
| 73 | -import com.sap.sailing.domain.common.Bounds;
|
|
| 74 | -import com.sap.sailing.domain.common.ManeuverType;
|
|
| 75 | -import com.sap.sailing.domain.common.Position;
|
|
| 76 | -import com.sap.sailing.domain.common.RaceIdentifier;
|
|
| 77 | -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
|
| 78 | -import com.sap.sailing.domain.common.Tack;
|
|
| 79 | -import com.sap.sailing.domain.common.WindSource;
|
|
| 80 | -import com.sap.sailing.domain.common.WindSourceType;
|
|
| 81 | -import com.sap.sailing.domain.common.dto.CompetitorDTO;
|
|
| 82 | -import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
|
|
| 83 | -import com.sap.sailing.domain.common.impl.DegreePosition;
|
|
| 84 | -import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing;
|
|
| 85 | -import com.sap.sailing.domain.common.scalablevalue.impl.ScalablePosition;
|
|
| 86 | -import com.sap.sailing.gwt.ui.actions.GetPolarAction;
|
|
| 87 | -import com.sap.sailing.gwt.ui.actions.GetRaceMapDataAction;
|
|
| 88 | -import com.sap.sailing.gwt.ui.actions.GetWindInfoAction;
|
|
| 89 | -import com.sap.sailing.gwt.ui.client.ClientResources;
|
|
| 90 | -import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener;
|
|
| 91 | -import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider;
|
|
| 92 | -import com.sap.sailing.gwt.ui.client.NumberFormatterFactory;
|
|
| 93 | -import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener;
|
|
| 94 | -import com.sap.sailing.gwt.ui.client.RaceTimesInfoProviderListener;
|
|
| 95 | -import com.sap.sailing.gwt.ui.client.RequiresDataInitialization;
|
|
| 96 | -import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
|
| 97 | -import com.sap.sailing.gwt.ui.client.StringMessages;
|
|
| 98 | -import com.sap.sailing.gwt.ui.client.WindSourceTypeFormatter;
|
|
| 99 | -import com.sap.sailing.gwt.ui.client.shared.filter.QuickRankProvider;
|
|
| 100 | -import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapHelpLinesSettings.HelpLineTypes;
|
|
| 101 | -import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings.ZoomTypes;
|
|
| 102 | -import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil;
|
|
| 103 | -import com.sap.sailing.gwt.ui.shared.ControlPointDTO;
|
|
| 104 | -import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO;
|
|
| 105 | -import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
|
|
| 106 | -import com.sap.sailing.gwt.ui.shared.LegInfoDTO;
|
|
| 107 | -import com.sap.sailing.gwt.ui.shared.ManeuverDTO;
|
|
| 108 | -import com.sap.sailing.gwt.ui.shared.MarkDTO;
|
|
| 109 | -import com.sap.sailing.gwt.ui.shared.QuickRankDTO;
|
|
| 110 | -import com.sap.sailing.gwt.ui.shared.RaceCourseDTO;
|
|
| 111 | -import com.sap.sailing.gwt.ui.shared.RaceMapDataDTO;
|
|
| 112 | -import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
|
|
| 113 | -import com.sap.sailing.gwt.ui.shared.SidelineDTO;
|
|
| 114 | -import com.sap.sailing.gwt.ui.shared.SpeedWithBearingDTO;
|
|
| 115 | -import com.sap.sailing.gwt.ui.shared.WaypointDTO;
|
|
| 116 | -import com.sap.sailing.gwt.ui.shared.WindDTO;
|
|
| 117 | -import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO;
|
|
| 118 | -import com.sap.sailing.gwt.ui.shared.WindTrackInfoDTO;
|
|
| 119 | -import com.sap.sailing.gwt.ui.shared.racemap.GoogleMapAPIKey;
|
|
| 120 | -import com.sap.sailing.gwt.ui.shared.racemap.GoogleMapStyleHelper;
|
|
| 121 | -import com.sap.sailing.gwt.ui.shared.racemap.RaceSimulationOverlay;
|
|
| 122 | -import com.sap.sailing.gwt.ui.shared.racemap.WindStreamletsRaceboardOverlay;
|
|
| 123 | -import com.sap.sse.common.Util;
|
|
| 124 | -import com.sap.sse.common.Util.Pair;
|
|
| 125 | -import com.sap.sse.common.Util.Triple;
|
|
| 126 | -import com.sap.sse.common.filter.Filter;
|
|
| 127 | -import com.sap.sse.common.filter.FilterSet;
|
|
| 128 | -import com.sap.sse.common.impl.RGBColor;
|
|
| 129 | -import com.sap.sse.gwt.client.ErrorReporter;
|
|
| 130 | -import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
|
|
| 131 | -import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
|
|
| 132 | -import com.sap.sse.gwt.client.player.TimeListener;
|
|
| 133 | -import com.sap.sse.gwt.client.player.Timer;
|
|
| 134 | -import com.sap.sse.gwt.client.player.Timer.PlayModes;
|
|
| 135 | -import com.sap.sse.gwt.client.player.Timer.PlayStates;
|
|
| 136 | -import com.sap.sse.gwt.client.shared.components.Component;
|
|
| 137 | -import com.sap.sse.gwt.client.shared.components.SettingsDialog;
|
|
| 138 | -import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent;
|
|
| 139 | -
|
|
| 140 | -public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSelectionChangeListener, RaceSelectionChangeListener,
|
|
| 141 | - RaceTimesInfoProviderListener, TailFactory, Component<RaceMapSettings>, RequiresDataInitialization, RequiresResize, QuickRankProvider {
|
|
| 142 | - public static final String GET_RACE_MAP_DATA_CATEGORY = "getRaceMapData";
|
|
| 143 | - public static final String GET_WIND_DATA_CATEGORY = "getWindData";
|
|
| 144 | -
|
|
| 145 | - private MapWidget map;
|
|
| 146 | -
|
|
| 147 | - /**
|
|
| 148 | - * Always valid, non-<code>null</code>. Must be used to map all coordinates, headings, bearings, and directions
|
|
| 149 | - * displayed on the map, including the orientations of any canvases such as boat icons, wind displays etc. that are
|
|
| 150 | - * embedded in the map. The coordinate systems facilitates the possibility of transformed displays such as
|
|
| 151 | - * rotated and translated versions of the map, implementing the "wind-up" view.
|
|
| 152 | - */
|
|
| 153 | - private DelegateCoordinateSystem coordinateSystem;
|
|
| 154 | -
|
|
| 155 | - private FlowPanel headerPanel;
|
|
| 156 | - private AbsolutePanel panelForLeftHeaderLabels;
|
|
| 157 | - private AbsolutePanel panelForRightHeaderLabels;
|
|
| 158 | -
|
|
| 159 | - private final SailingServiceAsync sailingService;
|
|
| 160 | - private final ErrorReporter errorReporter;
|
|
| 161 | -
|
|
| 162 | - private final static ClientResources resources = GWT.create(ClientResources.class);
|
|
| 163 | -
|
|
| 164 | - /**
|
|
| 165 | - * Polyline for the start line (connecting two marks representing the start gate).
|
|
| 166 | - */
|
|
| 167 | - private Polyline startLine;
|
|
| 168 | -
|
|
| 169 | - /**
|
|
| 170 | - * Polyline for the finish line (connecting two marks representing the finish gate).
|
|
| 171 | - */
|
|
| 172 | - private Polyline finishLine;
|
|
| 173 | -
|
|
| 174 | - /**
|
|
| 175 | - * Polyline for the advantage line (the leading line for the boats, orthogonal to the wind direction; touching the leading boat).
|
|
| 176 | - */
|
|
| 177 | - private Polyline advantageLine;
|
|
| 178 | -
|
|
| 179 | - /**
|
|
| 180 | - * The windward of two Polylines representing a triangle between startline and first mark.
|
|
| 181 | - */
|
|
| 182 | - private Polyline windwardStartLineMarkToFirstMarkLine;
|
|
| 183 | -
|
|
| 184 | - /**
|
|
| 185 | - * The leeward of two Polylines representing a triangle between startline and first mark.
|
|
| 186 | - */
|
|
| 187 | - private Polyline leewardStartLineMarkToFirstMarkLine;
|
|
| 188 | -
|
|
| 189 | - private class AdvantageLineMouseOverMapHandler implements MouseOverMapHandler {
|
|
| 190 | - private double trueWindAngle;
|
|
| 191 | - private Date date;
|
|
| 192 | -
|
|
| 193 | - public AdvantageLineMouseOverMapHandler(double trueWindAngle, Date date) {
|
|
| 194 | - this.trueWindAngle = trueWindAngle;
|
|
| 195 | - this.date = date;
|
|
| 196 | - }
|
|
| 197 | -
|
|
| 198 | - public void setTrueWindBearing(double trueWindAngle) {
|
|
| 199 | - this.trueWindAngle = trueWindAngle;
|
|
| 200 | - }
|
|
| 201 | -
|
|
| 202 | - public void setDate(Date date) {
|
|
| 203 | - this.date = date;
|
|
| 204 | - }
|
|
| 205 | -
|
|
| 206 | - @Override
|
|
| 207 | - public void onEvent(MouseOverMapEvent event) {
|
|
| 208 | - map.setTitle(stringMessages.advantageLine()+" (from "+new DegreeBearingImpl(Math.round(trueWindAngle)).reverse().getDegrees()+"deg"+
|
|
| 209 | - (date == null ? ")" : ", "+ date) + ")");
|
|
| 210 | - }
|
|
| 211 | - };
|
|
| 212 | -
|
|
| 213 | - private AdvantageLineMouseOverMapHandler advantageLineMouseOverHandler;
|
|
| 214 | -
|
|
| 215 | - /**
|
|
| 216 | - * Polylines for the course middle lines; keys are the two control points delimiting the leg for which the
|
|
| 217 | - * {@link Polyline} value shows the course middle line. As only one course middle line is shown even if there
|
|
| 218 | - * are multiple legs using the same control points in different directions, using a {@link Set} makes this
|
|
| 219 | - * independent of the order of the two control points. If no course middle line is currently being shown for
|
|
| 220 | - * a pair of control points, the map will not contain a value for this pair.
|
|
| 221 | - */
|
|
| 222 | - private final Map<Set<ControlPointDTO>, Polyline> courseMiddleLines;
|
|
| 223 | -
|
|
| 224 | - private final Map<SidelineDTO, Polygon> courseSidelines;
|
|
| 225 | -
|
|
| 226 | - /**
|
|
| 227 | - * When the {@link HelpLineTypes#COURSEGEOMETRY} option is selected, little markers will be displayed on the
|
|
| 228 | - * lines that show the tooltip text in a little info box linked to the line. When the line is removed by
|
|
| 229 | - * {@link #showOrRemoveOrUpdateLine(Polyline, boolean, Position, Position, LineInfoProvider, String)}, these
|
|
| 230 | - * overlays need to be removed as well. Also, when the {@link HelpLineTypes#COURSEGEOMETRY} setting is
|
|
| 231 | - * deactivated, all these overlays need to go.
|
|
| 232 | - */
|
|
| 233 | - private final Map<Polyline, SmallTransparentInfoOverlay> infoOverlaysForLinesForCourseGeometry;
|
|
| 234 | -
|
|
| 235 | - /**
|
|
| 236 | - * Wind data used to display the advantage line. Retrieved by a {@link GetWindInfoAction} execution and used in
|
|
| 237 | - * {@link #showAdvantageLine(Iterable, Date)}.
|
|
| 238 | - */
|
|
| 239 | - private WindInfoForRaceDTO lastCombinedWindTrackInfoDTO;
|
|
| 240 | -
|
|
| 241 | - /**
|
|
| 242 | - * Manages the cached set of {@link GPSFixDTO}s for the boat positions as well as their graphical counterpart in the
|
|
| 243 | - * form of {@link Polyline}s.
|
|
| 244 | - */
|
|
| 245 | - private final FixesAndTails fixesAndTails;
|
|
| 246 | -
|
|
| 247 | - /**
|
|
| 248 | - * html5 canvases used as boat display on the map
|
|
| 249 | - */
|
|
| 250 | - private final Map<CompetitorDTO, BoatOverlay> boatOverlays;
|
|
| 251 | -
|
|
| 252 | - /**
|
|
| 253 | - * html5 canvases used for competitor info display on the map
|
|
| 254 | - */
|
|
| 255 | - private final Map<CompetitorDTO, CompetitorInfoOverlay> competitorInfoOverlays;
|
|
| 256 | -
|
|
| 257 | - private SmallTransparentInfoOverlay countDownOverlay;
|
|
| 258 | -
|
|
| 259 | - /**
|
|
| 260 | - * Map overlays with html5 canvas used to display wind sensors
|
|
| 261 | - */
|
|
| 262 | - private final Map<WindSource, WindSensorOverlay> windSensorOverlays;
|
|
| 263 | -
|
|
| 264 | - /**
|
|
| 265 | - * Map overlays with html5 canvas used to display course marks including buoy zones
|
|
| 266 | - */
|
|
| 267 | - private final Map<String, CourseMarkOverlay> courseMarkOverlays;
|
|
| 268 | -
|
|
| 269 | - private final Map<String, MarkDTO> markDTOs;
|
|
| 270 | -
|
|
| 271 | - /**
|
|
| 272 | - * markers displayed in response to
|
|
| 273 | - * {@link SailingServiceAsync#getDouglasPoints(String, String, Map, Map, double, AsyncCallback)}
|
|
| 274 | - */
|
|
| 275 | - protected Set<Marker> douglasMarkers;
|
|
| 276 | -
|
|
| 277 | - /**
|
|
| 278 | - * markers displayed in response to
|
|
| 279 | - * {@link SailingServiceAsync#getDouglasPoints(String, String, Map, Map, double, AsyncCallback)}
|
|
| 280 | - */
|
|
| 281 | - private Set<Marker> maneuverMarkers;
|
|
| 282 | -
|
|
| 283 | - private Map<CompetitorDTO, List<ManeuverDTO>> lastManeuverResult;
|
|
| 284 | -
|
|
| 285 | - private Map<CompetitorDTO, List<GPSFixDTO>> lastDouglasPeuckerResult;
|
|
| 286 | -
|
|
| 287 | - private final CompetitorSelectionProvider competitorSelection;
|
|
| 288 | -
|
|
| 289 | - private List<RegattaAndRaceIdentifier> selectedRaces;
|
|
| 290 | -
|
|
| 291 | - /**
|
|
| 292 | - * Used to check if the first initial zoom to the mark markers was already done.
|
|
| 293 | - */
|
|
| 294 | - private boolean mapFirstZoomDone = false;
|
|
| 295 | -
|
|
| 296 | - private final Timer timer;
|
|
| 297 | -
|
|
| 298 | - private RaceTimesInfoDTO lastRaceTimesInfo;
|
|
| 299 | -
|
|
| 300 | - private InfoWindow lastInfoWindow = null;
|
|
| 301 | -
|
|
| 302 | - /**
|
|
| 303 | - * RPC calls may receive responses out of order if there are multiple calls in-flight at the same time. If the time
|
|
| 304 | - * slider is moved quickly it generates many requests for boat positions quickly after each other. Sometimes,
|
|
| 305 | - * responses for requests send later may return before the responses to all earlier requests have been received and
|
|
| 306 | - * processed. This counter is used to number the requests. When processing of a response for a later request has
|
|
| 307 | - * already begun, responses to earlier requests will be ignored.
|
|
| 308 | - */
|
|
| 309 | - private int boatPositionRequestIDCounter;
|
|
| 310 | -
|
|
| 311 | - /**
|
|
| 312 | - * Corresponds to {@link #boatPositionRequestIDCounter}. As soon as the processing of a response for a request ID
|
|
| 313 | - * begins, this attribute is set to the ID. A response won't be processed if a later response is already being
|
|
| 314 | - * processed.
|
|
| 315 | - */
|
|
| 316 | - private int startedProcessingRequestID;
|
|
| 317 | -
|
|
| 318 | - private RaceMapImageManager raceMapImageManager;
|
|
| 319 | -
|
|
| 320 | - private final RaceMapSettings settings;
|
|
| 321 | -
|
|
| 322 | - private final StringMessages stringMessages;
|
|
| 323 | -
|
|
| 324 | - private boolean isMapInitialized;
|
|
| 325 | -
|
|
| 326 | - private Date lastTimeChangeBeforeInitialization;
|
|
| 327 | -
|
|
| 328 | - private int lastLegNumber;
|
|
| 329 | -
|
|
| 330 | - /**
|
|
| 331 | - * The last quick ranks received from a call to {@link SailingServiceAsync#getQuickRanks(RaceIdentifier, Date, AsyncCallback)} upon
|
|
| 332 | - * the last {@link #timeChanged(Date, Date)} event. Therefore, the ranks listed here correspond to the {@link #timer}'s time.
|
|
| 333 | - */
|
|
| 334 | - private LinkedHashMap<CompetitorDTO, QuickRankDTO> quickRanks;
|
|
| 335 | -
|
|
| 336 | - /**
|
|
| 337 | - * Taken from {@link RaceMapDataDTO#competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber}; tells the
|
|
| 338 | - * windward distances traveled and the leg numbers in which the respective competitor is.
|
|
| 339 | - */
|
|
| 340 | - private LinkedHashMap<CompetitorDTO, Integer> competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber;
|
|
| 341 | -
|
|
| 342 | -
|
|
| 343 | - private final CombinedWindPanel combinedWindPanel;
|
|
| 344 | -
|
|
| 345 | - private final TrueNorthIndicatorPanel trueNorthIndicatorPanel;
|
|
| 346 | -
|
|
| 347 | - private final AsyncActionsExecutor asyncActionsExecutor;
|
|
| 348 | -
|
|
| 349 | - /**
|
|
| 350 | - * The map bounds as last received by map callbacks; used to determine whether to suppress the boat animation during zoom/pan
|
|
| 351 | - */
|
|
| 352 | - private LatLngBounds currentMapBounds; // bounds to which bounds-changed-handler compares
|
|
| 353 | - private int currentZoomLevel; // zoom-level to which bounds-changed-handler compares
|
|
| 354 | -
|
|
| 355 | - private boolean autoZoomIn = false; // flags auto-zoom-in in progress
|
|
| 356 | - private boolean autoZoomOut = false; // flags auto-zoom-out in progress
|
|
| 357 | - private int autoZoomLevel; // zoom-level to which auto-zoom-in/-out is zooming
|
|
| 358 | - LatLngBounds autoZoomLatLngBounds; // bounds to which auto-zoom-in/-out is panning&zooming
|
|
| 359 | -
|
|
| 360 | - private RaceSimulationOverlay simulationOverlay;
|
|
| 361 | - private WindStreamletsRaceboardOverlay streamletOverlay;
|
|
| 362 | - private final boolean showViewStreamlets;
|
|
| 363 | - private final boolean showViewStreamletColors;
|
|
| 364 | - private final boolean showViewSimulation;
|
|
| 365 | -
|
|
| 366 | - private static final String GET_POLAR_CATEGORY = "getPolar";
|
|
| 367 | -
|
|
| 368 | - /**
|
|
| 369 | - * Tells about the availability of polar / VPP data for this race. If available, the simulation feature can be
|
|
| 370 | - * offered to the user.
|
|
| 371 | - */
|
|
| 372 | - private boolean hasPolar;
|
|
| 373 | -
|
|
| 374 | - private final RegattaAndRaceIdentifier raceIdentifier;
|
|
| 375 | -
|
|
| 376 | - /**
|
|
| 377 | - * When the user requests wind-up display this may happen at a point where no mark positions are known or when
|
|
| 378 | - * no wind direction is known yet. In this case, this flag will be set, and when wind information or course mark
|
|
| 379 | - * positions are received later, this flag is checked, and if set, a {@link #updateCoordinateSystemFromSettings()}
|
|
| 380 | - * call is issued to make sure that the user's request for a new coordinate system is honored.
|
|
| 381 | - */
|
|
| 382 | - private boolean requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown;
|
|
| 383 | -
|
|
| 384 | - private final boolean showMapControls;
|
|
| 385 | -
|
|
| 386 | - /**
|
|
| 387 | - * Tells whether currently an auto-zoom is in progress; this is used particularly to keep the smooth CSS boat transitions
|
|
| 388 | - * active while auto-zooming whereas stopping them seems the better option for manual zooms.
|
|
| 389 | - */
|
|
| 390 | - private boolean autoZoomInProgress;
|
|
| 391 | -
|
|
| 392 | - /**
|
|
| 393 | - * Tells whether currently an orientation change is in progress; this is required handle map events during the configuration of the map
|
|
| 394 | - * during an orientation change.
|
|
| 395 | - */
|
|
| 396 | - private boolean orientationChangeInProgress;
|
|
| 397 | -
|
|
| 398 | - private final NumberFormat numberFormatOneDecimal = NumberFormat.getFormat("0.0");
|
|
| 399 | -
|
|
| 400 | - public RaceMap(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor,
|
|
| 401 | - ErrorReporter errorReporter, Timer timer, CompetitorSelectionProvider competitorSelection, StringMessages stringMessages,
|
|
| 402 | - boolean showMapControls, boolean showViewStreamlets, boolean showViewStreamletColors, boolean showViewSimulation,
|
|
| 403 | - RegattaAndRaceIdentifier raceIdentifier, CombinedWindPanelStyle combinedWindPanelStyle, boolean showHeaderPanel) {
|
|
| 404 | - this.setSize("100%", "100%");
|
|
| 405 | - this.showMapControls = showMapControls;
|
|
| 406 | - this.stringMessages = stringMessages;
|
|
| 407 | - this.sailingService = sailingService;
|
|
| 408 | - this.raceIdentifier = raceIdentifier;
|
|
| 409 | - this.asyncActionsExecutor = asyncActionsExecutor;
|
|
| 410 | - this.errorReporter = errorReporter;
|
|
| 411 | - this.timer = timer;
|
|
| 412 | - timer.addTimeListener(this);
|
|
| 413 | - raceMapImageManager = new RaceMapImageManager();
|
|
| 414 | - markDTOs = new HashMap<String, MarkDTO>();
|
|
| 415 | - courseSidelines = new HashMap<>();
|
|
| 416 | - courseMiddleLines = new HashMap<>();
|
|
| 417 | - infoOverlaysForLinesForCourseGeometry = new HashMap<>();
|
|
| 418 | - boatOverlays = new HashMap<CompetitorDTO, BoatOverlay>();
|
|
| 419 | - competitorInfoOverlays = new HashMap<CompetitorDTO, CompetitorInfoOverlay>();
|
|
| 420 | - windSensorOverlays = new HashMap<WindSource, WindSensorOverlay>();
|
|
| 421 | - courseMarkOverlays = new HashMap<String, CourseMarkOverlay>();
|
|
| 422 | - this.competitorSelection = competitorSelection;
|
|
| 423 | - competitorSelection.addCompetitorSelectionChangeListener(this);
|
|
| 424 | - settings = new RaceMapSettings();
|
|
| 425 | - coordinateSystem = new DelegateCoordinateSystem(new IdentityCoordinateSystem());
|
|
| 426 | - fixesAndTails = new FixesAndTails(coordinateSystem);
|
|
| 427 | - updateCoordinateSystemFromSettings();
|
|
| 428 | - lastTimeChangeBeforeInitialization = null;
|
|
| 429 | - isMapInitialized = false;
|
|
| 430 | - this.showViewStreamlets = showViewStreamlets;
|
|
| 431 | - this.showViewStreamletColors = showViewStreamletColors;
|
|
| 432 | - this.showViewSimulation = showViewSimulation;
|
|
| 433 | - this.hasPolar = false;
|
|
| 434 | - headerPanel = new FlowPanel();
|
|
| 435 | - headerPanel.setStyleName("RaceMap-HeaderPanel");
|
|
| 436 | - panelForLeftHeaderLabels = new AbsolutePanel();
|
|
| 437 | - panelForRightHeaderLabels = new AbsolutePanel();
|
|
| 438 | - initializeData(showMapControls, showHeaderPanel);
|
|
| 439 | - combinedWindPanel = new CombinedWindPanel(raceMapImageManager, combinedWindPanelStyle, stringMessages, coordinateSystem);
|
|
| 440 | - combinedWindPanel.setVisible(false);
|
|
| 441 | - trueNorthIndicatorPanel = new TrueNorthIndicatorPanel(this, raceMapImageManager, combinedWindPanelStyle, stringMessages, coordinateSystem);
|
|
| 442 | - trueNorthIndicatorPanel.setVisible(true);
|
|
| 443 | - orientationChangeInProgress = false;
|
|
| 444 | - }
|
|
| 445 | -
|
|
| 446 | - /**
|
|
| 447 | - * The {@link WindDTO#dampenedTrueWindFromDeg} direction if {@link #lastCombinedWindTrackInfoDTO} has a
|
|
| 448 | - * {@link WindSourceType#COMBINED} source which has at least one fix recorded; <code>null</code> otherwise.
|
|
| 449 | - */
|
|
| 450 | - private Bearing getLastCombinedTrueWindFromDirection() {
|
|
| 451 | - if (lastCombinedWindTrackInfoDTO != null) {
|
|
| 452 | - for (Entry<WindSource, WindTrackInfoDTO> e : lastCombinedWindTrackInfoDTO.windTrackInfoByWindSource.entrySet()) {
|
|
| 453 | - if (e.getKey().getType() == WindSourceType.COMBINED) {
|
|
| 454 | - final List<WindDTO> windFixes = e.getValue().windFixes;
|
|
| 455 | - if (!windFixes.isEmpty()) {
|
|
| 456 | - return new DegreeBearingImpl(windFixes.get(0).dampenedTrueWindFromDeg);
|
|
| 457 | - }
|
|
| 458 | - }
|
|
| 459 | - }
|
|
| 460 | - }
|
|
| 461 | - return null;
|
|
| 462 | - }
|
|
| 463 | -
|
|
| 464 | - private void updateCoordinateSystemFromSettings() {
|
|
| 465 | - final MapOptions mapOptions;
|
|
| 466 | - orientationChangeInProgress = true;
|
|
| 467 | - if (getSettings().isWindUp()) {
|
|
| 468 | - final Position centerOfCourse = getCenterOfCourse();
|
|
| 469 | - if (centerOfCourse != null) {
|
|
| 470 | - final Bearing lastCombinedTrueWindFromDirection = getLastCombinedTrueWindFromDirection();
|
|
| 471 | - if (lastCombinedTrueWindFromDirection != null) {
|
|
| 472 | - // new equator shall point 90deg right of the "from" wind direction to make wind come from top of map
|
|
| 473 | - coordinateSystem.setCoordinateSystem(new RotateAndTranslateCoordinateSystem(centerOfCourse,
|
|
| 474 | - lastCombinedTrueWindFromDirection.add(new DegreeBearingImpl(90))));
|
|
| 475 | - if (map != null) {
|
|
| 476 | - mapOptions = getMapOptions(showMapControls, /* wind-up */ true);
|
|
| 477 | - } else {
|
|
| 478 | - mapOptions = null;
|
|
| 479 | - }
|
|
| 480 | - requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown = false;
|
|
| 481 | - } else {
|
|
| 482 | - // register callback in case center of course and wind info becomes known
|
|
| 483 | - requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown = true;
|
|
| 484 | - mapOptions = null;
|
|
| 485 | - }
|
|
| 486 | - } else {
|
|
| 487 | - // register callback in case center of course and wind info becomes known
|
|
| 488 | - requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown = true;
|
|
| 489 | - mapOptions = null;
|
|
| 490 | - }
|
|
| 491 | - } else {
|
|
| 492 | - if (map != null) {
|
|
| 493 | - mapOptions = getMapOptions(showMapControls, /* wind-up */ false);
|
|
| 494 | - } else {
|
|
| 495 | - mapOptions = null;
|
|
| 496 | - }
|
|
| 497 | - coordinateSystem.setCoordinateSystem(new IdentityCoordinateSystem());
|
|
| 498 | - }
|
|
| 499 | - if (mapOptions != null) { // if no coordinate system change happened that affects an existing map, don't redraw
|
|
| 500 | - fixesAndTails.clearTails();
|
|
| 501 | - redraw();
|
|
| 502 | - // zooming and setting options while the event loop is still working doesn't work reliably; defer until event loop returns
|
|
| 503 | - Scheduler.get().scheduleDeferred(new ScheduledCommand() {
|
|
| 504 | - @Override
|
|
| 505 | - public void execute() {
|
|
| 506 | - if (map != null) {
|
|
| 507 | - map.setOptions(mapOptions);
|
|
| 508 | - // ensure zooming to what the settings tell, or defaults if what the settings tell isn't possible right now
|
|
| 509 | - mapFirstZoomDone = false;
|
|
| 510 | - trueNorthIndicatorPanel.redraw();
|
|
| 511 | - orientationChangeInProgress = false;
|
|
| 512 | - }
|
|
| 513 | - }
|
|
| 514 | - });
|
|
| 515 | - }
|
|
| 516 | - }
|
|
| 517 | -
|
|
| 518 | - private void loadMapsAPIV3(final boolean showMapControls, final boolean showHeaderPanel) {
|
|
| 519 | - boolean sensor = true;
|
|
| 520 | -
|
|
| 521 | - // load all the libs for use in the maps
|
|
| 522 | - ArrayList<LoadLibrary> loadLibraries = new ArrayList<LoadApi.LoadLibrary>();
|
|
| 523 | - loadLibraries.add(LoadLibrary.DRAWING);
|
|
| 524 | - loadLibraries.add(LoadLibrary.GEOMETRY);
|
|
| 525 | -
|
|
| 526 | - Runnable onLoad = new Runnable() {
|
|
| 527 | - @Override
|
|
| 528 | - public void run() {
|
|
| 529 | - MapOptions mapOptions = getMapOptions(showMapControls, /* wind up */ false);
|
|
| 530 | - map = new MapWidget(mapOptions);
|
|
| 531 | - RaceMap.this.add(map, 0, 0);
|
|
| 532 | - if (showHeaderPanel) {
|
|
| 533 | - Image sapLogo = createSAPLogo();
|
|
| 534 | - RaceMap.this.add(sapLogo);
|
|
| 535 | - }
|
|
| 536 | - map.setControls(ControlPosition.LEFT_TOP, combinedWindPanel);
|
|
| 537 | - combinedWindPanel.getParent().addStyleName("CombinedWindPanelParentDiv");
|
|
| 538 | - map.setControls(ControlPosition.LEFT_TOP, trueNorthIndicatorPanel);
|
|
| 539 | - trueNorthIndicatorPanel.getParent().addStyleName("TrueNorthIndicatorPanelParentDiv");
|
|
| 540 | -
|
|
| 541 | - RaceMap.this.raceMapImageManager.loadMapIcons(map);
|
|
| 542 | - map.setSize("100%", "100%");
|
|
| 543 | - map.addZoomChangeHandler(new ZoomChangeMapHandler() {
|
|
| 544 | - @Override
|
|
| 545 | - public void onEvent(ZoomChangeMapEvent event) {
|
|
| 546 | - if (!autoZoomIn && !autoZoomOut && !orientationChangeInProgress) {
|
|
| 547 | - // stop automatic zoom after a manual zoom event; automatic zoom in zoomMapToNewBounds will restore old settings
|
|
| 548 | - final List<RaceMapZoomSettings.ZoomTypes> emptyList = Collections.emptyList();
|
|
| 549 | - settings.getZoomSettings().setTypesToConsiderOnZoom(emptyList);
|
|
| 550 | - }
|
|
| 551 | - // TODO bug489 when in wind-up mode, avoid zooming out too far; perhaps zoom back in if zoomed out too far
|
|
| 552 | - }
|
|
| 553 | - });
|
|
| 554 | - map.addDragEndHandler(new DragEndMapHandler() {
|
|
| 555 | - @Override
|
|
| 556 | - public void onEvent(DragEndMapEvent event) {
|
|
| 557 | - // stop automatic zoom after a manual drag event
|
|
| 558 | - autoZoomIn = false;
|
|
| 559 | - autoZoomOut = false;
|
|
| 560 | - final List<RaceMapZoomSettings.ZoomTypes> emptyList = Collections.emptyList();
|
|
| 561 | - settings.getZoomSettings().setTypesToConsiderOnZoom(emptyList);
|
|
| 562 | - }
|
|
| 563 | - });
|
|
| 564 | - map.addIdleHandler(new IdleMapHandler() {
|
|
| 565 | - @Override
|
|
| 566 | - public void onEvent(IdleMapEvent event) {
|
|
| 567 | - // the "idle"-event is raised at the end of map-animations
|
|
| 568 | - if (autoZoomIn) {
|
|
| 569 | - // finalize zoom-in that was started with panTo() in zoomMapToNewBounds()
|
|
| 570 | - map.setZoom(autoZoomLevel);
|
|
| 571 | - autoZoomIn = false;
|
|
| 572 | - }
|
|
| 573 | - if (autoZoomOut) {
|
|
| 574 | - // finalize zoom-out that was started with setZoom() in zoomMapToNewBounds()
|
|
| 575 | - map.panTo(autoZoomLatLngBounds.getCenter());
|
|
| 576 | - autoZoomOut = false;
|
|
| 577 | - }
|
|
| 578 | - }
|
|
| 579 | - });
|
|
| 580 | - map.addBoundsChangeHandler(new BoundsChangeMapHandler() {
|
|
| 581 | - @Override
|
|
| 582 | - public void onEvent(BoundsChangeMapEvent event) {
|
|
| 583 | - int newZoomLevel = map.getZoom();
|
|
| 584 | - if (!isAutoZoomInProgress() && (newZoomLevel != currentZoomLevel)) {
|
|
| 585 | - removeTransitions();
|
|
| 586 | - }
|
|
| 587 | - if ((streamletOverlay != null) && !map.getBounds().equals(currentMapBounds)) {
|
|
| 588 | - streamletOverlay.onBoundsChanged(newZoomLevel != currentZoomLevel);
|
|
| 589 | - }
|
|
| 590 | - if ((simulationOverlay != null) && !map.getBounds().equals(currentMapBounds)) {
|
|
| 591 | - simulationOverlay.onBoundsChanged(newZoomLevel != currentZoomLevel);
|
|
| 592 | - }
|
|
| 593 | - currentMapBounds = map.getBounds();
|
|
| 594 | - currentZoomLevel = newZoomLevel;
|
|
| 595 | - headerPanel.getElement().getStyle().setWidth(map.getOffsetWidth(), Unit.PX);
|
|
| 596 | - }
|
|
| 597 | - });
|
|
| 598 | -
|
|
| 599 | - // If there was a time change before the API was loaded, reset the time
|
|
| 600 | - if (lastTimeChangeBeforeInitialization != null) {
|
|
| 601 | - timeChanged(lastTimeChangeBeforeInitialization, null);
|
|
| 602 | - lastTimeChangeBeforeInitialization = null;
|
|
| 603 | - }
|
|
| 604 | - // Initialize streamlet canvas for wind visualization; it shouldn't be doing anything unless it's visible
|
|
| 605 | - streamletOverlay = new WindStreamletsRaceboardOverlay(getMap(), /* zIndex */ 0,
|
|
| 606 | - timer, raceIdentifier, sailingService, asyncActionsExecutor, stringMessages, coordinateSystem);
|
|
| 607 | - streamletOverlay.addToMap();
|
|
| 608 | - if (showViewStreamlets) {
|
|
| 609 | - streamletOverlay.setColors(showViewStreamletColors);
|
|
| 610 | - streamletOverlay.setVisible(true);
|
|
| 611 | - }
|
|
| 612 | -
|
|
| 613 | - if (showViewSimulation) {
|
|
| 614 | - // determine availability of polar diagram
|
|
| 615 | - setHasPolar();
|
|
| 616 | - // initialize simulation canvas
|
|
| 617 | - simulationOverlay = new RaceSimulationOverlay(getMap(), /* zIndex */ 0, raceIdentifier, sailingService, stringMessages, asyncActionsExecutor, coordinateSystem);
|
|
| 618 | - simulationOverlay.addToMap();
|
|
| 619 | - simulationOverlay.setVisible(false);
|
|
| 620 | - }
|
|
| 621 | - if (showHeaderPanel) {
|
|
| 622 | - createHeaderPanel(map);
|
|
| 623 | - }
|
|
| 624 | - createSettingsButton(map);
|
|
| 625 | -
|
|
| 626 | - // Data has been initialized
|
|
| 627 | - RaceMap.this.isMapInitialized = true;
|
|
| 628 | - RaceMap.this.redraw();
|
|
| 629 | - trueNorthIndicatorPanel.redraw();
|
|
| 630 | - showAdditionalControls(map);
|
|
| 631 | - }
|
|
| 632 | - };
|
|
| 633 | - LoadApi.go(onLoad, loadLibraries, sensor, "key="+GoogleMapAPIKey.V3_APIKey);
|
|
| 634 | - }
|
|
| 635 | -
|
|
| 636 | - /**
|
|
| 637 | - * Subclasses may define additional stuff to be shown on the map.
|
|
| 638 | - */
|
|
| 639 | - protected void showAdditionalControls(MapWidget map) {
|
|
| 640 | - }
|
|
| 641 | -
|
|
| 642 | - private void setHasPolar() {
|
|
| 643 | - GetPolarAction getPolar = new GetPolarAction(sailingService, raceIdentifier);
|
|
| 644 | - asyncActionsExecutor.execute(getPolar, GET_POLAR_CATEGORY,
|
|
| 645 | - new MarkedAsyncCallback<>(new AsyncCallback<Boolean>() {
|
|
| 646 | - @Override
|
|
| 647 | - public void onFailure(Throwable caught) {
|
|
| 648 | - errorReporter.reportError(stringMessages.errorDeterminingPolarAvailability(
|
|
| 649 | - raceIdentifier.getRaceName(), caught.getMessage()), /* silent */ true);
|
|
| 650 | - }
|
|
| 651 | -
|
|
| 652 | - @Override
|
|
| 653 | - public void onSuccess(Boolean result) {
|
|
| 654 | - // store results
|
|
| 655 | - hasPolar = result.booleanValue();
|
|
| 656 | - }
|
|
| 657 | - }));
|
|
| 658 | -
|
|
| 659 | - }
|
|
| 660 | -
|
|
| 661 | - /**
|
|
| 662 | - * Creates a header panel where additional information can be displayed by using
|
|
| 663 | - * {@link #getLeftHeaderPanel()} or {@link #getRightHeaderPanel()}.
|
|
| 664 | - *
|
|
| 665 | - * This panel is transparent and configured in such a way that it moves other controls
|
|
| 666 | - * down by its height. To achieve the goal of not having added widgets transparent
|
|
| 667 | - * this widget consists of two parts: First one is the transparent panel and the
|
|
| 668 | - * second one is the panel for the controls. The controls then need to moved onto
|
|
| 669 | - * the panel by using CSS.
|
|
| 670 | - */
|
|
| 671 | - private void createHeaderPanel(MapWidget map) {
|
|
| 672 | - // we need a panel that does not have any transparency to have the
|
|
| 673 | - // labels shown in the right color. This panel also needs to have
|
|
| 674 | - // a higher z-index than other elements on the map
|
|
| 675 | - map.setControls(ControlPosition.TOP_LEFT, panelForLeftHeaderLabels);
|
|
| 676 | - panelForLeftHeaderLabels.getElement().getParentElement().getStyle().setProperty("zIndex", "1");
|
|
| 677 | - panelForLeftHeaderLabels.getElement().getStyle().setProperty("overflow", "visible");
|
|
| 678 | - add(panelForRightHeaderLabels);
|
|
| 679 | - panelForRightHeaderLabels.getElement().getStyle().setProperty("zIndex", "1");
|
|
| 680 | - panelForRightHeaderLabels.getElement().getStyle().setProperty("overflow", "visible");
|
|
| 681 | - // need to initialize size before css kicks in to make sure
|
|
| 682 | - // that controls get positioned right
|
|
| 683 | - headerPanel.getElement().getStyle().setHeight(60, Unit.PX);
|
|
| 684 | - headerPanel.getElement().getStyle().setWidth(map.getOffsetWidth(), Unit.PX);
|
|
| 685 | - // some sort of hack: not positioning TOP_LEFT because then the
|
|
| 686 | - // controls at RIGHT would not get the correct top setting
|
|
| 687 | - map.setControls(ControlPosition.TOP_RIGHT, headerPanel);
|
|
| 688 | - }
|
|
| 689 | -
|
|
| 690 | - private void createSettingsButton(MapWidget map) {
|
|
| 691 | - final Component<RaceMapSettings> component = this;
|
|
| 692 | - Button settingsButton = new Button();
|
|
| 693 | - settingsButton.setStyleName("gwt-MapSettingsButton");
|
|
| 694 | - settingsButton.setTitle(stringMessages.settings());
|
|
| 695 | - settingsButton.addClickHandler(new ClickHandler() {
|
|
| 696 | - @Override
|
|
| 697 | - public void onClick(ClickEvent event) {
|
|
| 698 | - new SettingsDialog<RaceMapSettings>(component, stringMessages).show();
|
|
| 699 | - }
|
|
| 700 | - });
|
|
| 701 | - map.setControls(ControlPosition.RIGHT_TOP, settingsButton);
|
|
| 702 | - }
|
|
| 703 | -
|
|
| 704 | - private void removeTransitions() {
|
|
| 705 | - // remove the canvas animations for boats
|
|
| 706 | - for (BoatOverlay boatOverlay : RaceMap.this.getBoatOverlays().values()) {
|
|
| 707 | - boatOverlay.removeCanvasPositionAndRotationTransition();
|
|
| 708 | - }
|
|
| 709 | - // remove the canvas animations for the info overlays of the selected boats
|
|
| 710 | - for (CompetitorInfoOverlay infoOverlay : competitorInfoOverlays.values()) {
|
|
| 711 | - infoOverlay.removeCanvasPositionAndRotationTransition();
|
|
| 712 | - }
|
|
| 713 | - }
|
|
| 714 | -
|
|
| 715 | - public void redraw() {
|
|
| 716 | - timeChanged(timer.getTime(), null);
|
|
| 717 | - }
|
|
| 718 | -
|
|
| 719 | - Map<CompetitorDTO, BoatOverlay> getBoatOverlays() {
|
|
| 720 | - return Collections.unmodifiableMap(boatOverlays);
|
|
| 721 | - }
|
|
| 722 | -
|
|
| 723 | - public MapWidget getMap() {
|
|
| 724 | - return map;
|
|
| 725 | - }
|
|
| 726 | -
|
|
| 727 | - /**
|
|
| 728 | - * @return the Panel where labels or other controls for the header can be positioned
|
|
| 729 | - */
|
|
| 730 | - public AbsolutePanel getLeftHeaderPanel() {
|
|
| 731 | - return panelForLeftHeaderLabels;
|
|
| 732 | - }
|
|
| 733 | -
|
|
| 734 | - public AbsolutePanel getRightHeaderPanel() {
|
|
| 735 | - return panelForRightHeaderLabels;
|
|
| 736 | - }
|
|
| 737 | -
|
|
| 738 | - @Override
|
|
| 739 | - public void onRaceSelectionChange(List<RegattaAndRaceIdentifier> selectedRaces) {
|
|
| 740 | - mapFirstZoomDone = false;
|
|
| 741 | - // TODO bug 494: reset zoom settings to user preferences
|
|
| 742 | - this.selectedRaces = selectedRaces;
|
|
| 743 | - }
|
|
| 744 | -
|
|
| 745 | - @Override
|
|
| 746 | - public void raceTimesInfosReceived(Map<RegattaAndRaceIdentifier, RaceTimesInfoDTO> raceTimesInfos, long clientTimeWhenRequestWasSent, Date serverTimeDuringRequest, long clientTimeWhenResponseWasReceived) {
|
|
| 747 | - timer.adjustClientServerOffset(clientTimeWhenRequestWasSent, serverTimeDuringRequest, clientTimeWhenResponseWasReceived);
|
|
| 748 | - this.lastRaceTimesInfo = raceTimesInfos.get(selectedRaces.get(0));
|
|
| 749 | - }
|
|
| 750 | -
|
|
| 751 | - /**
|
|
| 752 | - * In {@link PlayModes#Live live mode}, when {@link #loadCompleteLeaderboard(Date) loading the leaderboard contents}, <code>null</code>
|
|
| 753 | - * is used as time point. The condition for this is encapsulated in this method so others can find out. For example, when a time change
|
|
| 754 | - * is signaled due to local offset / delay adjustments, no additional call to {@link #loadCompleteLeaderboard(Date)} would be required
|
|
| 755 | - * as <code>null</code> will be passed in any case, not being affected by local time offsets.
|
|
| 756 | - */
|
|
| 757 | - private boolean useNullAsTimePoint() {
|
|
| 758 | - return timer.getPlayMode() == PlayModes.Live;
|
|
| 759 | - }
|
|
| 760 | -
|
|
| 761 | - @Override
|
|
| 762 | - public void timeChanged(final Date newTime, final Date oldTime) {
|
|
| 763 | - if (newTime != null && isMapInitialized) {
|
|
| 764 | - if (selectedRaces != null && !selectedRaces.isEmpty()) {
|
|
| 765 | - RegattaAndRaceIdentifier race = selectedRaces.get(selectedRaces.size() - 1);
|
|
| 766 | - final Iterable<CompetitorDTO> competitorsToShow = getCompetitorsToShow();
|
|
| 767 | -
|
|
| 768 | - if (race != null) {
|
|
| 769 | - final com.sap.sse.common.Util.Triple<Map<CompetitorDTO, Date>, Map<CompetitorDTO, Date>, Map<CompetitorDTO, Boolean>> fromAndToAndOverlap =
|
|
| 770 | - fixesAndTails.computeFromAndTo(newTime, competitorsToShow, settings.getEffectiveTailLengthInMilliseconds());
|
|
| 771 | - int requestID = ++boatPositionRequestIDCounter;
|
|
| 772 | - // For those competitors for which the tails don't overlap (and therefore will be replaced by the new tail coming from the server)
|
|
| 773 | - // we expect some potential delay in computing the full tail. Therefore, in those cases we fire two requests: one fetching only the
|
|
| 774 | - // boat positions at newTime with zero tail length; and another one fetching everything else.
|
|
| 775 | - GetRaceMapDataAction getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping = getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping(fromAndToAndOverlap, race, newTime);
|
|
| 776 | - if (getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping != null) {
|
|
| 777 | - asyncActionsExecutor.execute(getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping, GET_RACE_MAP_DATA_CATEGORY,
|
|
| 778 | - getRaceMapDataCallback(oldTime, newTime, fromAndToAndOverlap.getC(), competitorsToShow, requestID));
|
|
| 779 | - requestID = ++boatPositionRequestIDCounter;
|
|
| 780 | - }
|
|
| 781 | - // next, do the full thing; being the later call, if request throttling kicks in, the later call
|
|
| 782 | - // supersedes the earlier call which may get dropped then
|
|
| 783 | - GetRaceMapDataAction getRaceMapDataAction = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(), race,
|
|
| 784 | - useNullAsTimePoint() ? null : newTime, fromAndToAndOverlap.getA(), fromAndToAndOverlap.getB(), /* extrapolate */ true, (settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null));
|
|
| 785 | - asyncActionsExecutor.execute(getRaceMapDataAction, GET_RACE_MAP_DATA_CATEGORY,
|
|
| 786 | - getRaceMapDataCallback(oldTime, newTime, fromAndToAndOverlap.getC(), competitorsToShow, requestID));
|
|
| 787 | -
|
|
| 788 | - // draw the wind into the map, get the combined wind
|
|
| 789 | - List<String> windSourceTypeNames = new ArrayList<String>();
|
|
| 790 | - windSourceTypeNames.add(WindSourceType.EXPEDITION.name());
|
|
| 791 | - windSourceTypeNames.add(WindSourceType.COMBINED.name());
|
|
| 792 | - GetWindInfoAction getWindInfoAction = new GetWindInfoAction(sailingService, race, newTime, 1000L, 1, windSourceTypeNames,
|
|
| 793 | - /* onlyUpToNewestEvent==false means get us any data we can get by a best effort */ false);
|
|
| 794 | - asyncActionsExecutor.execute(getWindInfoAction, GET_WIND_DATA_CATEGORY, new AsyncCallback<WindInfoForRaceDTO>() {
|
|
| 795 | - @Override
|
|
| 796 | - public void onFailure(Throwable caught) {
|
|
| 797 | - errorReporter.reportError("Error obtaining wind information: " + caught.getMessage(), true /*silentMode */);
|
|
| 798 | - }
|
|
| 799 | -
|
|
| 800 | - @Override
|
|
| 801 | - public void onSuccess(WindInfoForRaceDTO windInfo) {
|
|
| 802 | - List<com.sap.sse.common.Util.Pair<WindSource, WindTrackInfoDTO>> windSourcesToShow = new ArrayList<com.sap.sse.common.Util.Pair<WindSource, WindTrackInfoDTO>>();
|
|
| 803 | - if (windInfo != null) {
|
|
| 804 | - lastCombinedWindTrackInfoDTO = windInfo;
|
|
| 805 | - showAdvantageLine(competitorsToShow, newTime);
|
|
| 806 | - for (WindSource windSource : windInfo.windTrackInfoByWindSource.keySet()) {
|
|
| 807 | - WindTrackInfoDTO windTrackInfoDTO = windInfo.windTrackInfoByWindSource.get(windSource);
|
|
| 808 | - switch (windSource.getType()) {
|
|
| 809 | - case EXPEDITION:
|
|
| 810 | - // we filter out measured wind sources with vary low confidence
|
|
| 811 | - if (windTrackInfoDTO.minWindConfidence > 0.0001) {
|
|
| 812 | - windSourcesToShow.add(new com.sap.sse.common.Util.Pair<WindSource, WindTrackInfoDTO>(windSource, windTrackInfoDTO));
|
|
| 813 | - }
|
|
| 814 | - break;
|
|
| 815 | - case COMBINED:
|
|
| 816 | - showCombinedWindOnMap(windSource, windTrackInfoDTO);
|
|
| 817 | - if (requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown) {
|
|
| 818 | - updateCoordinateSystemFromSettings();
|
|
| 819 | - }
|
|
| 820 | - break;
|
|
| 821 | - default:
|
|
| 822 | - // Which wind sources are requested is defined in a list above this
|
|
| 823 | - // action. So we throw here an exception to notice a missing source.
|
|
| 824 | - throw new UnsupportedOperationException(
|
|
| 825 | - "There is currently no support for the enum value '"
|
|
| 826 | - + windSource.getType() + "' in this method.");
|
|
| 827 | - }
|
|
| 828 | - }
|
|
| 829 | - }
|
|
| 830 | - showWindSensorsOnMap(windSourcesToShow);
|
|
| 831 | - }
|
|
| 832 | - });
|
|
| 833 | - }
|
|
| 834 | - }
|
|
| 835 | - }
|
|
| 836 | - }
|
|
| 837 | -
|
|
| 838 | - /**
|
|
| 839 | - * We assume that overlapping segments usually don't require a lot of loading time as the most typical case will be to update a longer
|
|
| 840 | - * tail with a few new fixes that were received since the last time tick. Non-overlapping position requests typically occur for the
|
|
| 841 | - * first request when no fix at all is known for the competitor yet, and when the user has radically moved the time slider to some
|
|
| 842 | - * other time such that given the current tail length setting the new tail segment does not overlap with the old one, requiring a full
|
|
| 843 | - * load of the entire tail data for that competitor.<p>
|
|
| 844 | - *
|
|
| 845 | - * For the non-overlapping requests, this method creates a separate request which only loads boat positions, quick ranks, sidelines and
|
|
| 846 | - * mark positions for the zero-length interval at <code>newTime</code>, assuming that this will work fairly fast and in particular in
|
|
| 847 | - * O(1) time regardless of tail length, compared to fetching the entire tail for all competitors.
|
|
| 848 | - */
|
|
| 849 | - private GetRaceMapDataAction getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping(
|
|
| 850 | - Triple<Map<CompetitorDTO, Date>, Map<CompetitorDTO, Date>, Map<CompetitorDTO, Boolean>> fromAndToAndOverlap,
|
|
| 851 | - RegattaAndRaceIdentifier race, Date newTime) {
|
|
| 852 | - Map<CompetitorDTO, Date> fromTimes = new HashMap<>();
|
|
| 853 | - Map<CompetitorDTO, Date> toTimes = new HashMap<>();
|
|
| 854 | - for (Map.Entry<CompetitorDTO, Boolean> e : fromAndToAndOverlap.getC().entrySet()) {
|
|
| 855 | - if (!e.getValue()) {
|
|
| 856 | - // no overlap; add competitor to request
|
|
| 857 | - fromTimes.put(e.getKey(), newTime);
|
|
| 858 | - toTimes.put(e.getKey(), newTime);
|
|
| 859 | - }
|
|
| 860 | - }
|
|
| 861 | - final GetRaceMapDataAction result;
|
|
| 862 | - if (!fromTimes.isEmpty()) {
|
|
| 863 | - result = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(),
|
|
| 864 | - race, useNullAsTimePoint() ? null : newTime, fromTimes, toTimes, /* extrapolate */true, (settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null));
|
|
| 865 | - } else {
|
|
| 866 | - result = null;
|
|
| 867 | - }
|
|
| 868 | - return result;
|
|
| 869 | - }
|
|
| 870 | -
|
|
| 871 | - private AsyncCallback<RaceMapDataDTO> getRaceMapDataCallback(
|
|
| 872 | - final Date oldTime,
|
|
| 873 | - final Date newTime,
|
|
| 874 | - final Map<CompetitorDTO, Boolean> hasTailOverlapForCompetitor,
|
|
| 875 | - final Iterable<CompetitorDTO> competitorsToShow, final int requestID) {
|
|
| 876 | - return new AsyncCallback<RaceMapDataDTO>() {
|
|
| 877 | - @Override
|
|
| 878 | - public void onFailure(Throwable caught) {
|
|
| 879 | - errorReporter.reportError("Error obtaining racemap data: " + caught.getMessage(), true /*silentMode */);
|
|
| 880 | - }
|
|
| 881 | -
|
|
| 882 | - @Override
|
|
| 883 | - public void onSuccess(RaceMapDataDTO raceMapDataDTO) {
|
|
| 884 | - if (map != null && raceMapDataDTO != null) {
|
|
| 885 | - quickRanks = raceMapDataDTO.quickRanks;
|
|
| 886 | - competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber =
|
|
| 887 | - raceMapDataDTO.competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber;
|
|
| 888 | - if (showViewSimulation && settings.isShowSimulationOverlay()) {
|
|
| 889 | - lastLegNumber = raceMapDataDTO.coursePositions.currentLegNumber;
|
|
| 890 | - simulationOverlay.updateLeg(Math.max(lastLegNumber,1), /* clearCanvas */ false, raceMapDataDTO.simulationResultVersion);
|
|
| 891 | - }
|
|
| 892 | - // process response only if not received out of order
|
|
| 893 | - if (startedProcessingRequestID < requestID) {
|
|
| 894 | - startedProcessingRequestID = requestID;
|
|
| 895 | - // Do boat specific actions
|
|
| 896 | - Map<CompetitorDTO, List<GPSFixDTO>> boatData = raceMapDataDTO.boatPositions;
|
|
| 897 | - long timeForPositionTransitionMillis = calculateTimeForPositionTransition(newTime, oldTime);
|
|
| 898 | - fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, timeForPositionTransitionMillis);
|
|
| 899 | - showBoatsOnMap(newTime, timeForPositionTransitionMillis, getCompetitorsToShow());
|
|
| 900 | - showCompetitorInfoOnMap(newTime, timeForPositionTransitionMillis, competitorSelection.getSelectedFilteredCompetitors());
|
|
| 901 | - if (douglasMarkers != null) {
|
|
| 902 | - removeAllMarkDouglasPeuckerpoints();
|
|
| 903 | - }
|
|
| 904 | - if (maneuverMarkers != null) {
|
|
| 905 | - removeAllManeuverMarkers();
|
|
| 906 | - }
|
|
| 907 | -
|
|
| 908 | - // Do mark specific actions
|
|
| 909 | - showCourseMarksOnMap(raceMapDataDTO.coursePositions);
|
|
| 910 | - if (requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown) {
|
|
| 911 | - updateCoordinateSystemFromSettings();
|
|
| 912 | - }
|
|
| 913 | - showCourseSidelinesOnMap(raceMapDataDTO.courseSidelines);
|
|
| 914 | - showStartAndFinishAndCourseMiddleLines(raceMapDataDTO.coursePositions);
|
|
| 915 | - showStartLineToFirstMarkTriangle(raceMapDataDTO.coursePositions);
|
|
| 916 | - // even though the wind data is retrieved by a separate call, re-draw the advantage line because it needs to
|
|
| 917 | - // adjust to new boat positions
|
|
| 918 | - showAdvantageLine(competitorsToShow, newTime);
|
|
| 919 | -
|
|
| 920 | - // Rezoom the map
|
|
| 921 | - LatLngBounds zoomToBounds = null;
|
|
| 922 | - if (!settings.getZoomSettings().containsZoomType(ZoomTypes.NONE)) { // Auto zoom if setting is not manual
|
|
| 923 | - zoomToBounds = settings.getZoomSettings().getNewBounds(RaceMap.this);
|
|
| 924 | - if (zoomToBounds == null && !mapFirstZoomDone) {
|
|
| 925 | - zoomToBounds = getDefaultZoomBounds(); // the user-specified zoom couldn't find what it was looking for; try defaults once
|
|
| 926 | - }
|
|
| 927 | - } else if (!mapFirstZoomDone) { // Zoom once to the marks if marks exist
|
|
| 928 | - zoomToBounds = new CourseMarksBoundsCalculator().calculateNewBounds(RaceMap.this);
|
|
| 929 | - if (zoomToBounds == null) {
|
|
| 930 | - zoomToBounds = getDefaultZoomBounds(); // use default zoom, e.g.,
|
|
| 931 | - }
|
|
| 932 | - /*
|
|
| 933 | - * Reset the mapZoomedOrPannedSinceLastRaceSelection: In spite of the fact that
|
|
| 934 | - * the map was just zoomed to the bounds of the marks, it was not a zoom or pan
|
|
| 935 | - * triggered by the user. As a consequence the
|
|
| 936 | - * mapZoomedOrPannedSinceLastRaceSelection option has to reset again.
|
|
| 937 | - */
|
|
| 938 | - }
|
|
| 939 | - zoomMapToNewBounds(zoomToBounds);
|
|
| 940 | - mapFirstZoomDone = true;
|
|
| 941 | - }
|
|
| 942 | - } else {
|
|
| 943 | - lastTimeChangeBeforeInitialization = newTime;
|
|
| 944 | - }
|
|
| 945 | - }
|
|
| 946 | - };
|
|
| 947 | - }
|
|
| 948 | -
|
|
| 949 | - private void showCourseSidelinesOnMap(List<SidelineDTO> sidelinesDTOs) {
|
|
| 950 | - if (map != null && sidelinesDTOs != null ) {
|
|
| 951 | - Map<SidelineDTO, Polygon> toRemoveSidelines = new HashMap<SidelineDTO, Polygon>(courseSidelines);
|
|
| 952 | - for (SidelineDTO sidelineDTO : sidelinesDTOs) {
|
|
| 953 | - if (sidelineDTO.getMarks().size() == 2) { // right now we only support sidelines with 2 marks
|
|
| 954 | - Polygon sideline = courseSidelines.get(sidelineDTO);
|
|
| 955 | - LatLng[] sidelinePoints = new LatLng[sidelineDTO.getMarks().size()];
|
|
| 956 | - int i=0;
|
|
| 957 | - for (MarkDTO sidelineMark : sidelineDTO.getMarks()) {
|
|
| 958 | - sidelinePoints[i] = coordinateSystem.toLatLng(sidelineMark.position);
|
|
| 959 | - i++;
|
|
| 960 | - }
|
|
| 961 | - if (sideline == null) {
|
|
| 962 | - PolygonOptions options = PolygonOptions.newInstance();
|
|
| 963 | - options.setClickable(true);
|
|
| 964 | - options.setStrokeColor("#0000FF");
|
|
| 965 | - options.setStrokeWeight(1);
|
|
| 966 | - options.setStrokeOpacity(1.0);
|
|
| 967 | - options.setFillColor(null);
|
|
| 968 | - options.setFillOpacity(1.0);
|
|
| 969 | -
|
|
| 970 | - sideline = Polygon.newInstance(options);
|
|
| 971 | - MVCArray<LatLng> pointsAsArray = MVCArray.newInstance(sidelinePoints);
|
|
| 972 | - sideline.setPath(pointsAsArray);
|
|
| 973 | -
|
|
| 974 | - sideline.addMouseOverHandler(new MouseOverMapHandler() {
|
|
| 975 | - @Override
|
|
| 976 | - public void onEvent(MouseOverMapEvent event) {
|
|
| 977 | - map.setTitle(stringMessages.sideline());
|
|
| 978 | - }
|
|
| 979 | - });
|
|
| 980 | - sideline.addMouseOutMoveHandler(new MouseOutMapHandler() {
|
|
| 981 | - @Override
|
|
| 982 | - public void onEvent(MouseOutMapEvent event) {
|
|
| 983 | - map.setTitle("");
|
|
| 984 | - }
|
|
| 985 | - });
|
|
| 986 | - courseSidelines.put(sidelineDTO, sideline);
|
|
| 987 | - sideline.setMap(map);
|
|
| 988 | - } else {
|
|
| 989 | - sideline.getPath().removeAt(1);
|
|
| 990 | - sideline.getPath().removeAt(0);
|
|
| 991 | - sideline.getPath().insertAt(0, sidelinePoints[0]);
|
|
| 992 | - sideline.getPath().insertAt(1, sidelinePoints[1]);
|
|
| 993 | - toRemoveSidelines.remove(sidelineDTO);
|
|
| 994 | - }
|
|
| 995 | - }
|
|
| 996 | - }
|
|
| 997 | - for (SidelineDTO toRemoveSideline : toRemoveSidelines.keySet()) {
|
|
| 998 | - Polygon sideline = courseSidelines.remove(toRemoveSideline);
|
|
| 999 | - sideline.setMap(null);
|
|
| 1000 | - }
|
|
| 1001 | - }
|
|
| 1002 | - }
|
|
| 1003 | -
|
|
| 1004 | - private void showCourseMarksOnMap(CoursePositionsDTO courseDTO) {
|
|
| 1005 | - if (map != null && courseDTO != null) {
|
|
| 1006 | - WaypointDTO endWaypointForCurrentLegNumber = null;
|
|
| 1007 | - if(courseDTO.currentLegNumber > 0 && courseDTO.currentLegNumber <= courseDTO.totalLegsCount) {
|
|
| 1008 | - endWaypointForCurrentLegNumber = courseDTO.getEndWaypointForLegNumber(courseDTO.currentLegNumber);
|
|
| 1009 | - }
|
|
| 1010 | -
|
|
| 1011 | - Map<String, CourseMarkOverlay> toRemoveCourseMarks = new HashMap<String, CourseMarkOverlay>(courseMarkOverlays);
|
|
| 1012 | - if (courseDTO.marks != null) {
|
|
| 1013 | - for (MarkDTO markDTO : courseDTO.marks) {
|
|
| 1014 | - boolean isSelected = false;
|
|
| 1015 | - if (endWaypointForCurrentLegNumber != null && Util.contains(endWaypointForCurrentLegNumber.controlPoint.getMarks(), markDTO)) {
|
|
| 1016 | - isSelected = true;
|
|
| 1017 | - }
|
|
| 1018 | - CourseMarkOverlay courseMarkOverlay = courseMarkOverlays.get(markDTO.getName());
|
|
| 1019 | - if (courseMarkOverlay == null) {
|
|
| 1020 | - courseMarkOverlay = createCourseMarkOverlay(RaceMapOverlaysZIndexes.COURSEMARK_ZINDEX, markDTO);
|
|
| 1021 | - courseMarkOverlay.setShowBuoyZone(settings.getHelpLinesSettings().isVisible(HelpLineTypes.BUOYZONE));
|
|
| 1022 | - courseMarkOverlay.setBuoyZoneRadiusInMeter(settings.getBuoyZoneRadiusInMeters());
|
|
| 1023 | - courseMarkOverlay.setSelected(isSelected);
|
|
| 1024 | - courseMarkOverlays.put(markDTO.getName(), courseMarkOverlay);
|
|
| 1025 | - markDTOs.put(markDTO.getName(), markDTO);
|
|
| 1026 | - courseMarkOverlay.addToMap();
|
|
| 1027 | - } else {
|
|
| 1028 | - courseMarkOverlay.setMarkPosition(markDTO.position);
|
|
| 1029 | - courseMarkOverlay.setShowBuoyZone(settings.getHelpLinesSettings().isVisible(HelpLineTypes.BUOYZONE));
|
|
| 1030 | - courseMarkOverlay.setBuoyZoneRadiusInMeter(settings.getBuoyZoneRadiusInMeters());
|
|
| 1031 | - courseMarkOverlay.setSelected(isSelected);
|
|
| 1032 | - courseMarkOverlay.draw();
|
|
| 1033 | - toRemoveCourseMarks.remove(markDTO.getName());
|
|
| 1034 | - }
|
|
| 1035 | - }
|
|
| 1036 | - }
|
|
| 1037 | - for (String toRemoveMarkName : toRemoveCourseMarks.keySet()) {
|
|
| 1038 | - CourseMarkOverlay removedOverlay = courseMarkOverlays.remove(toRemoveMarkName);
|
|
| 1039 | - if(removedOverlay != null) {
|
|
| 1040 | - removedOverlay.removeFromMap();
|
|
| 1041 | - }
|
|
| 1042 | - }
|
|
| 1043 | - }
|
|
| 1044 | - }
|
|
| 1045 | -
|
|
| 1046 | - /**
|
|
| 1047 | - * Based on the mark positions in {@link #courseMarkOverlays}' values this method determines the center of gravity of these marks'
|
|
| 1048 | - * {@link CourseMarkOverlay#getPosition() positions}.
|
|
| 1049 | - */
|
|
| 1050 | - private Position getCenterOfCourse() {
|
|
| 1051 | - ScalablePosition center = null;
|
|
| 1052 | - int count = 0;
|
|
| 1053 | - for (CourseMarkOverlay markOverlay : courseMarkOverlays.values()) {
|
|
| 1054 | - ScalablePosition markPosition = new ScalablePosition(markOverlay.getPosition());
|
|
| 1055 | - if (center == null) {
|
|
| 1056 | - center = markPosition;
|
|
| 1057 | - } else {
|
|
| 1058 | - center.add(markPosition);
|
|
| 1059 | - }
|
|
| 1060 | - count++;
|
|
| 1061 | - }
|
|
| 1062 | - return center == null ? null : center.divide(count);
|
|
| 1063 | - }
|
|
| 1064 | -
|
|
| 1065 | - private void showCombinedWindOnMap(WindSource windSource, WindTrackInfoDTO windTrackInfoDTO) {
|
|
| 1066 | - if (map != null) {
|
|
| 1067 | - combinedWindPanel.setWindInfo(windTrackInfoDTO, windSource);
|
|
| 1068 | - combinedWindPanel.redraw();
|
|
| 1069 | - }
|
|
| 1070 | - }
|
|
| 1071 | -
|
|
| 1072 | - private void showWindSensorsOnMap(List<com.sap.sse.common.Util.Pair<WindSource, WindTrackInfoDTO>> windSensorsList) {
|
|
| 1073 | - if (map != null) {
|
|
| 1074 | - Set<WindSource> toRemoveWindSources = new HashSet<WindSource>(windSensorOverlays.keySet());
|
|
| 1075 | - for (com.sap.sse.common.Util.Pair<WindSource, WindTrackInfoDTO> windSourcePair : windSensorsList) {
|
|
| 1076 | - WindSource windSource = windSourcePair.getA();
|
|
| 1077 | - WindTrackInfoDTO windTrackInfoDTO = windSourcePair.getB();
|
|
| 1078 | -
|
|
| 1079 | - WindSensorOverlay windSensorOverlay = windSensorOverlays.get(windSource);
|
|
| 1080 | - if (windSensorOverlay == null) {
|
|
| 1081 | - windSensorOverlay = createWindSensorOverlay(RaceMapOverlaysZIndexes.WINDSENSOR_ZINDEX, windSource, windTrackInfoDTO);
|
|
| 1082 | - windSensorOverlays.put(windSource, windSensorOverlay);
|
|
| 1083 | - windSensorOverlay.addToMap();
|
|
| 1084 | - } else {
|
|
| 1085 | - windSensorOverlay.setWindInfo(windTrackInfoDTO, windSource);
|
|
| 1086 | - windSensorOverlay.draw();
|
|
| 1087 | - toRemoveWindSources.remove(windSource);
|
|
| 1088 | - }
|
|
| 1089 | - }
|
|
| 1090 | - for (WindSource toRemoveWindSource : toRemoveWindSources) {
|
|
| 1091 | - WindSensorOverlay removedWindSensorOverlay = windSensorOverlays.remove(toRemoveWindSource);
|
|
| 1092 | - if (removedWindSensorOverlay != null) {
|
|
| 1093 | - removedWindSensorOverlay.removeFromMap();
|
|
| 1094 | - }
|
|
| 1095 | - }
|
|
| 1096 | - }
|
|
| 1097 | - }
|
|
| 1098 | -
|
|
| 1099 | - private void showCompetitorInfoOnMap(final Date newTime, final long timeForPositionTransitionMillis, final Iterable<CompetitorDTO> competitorsToShow) {
|
|
| 1100 | - if (map != null) {
|
|
| 1101 | - if (settings.isShowSelectedCompetitorsInfo()) {
|
|
| 1102 | - Set<CompetitorDTO> toRemoveCompetorInfoOverlays = new HashSet<CompetitorDTO>(
|
|
| 1103 | - competitorInfoOverlays.keySet());
|
|
| 1104 | - for (CompetitorDTO competitorDTO : competitorsToShow) {
|
|
| 1105 | - if (fixesAndTails.hasFixesFor(competitorDTO)) {
|
|
| 1106 | - GPSFixDTO lastBoatFix = getBoatFix(competitorDTO, newTime);
|
|
| 1107 | - if (lastBoatFix != null) {
|
|
| 1108 | - CompetitorInfoOverlay competitorInfoOverlay = competitorInfoOverlays.get(competitorDTO);
|
|
| 1109 | - if (competitorInfoOverlay == null) {
|
|
| 1110 | - competitorInfoOverlay = createCompetitorInfoOverlay(RaceMapOverlaysZIndexes.INFO_OVERLAY_ZINDEX, competitorDTO);
|
|
| 1111 | - competitorInfoOverlays.put(competitorDTO, competitorInfoOverlay);
|
|
| 1112 | - competitorInfoOverlay.setPosition(lastBoatFix.position, timeForPositionTransitionMillis);
|
|
| 1113 | - competitorInfoOverlay.addToMap();
|
|
| 1114 | - } else {
|
|
| 1115 | - competitorInfoOverlay.setPosition(lastBoatFix.position, timeForPositionTransitionMillis);
|
|
| 1116 | - competitorInfoOverlay.draw();
|
|
| 1117 | - }
|
|
| 1118 | - toRemoveCompetorInfoOverlays.remove(competitorDTO);
|
|
| 1119 | - }
|
|
| 1120 | - }
|
|
| 1121 | - }
|
|
| 1122 | - for (CompetitorDTO toRemoveCompetorDTO : toRemoveCompetorInfoOverlays) {
|
|
| 1123 | - CompetitorInfoOverlay competitorInfoOverlay = competitorInfoOverlays.get(toRemoveCompetorDTO);
|
|
| 1124 | - competitorInfoOverlay.removeFromMap();
|
|
| 1125 | - competitorInfoOverlays.remove(toRemoveCompetorDTO);
|
|
| 1126 | - }
|
|
| 1127 | - } else {
|
|
| 1128 | - // remove all overlays
|
|
| 1129 | - for (CompetitorInfoOverlay competitorInfoOverlay : competitorInfoOverlays.values()) {
|
|
| 1130 | - competitorInfoOverlay.removeFromMap();
|
|
| 1131 | - }
|
|
| 1132 | - competitorInfoOverlays.clear();
|
|
| 1133 | - }
|
|
| 1134 | - }
|
|
| 1135 | - }
|
|
| 1136 | -
|
|
| 1137 | - private long calculateTimeForPositionTransition(final Date newTime, final Date oldTime) {
|
|
| 1138 | - final long timeForPositionTransitionMillis;
|
|
| 1139 | - boolean hasTimeJumped = oldTime != null && Math.abs(oldTime.getTime() - newTime.getTime()) > 3*timer.getRefreshInterval();
|
|
| 1140 | - if (timer.getPlayState() == PlayStates.Playing && !hasTimeJumped) {
|
|
| 1141 | - // choose 130% of the refresh interval as transition period to make it unlikely that the transition
|
|
| 1142 | - // stops before the next update has been received
|
|
| 1143 | - timeForPositionTransitionMillis = 1300 * timer.getRefreshInterval() / 1000;
|
|
| 1144 | - } else {
|
|
| 1145 | - timeForPositionTransitionMillis = -1; // -1 means 'no transition
|
|
| 1146 | - }
|
|
| 1147 | - return timeForPositionTransitionMillis;
|
|
| 1148 | - }
|
|
| 1149 | -
|
|
| 1150 | - private void showBoatsOnMap(final Date newTime, final long timeForPositionTransitionMillis, final Iterable<CompetitorDTO> competitorsToShow) {
|
|
| 1151 | - if (map != null) {
|
|
| 1152 | - Date tailsFromTime = new Date(newTime.getTime() - settings.getEffectiveTailLengthInMilliseconds());
|
|
| 1153 | - Date tailsToTime = newTime;
|
|
| 1154 | - Set<CompetitorDTO> competitorDTOsOfUnusedTails = new HashSet<CompetitorDTO>(fixesAndTails.getCompetitorsWithTails());
|
|
| 1155 | - Set<CompetitorDTO> competitorDTOsOfUnusedBoatCanvases = new HashSet<CompetitorDTO>(boatOverlays.keySet());
|
|
| 1156 | - for (CompetitorDTO competitorDTO : competitorsToShow) {
|
|
| 1157 | - if (fixesAndTails.hasFixesFor(competitorDTO)) {
|
|
| 1158 | - Polyline tail = fixesAndTails.getTail(competitorDTO);
|
|
| 1159 | - if (tail == null) {
|
|
| 1160 | - tail = fixesAndTails.createTailAndUpdateIndices(competitorDTO, tailsFromTime, tailsToTime, this);
|
|
| 1161 | - tail.setMap(map);
|
|
| 1162 | - } else {
|
|
| 1163 | - fixesAndTails.updateTail(tail, competitorDTO, tailsFromTime, tailsToTime,
|
|
| 1164 | - (int) (timeForPositionTransitionMillis==-1?-1:timeForPositionTransitionMillis/2));
|
|
| 1165 | - competitorDTOsOfUnusedTails.remove(competitorDTO);
|
|
| 1166 | - PolylineOptions newOptions = createTailStyle(competitorDTO, displayHighlighted(competitorDTO));
|
|
| 1167 | - tail.setOptions(newOptions);
|
|
| 1168 | - }
|
|
| 1169 | - boolean usedExistingBoatCanvas = updateBoatCanvasForCompetitor(competitorDTO, newTime, timeForPositionTransitionMillis);
|
|
| 1170 | - if (usedExistingBoatCanvas) {
|
|
| 1171 | - competitorDTOsOfUnusedBoatCanvases.remove(competitorDTO);
|
|
| 1172 | - }
|
|
| 1173 | - }
|
|
| 1174 | - }
|
|
| 1175 | - for (CompetitorDTO unusedBoatCanvasCompetitorDTO : competitorDTOsOfUnusedBoatCanvases) {
|
|
| 1176 | - BoatOverlay boatCanvas = boatOverlays.get(unusedBoatCanvasCompetitorDTO);
|
|
| 1177 | - boatCanvas.removeFromMap();
|
|
| 1178 | - boatOverlays.remove(unusedBoatCanvasCompetitorDTO);
|
|
| 1179 | - }
|
|
| 1180 | - for (CompetitorDTO unusedTailCompetitorDTO : competitorDTOsOfUnusedTails) {
|
|
| 1181 | - fixesAndTails.removeTail(unusedTailCompetitorDTO);
|
|
| 1182 | - }
|
|
| 1183 | - }
|
|
| 1184 | - }
|
|
| 1185 | -
|
|
| 1186 | - /**
|
|
| 1187 | - * This algorithm is limited to distances such that dlon < pi/2, i.e., those that extend around less than one
|
|
| 1188 | - * quarter of the circumference of the earth in longitude. A completely general, but more complicated algorithm is
|
|
| 1189 | - * necessary if greater distances are allowed.
|
|
| 1190 | - */
|
|
| 1191 | - public LatLng calculatePositionAlongRhumbline(LatLng position, double bearingDeg, double distanceInKm) {
|
|
| 1192 | - double distianceRad = distanceInKm / 6371.0; // r = 6371 means earth's radius in km
|
|
| 1193 | - double lat1 = position.getLatitude() / 180. * Math.PI;
|
|
| 1194 | - double lon1 = position.getLongitude() / 180. * Math.PI;
|
|
| 1195 | - double bearingRad = bearingDeg / 180. * Math.PI;
|
|
| 1196 | - double lat2 = Math.asin(Math.sin(lat1) * Math.cos(distianceRad) +
|
|
| 1197 | - Math.cos(lat1) * Math.sin(distianceRad) * Math.cos(bearingRad));
|
|
| 1198 | - double lon2 = lon1 + Math.atan2(Math.sin(bearingRad)*Math.sin(distianceRad)*Math.cos(lat1),
|
|
| 1199 | - Math.cos(distianceRad)-Math.sin(lat1)*Math.sin(lat2));
|
|
| 1200 | - lon2 = (lon2+3*Math.PI) % (2*Math.PI) - Math.PI; // normalize to -180..+180�
|
|
| 1201 | - // position is already in LatLng space, so no mapping through coordinateSystem is required here
|
|
| 1202 | - return LatLng.newInstance(lat2 / Math.PI * 180., lon2 / Math.PI * 180.);
|
|
| 1203 | - }
|
|
| 1204 | -
|
|
| 1205 | - /**
|
|
| 1206 | - * Returns a pair whose first component is the leg number (one-based) of the competitor returned as the second component.
|
|
| 1207 | - */
|
|
| 1208 | - private com.sap.sse.common.Util.Pair<Integer, CompetitorDTO> getFarthestAheadVisibleCompetitorWithOneBasedLegNumber(
|
|
| 1209 | - Iterable<CompetitorDTO> competitorsToShow) {
|
|
| 1210 | - CompetitorDTO leadingCompetitorDTO = null;
|
|
| 1211 | - int legOfLeaderCompetitor = -1;
|
|
| 1212 | - // this only works because the quickRanks are sorted
|
|
| 1213 | - for (Entry<CompetitorDTO, Integer> competitorsByWindwardDistanceTraveledAndOneBasedLegNumber :
|
|
| 1214 | - competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber.entrySet()) {
|
|
| 1215 | - if (Util.contains(competitorsToShow, competitorsByWindwardDistanceTraveledAndOneBasedLegNumber.getKey()) &&
|
|
| 1216 | - competitorsByWindwardDistanceTraveledAndOneBasedLegNumber.getValue() != null) {
|
|
| 1217 | - leadingCompetitorDTO = competitorsByWindwardDistanceTraveledAndOneBasedLegNumber.getKey();
|
|
| 1218 | - legOfLeaderCompetitor = competitorsByWindwardDistanceTraveledAndOneBasedLegNumber.getValue();
|
|
| 1219 | - return new com.sap.sse.common.Util.Pair<Integer, CompetitorDTO>(legOfLeaderCompetitor, leadingCompetitorDTO);
|
|
| 1220 | - }
|
|
| 1221 | - }
|
|
| 1222 | - return null;
|
|
| 1223 | - }
|
|
| 1224 | -
|
|
| 1225 | - private void showAdvantageLine(Iterable<CompetitorDTO> competitorsToShow, Date date) {
|
|
| 1226 | - if (map != null && lastRaceTimesInfo != null && quickRanks != null && lastCombinedWindTrackInfoDTO != null) {
|
|
| 1227 | - boolean drawAdvantageLine = false;
|
|
| 1228 | - if (settings.getHelpLinesSettings().isVisible(HelpLineTypes.ADVANTAGELINE)) {
|
|
| 1229 | - // find competitor with highest rank
|
|
| 1230 | - com.sap.sse.common.Util.Pair<Integer, CompetitorDTO> visibleLeaderInfo = getFarthestAheadVisibleCompetitorWithOneBasedLegNumber(competitorsToShow);
|
|
| 1231 | - // the boat fix may be null; may mean that no positions were loaded yet for the leading visible boat;
|
|
| 1232 | - // don't show anything
|
|
| 1233 | - GPSFixDTO lastBoatFix = null;
|
|
| 1234 | - boolean isVisibleLeaderInfoComplete = false;
|
|
| 1235 | - boolean isLegTypeKnown = false;
|
|
| 1236 | - WindTrackInfoDTO windDataForLegMiddle = null;
|
|
| 1237 | - LegInfoDTO legInfoDTO = null;
|
|
| 1238 | - if (visibleLeaderInfo != null
|
|
| 1239 | - && visibleLeaderInfo.getA() > 0
|
|
| 1240 | - && visibleLeaderInfo.getA() <= lastRaceTimesInfo.getLegInfos().size()
|
|
| 1241 | - // get wind at middle of leg for leading visible competitor
|
|
| 1242 | - && (windDataForLegMiddle = lastCombinedWindTrackInfoDTO
|
|
| 1243 | - .getCombinedWindOnLegMiddle(visibleLeaderInfo.getA() - 1)) != null
|
|
| 1244 | - && !windDataForLegMiddle.windFixes.isEmpty()) {
|
|
| 1245 | - isVisibleLeaderInfoComplete = true;
|
|
| 1246 | - legInfoDTO = lastRaceTimesInfo.getLegInfos().get(visibleLeaderInfo.getA() - 1);
|
|
| 1247 | - if (legInfoDTO.legType != null) {
|
|
| 1248 | - isLegTypeKnown = true;
|
|
| 1249 | - }
|
|
| 1250 | - lastBoatFix = getBoatFix(visibleLeaderInfo.getB(), date);
|
|
| 1251 | - }
|
|
| 1252 | - if (isVisibleLeaderInfoComplete && isLegTypeKnown && lastBoatFix != null && lastBoatFix.speedWithBearing != null) {
|
|
| 1253 | - double advantageLineLengthInKm = 1.0; // TODO this should probably rather scale with the visible
|
|
| 1254 | - // area of the map; bug 616
|
|
| 1255 | - double distanceFromBoatPositionInKm = visibleLeaderInfo.getB().getBoatClass()
|
|
| 1256 | - .getHullLengthInMeters() / 1000.; // one hull length
|
|
| 1257 | - // implement and use Position.translateRhumb()
|
|
| 1258 | - double bearingOfBoatInDeg = lastBoatFix.speedWithBearing.bearingInDegrees;
|
|
| 1259 | - LatLng boatPosition = coordinateSystem.toLatLng(lastBoatFix.position);
|
|
| 1260 | - LatLng posAheadOfFirstBoat = calculatePositionAlongRhumbline(boatPosition,
|
|
| 1261 | - coordinateSystem.mapDegreeBearing(bearingOfBoatInDeg), distanceFromBoatPositionInKm);
|
|
| 1262 | - final WindDTO windFix = windDataForLegMiddle.windFixes.get(0);
|
|
| 1263 | - double bearingOfCombinedWindInDeg = windFix.trueWindBearingDeg;
|
|
| 1264 | - double rotatedBearingDeg1 = 0.0;
|
|
| 1265 | - double rotatedBearingDeg2 = 0.0;
|
|
| 1266 | - switch (legInfoDTO.legType) {
|
|
| 1267 | - case UPWIND:
|
|
| 1268 | - case DOWNWIND: {
|
|
| 1269 | - rotatedBearingDeg1 = bearingOfCombinedWindInDeg + 90.0;
|
|
| 1270 | - if (rotatedBearingDeg1 >= 360.0) {
|
|
| 1271 | - rotatedBearingDeg1 -= 360.0;
|
|
| 1272 | - }
|
|
| 1273 | - rotatedBearingDeg2 = bearingOfCombinedWindInDeg - 90.0;
|
|
| 1274 | - if (rotatedBearingDeg2 < 0.0) {
|
|
| 1275 | - rotatedBearingDeg2 += 360.0;
|
|
| 1276 | - }
|
|
| 1277 | - }
|
|
| 1278 | - break;
|
|
| 1279 | - case REACHING: {
|
|
| 1280 | - rotatedBearingDeg1 = legInfoDTO.legBearingInDegrees + 90.0;
|
|
| 1281 | - if (rotatedBearingDeg1 >= 360.0) {
|
|
| 1282 | - rotatedBearingDeg1 -= 360.0;
|
|
| 1283 | - }
|
|
| 1284 | - rotatedBearingDeg2 = legInfoDTO.legBearingInDegrees - 90.0;
|
|
| 1285 | - if (rotatedBearingDeg2 < 0.0) {
|
|
| 1286 | - rotatedBearingDeg2 += 360.0;
|
|
| 1287 | - }
|
|
| 1288 | - }
|
|
| 1289 | - break;
|
|
| 1290 | - }
|
|
| 1291 | - LatLng advantageLinePos1 = calculatePositionAlongRhumbline(posAheadOfFirstBoat,
|
|
| 1292 | - coordinateSystem.mapDegreeBearing(rotatedBearingDeg1), advantageLineLengthInKm / 2.0);
|
|
| 1293 | - LatLng advantageLinePos2 = calculatePositionAlongRhumbline(posAheadOfFirstBoat,
|
|
| 1294 | - coordinateSystem.mapDegreeBearing(rotatedBearingDeg2), advantageLineLengthInKm / 2.0);
|
|
| 1295 | - if (advantageLine == null) {
|
|
| 1296 | - PolylineOptions options = PolylineOptions.newInstance();
|
|
| 1297 | - options.setClickable(true);
|
|
| 1298 | - options.setGeodesic(true);
|
|
| 1299 | - options.setStrokeColor("#000000");
|
|
| 1300 | - options.setStrokeWeight(1);
|
|
| 1301 | - options.setStrokeOpacity(0.5);
|
|
| 1302 | -
|
|
| 1303 | - advantageLine = Polyline.newInstance(options);
|
|
| 1304 | - MVCArray<LatLng> pointsAsArray = MVCArray.newInstance();
|
|
| 1305 | - pointsAsArray.insertAt(0, advantageLinePos1);
|
|
| 1306 | - pointsAsArray.insertAt(1, advantageLinePos2);
|
|
| 1307 | - advantageLine.setPath(pointsAsArray);
|
|
| 1308 | -
|
|
| 1309 | - advantageLineMouseOverHandler = new AdvantageLineMouseOverMapHandler(
|
|
| 1310 | - bearingOfCombinedWindInDeg, new Date(windFix.measureTimepoint));
|
|
| 1311 | - advantageLine.addMouseOverHandler(advantageLineMouseOverHandler);
|
|
| 1312 | - advantageLine.addMouseOutMoveHandler(new MouseOutMapHandler() {
|
|
| 1313 | - @Override
|
|
| 1314 | - public void onEvent(MouseOutMapEvent event) {
|
|
| 1315 | - map.setTitle("");
|
|
| 1316 | - }
|
|
| 1317 | - });
|
|
| 1318 | - advantageLine.setMap(map);
|
|
| 1319 | - } else {
|
|
| 1320 | - advantageLine.getPath().removeAt(1);
|
|
| 1321 | - advantageLine.getPath().removeAt(0);
|
|
| 1322 | - advantageLine.getPath().insertAt(0, advantageLinePos1);
|
|
| 1323 | - advantageLine.getPath().insertAt(1, advantageLinePos2);
|
|
| 1324 | - advantageLineMouseOverHandler.setTrueWindBearing(bearingOfCombinedWindInDeg);
|
|
| 1325 | - advantageLineMouseOverHandler.setDate(new Date(windFix.measureTimepoint));
|
|
| 1326 | - }
|
|
| 1327 | - drawAdvantageLine = true;
|
|
| 1328 | - }
|
|
| 1329 | - }
|
|
| 1330 | - if (!drawAdvantageLine) {
|
|
| 1331 | - if (advantageLine != null) {
|
|
| 1332 | - advantageLine.setMap(null);
|
|
| 1333 | - advantageLine = null;
|
|
| 1334 | - }
|
|
| 1335 | - }
|
|
| 1336 | - }
|
|
| 1337 | - }
|
|
| 1338 | -
|
|
| 1339 | - private final StringBuilder windwardStartLineMarkToFirstMarkLineText = new StringBuilder();
|
|
| 1340 | - private final StringBuilder leewardStartLineMarkToFirstMarkLineText = new StringBuilder();
|
|
| 1341 | -
|
|
| 1342 | - private void showStartLineToFirstMarkTriangle(final CoursePositionsDTO courseDTO){
|
|
| 1343 | - final List<Position> startMarkPositions = courseDTO.getStartMarkPositions();
|
|
| 1344 | - final Position windwardStartLinePosition = startMarkPositions.get(0);
|
|
| 1345 | - final Position leewardStartLinePosition = startMarkPositions.get(1);
|
|
| 1346 | - final Position firstMarkPosition = courseDTO.waypointPositions.get(1);
|
|
| 1347 | - windwardStartLineMarkToFirstMarkLineText.replace(0, windwardStartLineMarkToFirstMarkLineText.length(),
|
|
| 1348 | - stringMessages.startLineToFirstMarkTriangle(numberFormatOneDecimal
|
|
| 1349 | - .format(windwardStartLinePosition.getDistance(firstMarkPosition)
|
|
| 1350 | - .getMeters())));
|
|
| 1351 | - leewardStartLineMarkToFirstMarkLineText.replace(0, leewardStartLineMarkToFirstMarkLineText.length(),
|
|
| 1352 | - stringMessages.startLineToFirstMarkTriangle(numberFormatOneDecimal
|
|
| 1353 | - .format(leewardStartLinePosition.getDistance(firstMarkPosition)
|
|
| 1354 | - .getMeters())));
|
|
| 1355 | - final LineInfoProvider windwardStartLineMarkToFirstMarkLineInfoProvider = new LineInfoProvider() {
|
|
| 1356 | - @Override
|
|
| 1357 | - public String getLineInfo() {
|
|
| 1358 | - return windwardStartLineMarkToFirstMarkLineText.toString();
|
|
| 1359 | - }
|
|
| 1360 | - };
|
|
| 1361 | - final LineInfoProvider leewardStartLineMarkToFirstMarkLineInfoProvider = new LineInfoProvider() {
|
|
| 1362 | - @Override
|
|
| 1363 | - public String getLineInfo() {
|
|
| 1364 | - return leewardStartLineMarkToFirstMarkLineText.toString();
|
|
| 1365 | - }
|
|
| 1366 | - };
|
|
| 1367 | - windwardStartLineMarkToFirstMarkLine = showOrRemoveOrUpdateLine(windwardStartLineMarkToFirstMarkLine, /* showLine */
|
|
| 1368 | - (settings.getHelpLinesSettings().isVisible(HelpLineTypes.STARTLINETOFIRSTMARKTRIANGLE))
|
|
| 1369 | - && startMarkPositions.size() > 1 && courseDTO.waypointPositions.size() > 1,
|
|
| 1370 | - windwardStartLinePosition, firstMarkPosition, windwardStartLineMarkToFirstMarkLineInfoProvider,
|
|
| 1371 | - "grey");
|
|
| 1372 | - leewardStartLineMarkToFirstMarkLine = showOrRemoveOrUpdateLine(leewardStartLineMarkToFirstMarkLine, /* showLine */
|
|
| 1373 | - (settings.getHelpLinesSettings().isVisible(HelpLineTypes.STARTLINETOFIRSTMARKTRIANGLE))
|
|
| 1374 | - && startMarkPositions.size() > 1 && courseDTO.waypointPositions.size() > 1,
|
|
| 1375 | - leewardStartLinePosition, firstMarkPosition, leewardStartLineMarkToFirstMarkLineInfoProvider,
|
|
| 1376 | - "grey");
|
|
| 1377 | - }
|
|
| 1378 | -
|
|
| 1379 | - private final StringBuilder startLineAdvantageText = new StringBuilder();
|
|
| 1380 | - private final StringBuilder finishLineAdvantageText = new StringBuilder();
|
|
| 1381 | - final LineInfoProvider startLineInfoProvider = new LineInfoProvider() {
|
|
| 1382 | - @Override
|
|
| 1383 | - public String getLineInfo() {
|
|
| 1384 | - return stringMessages.startLine()+startLineAdvantageText;
|
|
| 1385 | - }
|
|
| 1386 | - };
|
|
| 1387 | - final LineInfoProvider finishLineInfoProvider = new LineInfoProvider() {
|
|
| 1388 | - @Override
|
|
| 1389 | - public String getLineInfo() {
|
|
| 1390 | - return stringMessages.finishLine()+finishLineAdvantageText;
|
|
| 1391 | - }
|
|
| 1392 | - };
|
|
| 1393 | -
|
|
| 1394 | - private void showStartAndFinishAndCourseMiddleLines(final CoursePositionsDTO courseDTO) {
|
|
| 1395 | - if (map != null && courseDTO != null && courseDTO.course != null && courseDTO.course.waypoints != null &&
|
|
| 1396 | - !courseDTO.course.waypoints.isEmpty()) {
|
|
| 1397 | - // draw the start line
|
|
| 1398 | - final WaypointDTO startWaypoint = courseDTO.course.waypoints.get(0);
|
|
| 1399 | - updateCountdownCanvas(startWaypoint);
|
|
| 1400 | - final int numberOfStartWaypointMarks = courseDTO.getStartMarkPositions() == null ? 0 : courseDTO.getStartMarkPositions().size();
|
|
| 1401 | - final int numberOfFinishWaypointMarks = courseDTO.getFinishMarkPositions() == null ? 0 : courseDTO.getFinishMarkPositions().size();
|
|
| 1402 | - final Position startLineLeftPosition = numberOfStartWaypointMarks == 0 ? null : courseDTO.getStartMarkPositions().get(0);
|
|
| 1403 | - final Position startLineRightPosition = numberOfStartWaypointMarks < 2 ? null : courseDTO.getStartMarkPositions().get(1);
|
|
| 1404 | - if (courseDTO.startLineAngleToCombinedWind != null) {
|
|
| 1405 | - startLineAdvantageText.replace(0, startLineAdvantageText.length(), " "+stringMessages.lineAngleToWindAndAdvantage(
|
|
| 1406 | - NumberFormat.getFormat("0.0").format(courseDTO.startLineLengthInMeters),
|
|
| 1407 | - NumberFormat.getFormat("0.0").format(Math.abs(courseDTO.startLineAngleToCombinedWind)),
|
|
| 1408 | - courseDTO.startLineAdvantageousSide.name().charAt(0)+courseDTO.startLineAdvantageousSide.name().substring(1).toLowerCase(),
|
|
| 1409 | - NumberFormat.getFormat("0.0").format(courseDTO.startLineAdvantageInMeters)));
|
|
| 1410 | - } else {
|
|
| 1411 | - startLineAdvantageText.delete(0, startLineAdvantageText.length());
|
|
| 1412 | - }
|
|
| 1413 | - final boolean showStartLineBasedOnCurrentLeg = numberOfStartWaypointMarks == 2 && courseDTO.currentLegNumber <= 1;
|
|
| 1414 | - final boolean showFinishLineBasedOnCurrentLeg = numberOfFinishWaypointMarks == 2 && courseDTO.currentLegNumber == courseDTO.totalLegsCount;
|
|
| 1415 | - // show the line when STARTLINE is selected and the current leg is around the start leg,
|
|
| 1416 | - // or when COURSEGEOMETRY is selected and the finish line isn't equal and wouldn't be shown at the same time based on the current leg.
|
|
| 1417 | - // With this, if COURSEGEOMETRY is selected and start and finish line are equal, the start line will not be displayed if
|
|
| 1418 | - // based on the race progress the finish line is to be preferred, so only the finish line will be shown.
|
|
| 1419 | - final boolean reallyShowStartLine =
|
|
| 1420 | - (settings.getHelpLinesSettings().isVisible(HelpLineTypes.STARTLINE) && showStartLineBasedOnCurrentLeg) ||
|
|
| 1421 | - (settings.getHelpLinesSettings().isVisible(HelpLineTypes.COURSEGEOMETRY) &&
|
|
| 1422 | - (!showFinishLineBasedOnCurrentLeg || !startLineEqualsFinishLine(courseDTO)));
|
|
| 1423 | - // show the line when FINISHLINE is selected and the current leg is the last leg,
|
|
| 1424 | - // or when COURSEGEOMETRY is selected and the start line isn't equal or the current leg is the last leg.
|
|
| 1425 | - // With this, if COURSEGEOMETRY is selected and start and finish line are equal, the start line will be displayed unless
|
|
| 1426 | - // the finish line should take precedence based on race progress.
|
|
| 1427 | - final boolean reallyShowFinishLine = showFinishLineBasedOnCurrentLeg &&
|
|
| 1428 | - (!showStartLineBasedOnCurrentLeg || !startLineEqualsFinishLine(courseDTO)) &&
|
|
| 1429 | - (settings.getHelpLinesSettings().isVisible(HelpLineTypes.FINISHLINE) && showFinishLineBasedOnCurrentLeg) ||
|
|
| 1430 | - (settings.getHelpLinesSettings().isVisible(HelpLineTypes.COURSEGEOMETRY) &&
|
|
| 1431 | - (!startLineEqualsFinishLine(courseDTO) || showFinishLineBasedOnCurrentLeg));
|
|
| 1432 | - startLine = showOrRemoveOrUpdateLine(startLine, reallyShowStartLine,
|
|
| 1433 | - startLineLeftPosition, startLineRightPosition, startLineInfoProvider, "#ffffff");
|
|
| 1434 | - // draw the finish line
|
|
| 1435 | - final Position finishLineLeftPosition = numberOfFinishWaypointMarks == 0 ? null : courseDTO.getFinishMarkPositions().get(0);
|
|
| 1436 | - final Position finishLineRightPosition = numberOfFinishWaypointMarks < 2 ? null : courseDTO.getFinishMarkPositions().get(1);
|
|
| 1437 | - if (courseDTO.finishLineAngleToCombinedWind != null) {
|
|
| 1438 | - finishLineAdvantageText.replace(0, finishLineAdvantageText.length(), " "+stringMessages.lineAngleToWindAndAdvantage(
|
|
| 1439 | - NumberFormat.getFormat("0.0").format(courseDTO.finishLineLengthInMeters),
|
|
| 1440 | - NumberFormat.getFormat("0.0").format(Math.abs(courseDTO.finishLineAngleToCombinedWind)),
|
|
| 1441 | - courseDTO.finishLineAdvantageousSide.name().charAt(0)+courseDTO.finishLineAdvantageousSide.name().substring(1).toLowerCase(),
|
|
| 1442 | - NumberFormat.getFormat("0.0").format(courseDTO.finishLineAdvantageInMeters)));
|
|
| 1443 | - } else {
|
|
| 1444 | - finishLineAdvantageText.delete(0, finishLineAdvantageText.length());
|
|
| 1445 | - }
|
|
| 1446 | - finishLine = showOrRemoveOrUpdateLine(finishLine, reallyShowFinishLine,
|
|
| 1447 | - finishLineLeftPosition, finishLineRightPosition, finishLineInfoProvider, "#000000");
|
|
| 1448 | - // the control point pairs for which we already decided whether or not
|
|
| 1449 | - // to show a course middle line for; values tell whether to show the line and for which zero-based
|
|
| 1450 | - // start waypoint index to do so; when for an equal control point pair multiple decisions with different
|
|
| 1451 | - // outcome are made, a decision to show the line overrules the decision to not show it (OR-semantics)
|
|
| 1452 | - final Map<Set<ControlPointDTO>, Pair<Boolean, Integer>> keysAlreadyHandled = new HashMap<>();
|
|
| 1453 | - for (int zeroBasedIndexOfStartWaypoint = 0; zeroBasedIndexOfStartWaypoint<courseDTO.waypointPositions.size()-1; zeroBasedIndexOfStartWaypoint++) {
|
|
| 1454 | - final Set<ControlPointDTO> key = getCourseMiddleLinesKey(courseDTO, zeroBasedIndexOfStartWaypoint);
|
|
| 1455 | - boolean showCourseMiddleLine = keysAlreadyHandled.containsKey(key) && keysAlreadyHandled.get(key).getA() ||
|
|
| 1456 | - settings.getHelpLinesSettings().isVisible(HelpLineTypes.COURSEGEOMETRY) ||
|
|
| 1457 | - (settings.getHelpLinesSettings().isVisible(HelpLineTypes.COURSEMIDDLELINE)
|
|
| 1458 | - && courseDTO.currentLegNumber > 0
|
|
| 1459 | - && courseDTO.currentLegNumber-1 == zeroBasedIndexOfStartWaypoint);
|
|
| 1460 | - keysAlreadyHandled.put(key, new Pair<>(showCourseMiddleLine, zeroBasedIndexOfStartWaypoint));
|
|
| 1461 | - }
|
|
| 1462 | - Set<Set<ControlPointDTO>> keysToConsider = new HashSet<>(keysAlreadyHandled.keySet());
|
|
| 1463 | - keysToConsider.addAll(courseMiddleLines.keySet());
|
|
| 1464 | - for (final Set<ControlPointDTO> key : keysToConsider) {
|
|
| 1465 | - final int zeroBasedIndexOfStartWaypoint = keysAlreadyHandled.containsKey(key) ?
|
|
| 1466 | - keysAlreadyHandled.get(key).getB() : 0; // if not handled, the line will be removed, so the waypoint index doesn't matter
|
|
| 1467 | - final Pair<Boolean, Integer> showLineAndZeroBasedIndexOfStartWaypoint = keysAlreadyHandled.get(key);
|
|
| 1468 | - final boolean showCourseMiddleLine = showLineAndZeroBasedIndexOfStartWaypoint != null && showLineAndZeroBasedIndexOfStartWaypoint.getA();
|
|
| 1469 | - courseMiddleLines.put(key, showOrRemoveCourseMiddleLine(courseDTO, courseMiddleLines.get(key), zeroBasedIndexOfStartWaypoint, showCourseMiddleLine));
|
|
| 1470 | - }
|
|
| 1471 | - }
|
|
| 1472 | - }
|
|
| 1473 | -
|
|
| 1474 | - private boolean startLineEqualsFinishLine(CoursePositionsDTO courseDTO) {
|
|
| 1475 | - final List<WaypointDTO> waypoints;
|
|
| 1476 | - return courseDTO != null && courseDTO.course != null &&
|
|
| 1477 | - (waypoints = courseDTO.course.waypoints) != null &&
|
|
| 1478 | - waypoints.get(0).controlPoint.equals(waypoints.get(waypoints.size()-1).controlPoint);
|
|
| 1479 | - }
|
|
| 1480 | -
|
|
| 1481 | - /**
|
|
| 1482 | - * Given a zero-based index into <code>courseDTO</code>'s {@link RaceCourseDTO#waypoints waypoints list} that denotes the start
|
|
| 1483 | - * waypoint of the leg in question, returns a key that can be used for the {@link #courseMiddleLines} map, consisting of a set
|
|
| 1484 | - * that holds the two {@link ControlPointDTO}s representing the start and finish control point of that leg.
|
|
| 1485 | - */
|
|
| 1486 | - private Set<ControlPointDTO> getCourseMiddleLinesKey(final CoursePositionsDTO courseDTO,
|
|
| 1487 | - final int zeroBasedIndexOfStartWaypoint) {
|
|
| 1488 | - ControlPointDTO startControlPoint = courseDTO.course.waypoints.get(zeroBasedIndexOfStartWaypoint).controlPoint;
|
|
| 1489 | - ControlPointDTO endControlPoint = courseDTO.course.waypoints.get(zeroBasedIndexOfStartWaypoint+1).controlPoint;
|
|
| 1490 | - final Set<ControlPointDTO> key = new HashSet<>();
|
|
| 1491 | - key.add(startControlPoint);
|
|
| 1492 | - key.add(endControlPoint);
|
|
| 1493 | - return key;
|
|
| 1494 | - }
|
|
| 1495 | -
|
|
| 1496 | - /**
|
|
| 1497 | - * @param showLine
|
|
| 1498 | - * tells whether or not to show the line; if the <code>lineToShowOrRemoveOrUpdate</code> references a
|
|
| 1499 | - * line but the line shall not be shown, the line is removed from the map; conversely, if the line is not
|
|
| 1500 | - * yet shown but shall be, a new line is created, added to the map and returned. If the line is shown and
|
|
| 1501 | - * shall continue to be shown, the line is returned after updating its vertex coordinates.
|
|
| 1502 | - * @return <code>null</code> if the line is not shown; the polyline object representing the line being displayed
|
|
| 1503 | - * otherwise
|
|
| 1504 | - */
|
|
| 1505 | - private Polyline showOrRemoveCourseMiddleLine(final CoursePositionsDTO courseDTO, Polyline lineToShowOrRemoveOrUpdate,
|
|
| 1506 | - final int zeroBasedIndexOfStartWaypoint, final boolean showLine) {
|
|
| 1507 | - final Position position1DTO = courseDTO.waypointPositions.get(zeroBasedIndexOfStartWaypoint);
|
|
| 1508 | - final Position position2DTO = courseDTO.waypointPositions.get(zeroBasedIndexOfStartWaypoint+1);
|
|
| 1509 | - final LineInfoProvider lineInfoProvider = new LineInfoProvider() {
|
|
| 1510 | - @Override
|
|
| 1511 | - public String getLineInfo() {
|
|
| 1512 | - final StringBuilder sb = new StringBuilder();
|
|
| 1513 | - sb.append(stringMessages.courseMiddleLine());
|
|
| 1514 | - sb.append('\n');
|
|
| 1515 | - sb.append(NumberFormat.getFormat("0").format(
|
|
| 1516 | - Math.abs(position1DTO.getDistance(position2DTO).getMeters()))+stringMessages.metersUnit());
|
|
| 1517 | - if (lastCombinedWindTrackInfoDTO != null) {
|
|
| 1518 | - final WindTrackInfoDTO windTrackAtLegMiddle = lastCombinedWindTrackInfoDTO.getCombinedWindOnLegMiddle(zeroBasedIndexOfStartWaypoint);
|
|
| 1519 | - if (windTrackAtLegMiddle != null && windTrackAtLegMiddle.windFixes != null && !windTrackAtLegMiddle.windFixes.isEmpty()) {
|
|
| 1520 | - WindDTO windAtLegMiddle = windTrackAtLegMiddle.windFixes.get(0);
|
|
| 1521 | - final double legBearingDeg = position1DTO.getBearingGreatCircle(position2DTO).getDegrees();
|
|
| 1522 | - final String diff = NumberFormat.getFormat("0.0").format(
|
|
| 1523 | - Math.min(Math.abs(windAtLegMiddle.dampenedTrueWindBearingDeg-legBearingDeg),
|
|
| 1524 | - Math.abs(windAtLegMiddle.dampenedTrueWindFromDeg-legBearingDeg)));
|
|
| 1525 | - sb.append(", ");
|
|
| 1526 | - sb.append(stringMessages.degreesToWind(diff));
|
|
| 1527 | - }
|
|
| 1528 | - }
|
|
| 1529 | - return sb.toString();
|
|
| 1530 | - }
|
|
| 1531 | - };
|
|
| 1532 | - return showOrRemoveOrUpdateLine(lineToShowOrRemoveOrUpdate, showLine, position1DTO, position2DTO, lineInfoProvider, "#2268a0");
|
|
| 1533 | - }
|
|
| 1534 | -
|
|
| 1535 | - private interface LineInfoProvider {
|
|
| 1536 | - String getLineInfo();
|
|
| 1537 | - }
|
|
| 1538 | -
|
|
| 1539 | - /**
|
|
| 1540 | - * @param showLine
|
|
| 1541 | - * tells whether or not to show the line; if the <code>lineToShowOrRemoveOrUpdate</code> references a
|
|
| 1542 | - * line but the line shall not be shown, the line is removed from the map; conversely, if the line is not
|
|
| 1543 | - * yet shown but shall be, a new line is created, added to the map and returned. If the line is shown and
|
|
| 1544 | - * shall continue to be shown, the line is returned after updating its vertex coordinates.
|
|
| 1545 | - * @return <code>null</code> if the line is not shown; the polyline object representing the line being displayed
|
|
| 1546 | - * otherwise
|
|
| 1547 | - */
|
|
| 1548 | - private Polyline showOrRemoveOrUpdateLine(Polyline lineToShowOrRemoveOrUpdate, final boolean showLine,
|
|
| 1549 | - final Position position1DTO, final Position position2DTO, final LineInfoProvider lineInfoProvider, String lineColorRGB) {
|
|
| 1550 | - if (showLine) {
|
|
| 1551 | - LatLng courseMiddleLinePoint1 = coordinateSystem.toLatLng(position1DTO);
|
|
| 1552 | - LatLng courseMiddleLinePoint2 = coordinateSystem.toLatLng(position2DTO);
|
|
| 1553 | - final MVCArray<LatLng> pointsAsArray;
|
|
| 1554 | - if (lineToShowOrRemoveOrUpdate == null) {
|
|
| 1555 | - PolylineOptions options = PolylineOptions.newInstance();
|
|
| 1556 | - options.setClickable(true);
|
|
| 1557 | - options.setGeodesic(true);
|
|
| 1558 | - options.setStrokeColor(lineColorRGB);
|
|
| 1559 | - options.setStrokeWeight(1);
|
|
| 1560 | - options.setStrokeOpacity(1.0);
|
|
| 1561 | - pointsAsArray = MVCArray.newInstance();
|
|
| 1562 | - lineToShowOrRemoveOrUpdate = Polyline.newInstance(options);
|
|
| 1563 | - lineToShowOrRemoveOrUpdate.setPath(pointsAsArray);
|
|
| 1564 | - lineToShowOrRemoveOrUpdate.addMouseOverHandler(new MouseOverMapHandler() {
|
|
| 1565 | - @Override
|
|
| 1566 | - public void onEvent(MouseOverMapEvent event) {
|
|
| 1567 | - map.setTitle(lineInfoProvider.getLineInfo());
|
|
| 1568 | - }
|
|
| 1569 | - });
|
|
| 1570 | - lineToShowOrRemoveOrUpdate.addMouseOutMoveHandler(new MouseOutMapHandler() {
|
|
| 1571 | - @Override
|
|
| 1572 | - public void onEvent(MouseOutMapEvent event) {
|
|
| 1573 | - map.setTitle("");
|
|
| 1574 | - }
|
|
| 1575 | - });
|
|
| 1576 | - lineToShowOrRemoveOrUpdate.setMap(map);
|
|
| 1577 | -
|
|
| 1578 | - } else {
|
|
| 1579 | - pointsAsArray = lineToShowOrRemoveOrUpdate.getPath();
|
|
| 1580 | - pointsAsArray.removeAt(1);
|
|
| 1581 | - pointsAsArray.removeAt(0);
|
|
| 1582 | - }
|
|
| 1583 | - adjustInfoOverlayForVisibleLine(lineToShowOrRemoveOrUpdate, position1DTO, position2DTO, lineInfoProvider);
|
|
| 1584 | - pointsAsArray.insertAt(0, courseMiddleLinePoint1);
|
|
| 1585 | - pointsAsArray.insertAt(1, courseMiddleLinePoint2);
|
|
| 1586 | - } else {
|
|
| 1587 | - if (lineToShowOrRemoveOrUpdate != null) {
|
|
| 1588 | - lineToShowOrRemoveOrUpdate.setMap(null);
|
|
| 1589 | - adjustInfoOverlayForRemovedLine(lineToShowOrRemoveOrUpdate);
|
|
| 1590 | - lineToShowOrRemoveOrUpdate = null;
|
|
| 1591 | - }
|
|
| 1592 | - }
|
|
| 1593 | - return lineToShowOrRemoveOrUpdate;
|
|
| 1594 | - }
|
|
| 1595 | -
|
|
| 1596 | - private void adjustInfoOverlayForRemovedLine(Polyline lineToShowOrRemoveOrUpdate) {
|
|
| 1597 | - SmallTransparentInfoOverlay infoOverlay = infoOverlaysForLinesForCourseGeometry.remove(lineToShowOrRemoveOrUpdate);
|
|
| 1598 | - if (infoOverlay != null) {
|
|
| 1599 | - infoOverlay.removeFromMap();
|
|
| 1600 | - }
|
|
| 1601 | - }
|
|
| 1602 | -
|
|
| 1603 | - private void adjustInfoOverlayForVisibleLine(Polyline lineToShowOrRemoveOrUpdate, final Position position1DTO,
|
|
| 1604 | - final Position position2DTO, final LineInfoProvider lineInfoProvider) {
|
|
| 1605 | - SmallTransparentInfoOverlay infoOverlay = infoOverlaysForLinesForCourseGeometry.get(lineToShowOrRemoveOrUpdate);
|
|
| 1606 | - if (getSettings().getHelpLinesSettings().isVisible(HelpLineTypes.COURSEGEOMETRY)) {
|
|
| 1607 | - if (infoOverlay == null) {
|
|
| 1608 | - infoOverlay = new SmallTransparentInfoOverlay(map, RaceMapOverlaysZIndexes.INFO_OVERLAY_ZINDEX, lineInfoProvider.getLineInfo(), coordinateSystem);
|
|
| 1609 | - infoOverlaysForLinesForCourseGeometry.put(lineToShowOrRemoveOrUpdate, infoOverlay);
|
|
| 1610 | - infoOverlay.addToMap();
|
|
| 1611 | - } else {
|
|
| 1612 | - infoOverlay.setInfoText(lineInfoProvider.getLineInfo());
|
|
| 1613 | - }
|
|
| 1614 | - infoOverlay.setPosition(position1DTO.translateGreatCircle(position1DTO.getBearingGreatCircle(position2DTO),
|
|
| 1615 | - position1DTO.getDistance(position2DTO).scale(0.5)), /* transition time */ -1);
|
|
| 1616 | - infoOverlay.draw();
|
|
| 1617 | - } else {
|
|
| 1618 | - if (infoOverlay != null) {
|
|
| 1619 | - infoOverlay.removeFromMap();
|
|
| 1620 | - infoOverlaysForLinesForCourseGeometry.remove(lineToShowOrRemoveOrUpdate);
|
|
| 1621 | - }
|
|
| 1622 | - }
|
|
| 1623 | - }
|
|
| 1624 | -
|
|
| 1625 | - /**
|
|
| 1626 | - * If, according to {@link #lastRaceTimesInfo} and {@link #timer} the race is
|
|
| 1627 | - * still in the pre-start phase, show a {@link SmallTransparentInfoOverlay} at the
|
|
| 1628 | - * start line that shows the count down.
|
|
| 1629 | - */
|
|
| 1630 | - private void updateCountdownCanvas(WaypointDTO startWaypoint) {
|
|
| 1631 | - if (!settings.isShowSelectedCompetitorsInfo() || startWaypoint == null || Util.isEmpty(startWaypoint.controlPoint.getMarks())
|
|
| 1632 | - || lastRaceTimesInfo == null || lastRaceTimesInfo.startOfRace == null || timer.getTime().after(lastRaceTimesInfo.startOfRace)) {
|
|
| 1633 | - if (countDownOverlay != null) {
|
|
| 1634 | - countDownOverlay.removeFromMap();
|
|
| 1635 | - countDownOverlay = null;
|
|
| 1636 | - }
|
|
| 1637 | - } else {
|
|
| 1638 | - long timeToStartInMs = lastRaceTimesInfo.startOfRace.getTime() - timer.getTime().getTime();
|
|
| 1639 | - String countDownText = timeToStartInMs > 1000 ? stringMessages.timeToStart(DateAndTimeFormatterUtil
|
|
| 1640 | - .formatElapsedTime(timeToStartInMs)) : stringMessages.start();
|
|
| 1641 | - if (countDownOverlay == null) {
|
|
| 1642 | - countDownOverlay = new SmallTransparentInfoOverlay(map, RaceMapOverlaysZIndexes.INFO_OVERLAY_ZINDEX,
|
|
| 1643 | - countDownText, coordinateSystem);
|
|
| 1644 | - countDownOverlay.addToMap();
|
|
| 1645 | - } else {
|
|
| 1646 | - countDownOverlay.setInfoText(countDownText);
|
|
| 1647 | - }
|
|
| 1648 | - countDownOverlay.setPosition(startWaypoint.controlPoint.getMarks().iterator().next().position, /* transition time */ -1);
|
|
| 1649 | - countDownOverlay.draw();
|
|
| 1650 | - }
|
|
| 1651 | - }
|
|
| 1652 | -
|
|
| 1653 | - // Google scales coordinates so that the globe-tile has mercator-latitude [-pi, +pi], i.e. tile height of 2*pi
|
|
| 1654 | - // mercator-latitude pi corresponds to geo-latitude of approx. 85.0998 (where Google cuts off the map visualization)
|
|
| 1655 | - // official documentation: http://developers.google.com/maps/documentation/javascript/maptypes#TileCoordinates
|
|
| 1656 | - private double getMercatorLatitude(double lat) {
|
|
| 1657 | - // cutting-off for latitudes close to +-90 degrees is recommended (to avoid division by zero)
|
|
| 1658 | - double sine = Math.max(-0.9999, Math.min(0.9999, Math.sin(Math.PI * lat / 180)));
|
|
| 1659 | - return Math.log((1 + sine) / (1 - sine)) / 2;
|
|
| 1660 | - }
|
|
| 1661 | -
|
|
| 1662 | - private int getZoomLevel(LatLngBounds bounds) {
|
|
| 1663 | - int GLOBE_PXSIZE = 256; // a constant in Google's map projection
|
|
| 1664 | - int MAX_ZOOM = 20; // maximum zoom-level that should be automatically selected
|
|
| 1665 | - double LOG2 = Math.log(2.0);
|
|
| 1666 | - double deltaLng = bounds.getNorthEast().getLongitude() - bounds.getSouthWest().getLongitude();
|
|
| 1667 | - double deltaLat = getMercatorLatitude(bounds.getNorthEast().getLatitude()) - getMercatorLatitude(bounds.getSouthWest().getLatitude());
|
|
| 1668 | - if ((deltaLng == 0) && (deltaLat == 0)) {
|
|
| 1669 | - return MAX_ZOOM;
|
|
| 1670 | - }
|
|
| 1671 | - if (deltaLng < 0) {
|
|
| 1672 | - deltaLng += 360;
|
|
| 1673 | - }
|
|
| 1674 | - int zoomLng = (int) Math.floor(Math.log(map.getDiv().getClientWidth() * 360 / deltaLng / GLOBE_PXSIZE) / LOG2);
|
|
| 1675 | - int zoomLat = (int) Math.floor(Math.log(map.getDiv().getClientHeight() * 2 * Math.PI / deltaLat / GLOBE_PXSIZE) / LOG2);
|
|
| 1676 | - return Math.min(Math.min(zoomLat, zoomLng), MAX_ZOOM);
|
|
| 1677 | - }
|
|
| 1678 | -
|
|
| 1679 | - private void zoomMapToNewBounds(LatLngBounds newBounds) {
|
|
| 1680 | - if (newBounds != null) {
|
|
| 1681 | - LatLngBounds currentMapBounds;
|
|
| 1682 | - if (map.getBounds() == null
|
|
| 1683 | - || !BoundsUtil.contains((currentMapBounds = map.getBounds()), newBounds)
|
|
| 1684 | - || graticuleAreaRatio(currentMapBounds, newBounds) > 10) {
|
|
| 1685 | - // only change bounds if the new bounds don't fit into the current map zoom
|
|
| 1686 | - Iterable<ZoomTypes> oldZoomSettings = settings.getZoomSettings().getTypesToConsiderOnZoom();
|
|
| 1687 | - setAutoZoomInProgress(true);
|
|
| 1688 | - autoZoomLatLngBounds = newBounds;
|
|
| 1689 | - int newZoomLevel = getZoomLevel(autoZoomLatLngBounds);
|
|
| 1690 | - if (newZoomLevel != map.getZoom()) {
|
|
| 1691 | - // distinguish between zoom-in and zoom-out, because the sequence of panTo() and setZoom()
|
|
| 1692 | - // appears different on the screen due to map-animations
|
|
| 1693 | - // following sequences keep the selected boats allways visible:
|
|
| 1694 | - // zoom-in : 1. panTo(), 2. setZoom()
|
|
| 1695 | - // zoom-out: 1. setZoom(), 2. panTo()
|
|
| 1696 | - autoZoomIn = newZoomLevel > map.getZoom();
|
|
| 1697 | - autoZoomOut = !autoZoomIn;
|
|
| 1698 | - autoZoomLevel = newZoomLevel;
|
|
| 1699 | - removeTransitions();
|
|
| 1700 | - if (autoZoomIn) {
|
|
| 1701 | - map.panTo(autoZoomLatLngBounds.getCenter());
|
|
| 1702 | - } else {
|
|
| 1703 | - map.setZoom(autoZoomLevel);
|
|
| 1704 | - }
|
|
| 1705 | - } else {
|
|
| 1706 | - map.panTo(autoZoomLatLngBounds.getCenter());
|
|
| 1707 | - }
|
|
| 1708 | - settings.getZoomSettings().setTypesToConsiderOnZoom(oldZoomSettings);
|
|
| 1709 | - setAutoZoomInProgress(false);
|
|
| 1710 | - }
|
|
| 1711 | - }
|
|
| 1712 | - }
|
|
| 1713 | -
|
|
| 1714 | - private double graticuleAreaRatio(LatLngBounds containing, LatLngBounds contained) {
|
|
| 1715 | - assert BoundsUtil.contains(containing, contained);
|
|
| 1716 | - double containingAreaRatio = getGraticuleArea(containing) / getGraticuleArea(contained);
|
|
| 1717 | - return containingAreaRatio;
|
|
| 1718 | - }
|
|
| 1719 | -
|
|
| 1720 | - /**
|
|
| 1721 | - * A much simplified "area" calculation for a {@link Bounds} object, multiplying the differences in latitude and longitude degrees.
|
|
| 1722 | - * The result therefore is in the order of magnitude of 60*60 square nautical miles.
|
|
| 1723 | - */
|
|
| 1724 | - private double getGraticuleArea(LatLngBounds bounds) {
|
|
| 1725 | - return ((BoundsUtil.isCrossesDateLine(bounds) ? bounds.getNorthEast().getLongitude()+360 : bounds.getNorthEast().getLongitude())-bounds.getSouthWest().getLongitude()) *
|
|
| 1726 | - (bounds.getNorthEast().getLatitude() - bounds.getSouthWest().getLatitude());
|
|
| 1727 | - }
|
|
| 1728 | -
|
|
| 1729 | - private void setAutoZoomInProgress(boolean autoZoomInProgress) {
|
|
| 1730 | - this.autoZoomInProgress = autoZoomInProgress;
|
|
| 1731 | - }
|
|
| 1732 | -
|
|
| 1733 | - boolean isAutoZoomInProgress() {
|
|
| 1734 | - return autoZoomInProgress;
|
|
| 1735 | - }
|
|
| 1736 | -
|
|
| 1737 | - /**
|
|
| 1738 | - * @param timeForPositionTransitionMillis use -1 to not animate the position transition, e.g., during map zoom or non-play
|
|
| 1739 | - */
|
|
| 1740 | - private boolean updateBoatCanvasForCompetitor(CompetitorDTO competitorDTO, Date date, long timeForPositionTransitionMillis) {
|
|
| 1741 | - boolean usedExistingCanvas = false;
|
|
| 1742 | - GPSFixDTO lastBoatFix = getBoatFix(competitorDTO, date);
|
|
| 1743 | - if (lastBoatFix != null) {
|
|
| 1744 | - BoatOverlay boatOverlay = boatOverlays.get(competitorDTO);
|
|
| 1745 | - if (boatOverlay == null) {
|
|
| 1746 | - boatOverlay = createBoatOverlay(RaceMapOverlaysZIndexes.BOATS_ZINDEX, competitorDTO, displayHighlighted(competitorDTO));
|
|
| 1747 | - boatOverlays.put(competitorDTO, boatOverlay);
|
|
| 1748 | - boatOverlay.setSelected(displayHighlighted(competitorDTO));
|
|
| 1749 | - boatOverlay.setBoatFix(lastBoatFix, timeForPositionTransitionMillis);
|
|
| 1750 | - boatOverlay.addToMap();
|
|
| 1751 | - } else {
|
|
| 1752 | - usedExistingCanvas = true;
|
|
| 1753 | - boatOverlay.setSelected(displayHighlighted(competitorDTO));
|
|
| 1754 | - boatOverlay.setBoatFix(lastBoatFix, timeForPositionTransitionMillis);
|
|
| 1755 | - boatOverlay.draw();
|
|
| 1756 | - }
|
|
| 1757 | - }
|
|
| 1758 | -
|
|
| 1759 | - return usedExistingCanvas;
|
|
| 1760 | - }
|
|
| 1761 | -
|
|
| 1762 | - private boolean displayHighlighted(CompetitorDTO competitorDTO) {
|
|
| 1763 | - return !settings.isShowOnlySelectedCompetitors() && competitorSelection.isSelected(competitorDTO);
|
|
| 1764 | - }
|
|
| 1765 | -
|
|
| 1766 | - protected CourseMarkOverlay createCourseMarkOverlay(int zIndex, final MarkDTO markDTO) {
|
|
| 1767 | - final CourseMarkOverlay courseMarkOverlay = new CourseMarkOverlay(map, zIndex, markDTO, coordinateSystem);
|
|
| 1768 | - courseMarkOverlay.addClickHandler(new ClickMapHandler() {
|
|
| 1769 | - @Override
|
|
| 1770 | - public void onEvent(ClickMapEvent event) {
|
|
| 1771 | - LatLng latlng = courseMarkOverlay.getMarkLatLngPosition();
|
|
| 1772 | - showMarkInfoWindow(markDTO, latlng);
|
|
| 1773 | - }
|
|
| 1774 | - });
|
|
| 1775 | - return courseMarkOverlay;
|
|
| 1776 | - }
|
|
| 1777 | -
|
|
| 1778 | - private CompetitorInfoOverlay createCompetitorInfoOverlay(int zIndex, final CompetitorDTO competitorDTO) {
|
|
| 1779 | - String infoText = competitorDTO.getSailID() == null || competitorDTO.getSailID().isEmpty() ? competitorDTO.getName() : competitorDTO.getSailID();
|
|
| 1780 | - return new CompetitorInfoOverlay(map, zIndex, competitorSelection.getColor(competitorDTO, raceIdentifier), infoText, coordinateSystem);
|
|
| 1781 | - }
|
|
| 1782 | -
|
|
| 1783 | - private BoatOverlay createBoatOverlay(int zIndex, final CompetitorDTO competitorDTO, boolean highlighted) {
|
|
| 1784 | - final BoatOverlay boatCanvas = new BoatOverlay(map, zIndex, competitorDTO, competitorSelection.getColor(competitorDTO, raceIdentifier), coordinateSystem);
|
|
| 1785 | - boatCanvas.setSelected(highlighted);
|
|
| 1786 | - boatCanvas.addClickHandler(new ClickMapHandler() {
|
|
| 1787 | - @Override
|
|
| 1788 | - public void onEvent(ClickMapEvent event) {
|
|
| 1789 | - if (lastInfoWindow != null) {
|
|
| 1790 | - lastInfoWindow.close();
|
|
| 1791 | - }
|
|
| 1792 | - GPSFixDTO latestFixForCompetitor = getBoatFix(competitorDTO, timer.getTime());
|
|
| 1793 | - LatLng where = coordinateSystem.toLatLng(latestFixForCompetitor.position);
|
|
| 1794 | - InfoWindowOptions options = InfoWindowOptions.newInstance();
|
|
| 1795 | - InfoWindow infoWindow = InfoWindow.newInstance(options);
|
|
| 1796 | - infoWindow.setContent(getInfoWindowContent(competitorDTO, latestFixForCompetitor));
|
|
| 1797 | - infoWindow.setPosition(where);
|
|
| 1798 | - lastInfoWindow = infoWindow;
|
|
| 1799 | - infoWindow.open(map);
|
|
| 1800 | - }
|
|
| 1801 | - });
|
|
| 1802 | -
|
|
| 1803 | - boatCanvas.addMouseOverHandler(new MouseOverMapHandler() {
|
|
| 1804 | - @Override
|
|
| 1805 | - public void onEvent(MouseOverMapEvent event) {
|
|
| 1806 | - map.setTitle(competitorDTO.getSailID());
|
|
| 1807 | - }
|
|
| 1808 | - });
|
|
| 1809 | - boatCanvas.addMouseOutMoveHandler(new MouseOutMapHandler() {
|
|
| 1810 | - @Override
|
|
| 1811 | - public void onEvent(MouseOutMapEvent event) {
|
|
| 1812 | - map.setTitle("");
|
|
| 1813 | - }
|
|
| 1814 | - });
|
|
| 1815 | -
|
|
| 1816 | - return boatCanvas;
|
|
| 1817 | - }
|
|
| 1818 | -
|
|
| 1819 | - protected WindSensorOverlay createWindSensorOverlay(int zIndex, final WindSource windSource, final WindTrackInfoDTO windTrackInfoDTO) {
|
|
| 1820 | - final WindSensorOverlay windSensorOverlay = new WindSensorOverlay(map, zIndex, raceMapImageManager, stringMessages, coordinateSystem);
|
|
| 1821 | - windSensorOverlay.setWindInfo(windTrackInfoDTO, windSource);
|
|
| 1822 | - windSensorOverlay.addClickHandler(new ClickMapHandler() {
|
|
| 1823 | - @Override
|
|
| 1824 | - public void onEvent(ClickMapEvent event) {
|
|
| 1825 | - showWindSensorInfoWindow(windSensorOverlay);
|
|
| 1826 | - }
|
|
| 1827 | - });
|
|
| 1828 | - return windSensorOverlay;
|
|
| 1829 | - }
|
|
| 1830 | -
|
|
| 1831 | - private void showMarkInfoWindow(MarkDTO markDTO, LatLng position) {
|
|
| 1832 | - if(lastInfoWindow != null) {
|
|
| 1833 | - lastInfoWindow.close();
|
|
| 1834 | - }
|
|
| 1835 | - InfoWindowOptions options = InfoWindowOptions.newInstance();
|
|
| 1836 | - InfoWindow infoWindow = InfoWindow.newInstance(options);
|
|
| 1837 | - infoWindow.setContent(getInfoWindowContent(markDTO));
|
|
| 1838 | - infoWindow.setPosition(position);
|
|
| 1839 | - lastInfoWindow = infoWindow;
|
|
| 1840 | - infoWindow.open(map);
|
|
| 1841 | - }
|
|
| 1842 | -
|
|
| 1843 | - private void showCompetitorInfoWindow(final CompetitorDTO competitorDTO, LatLng where) {
|
|
| 1844 | - if(lastInfoWindow != null) {
|
|
| 1845 | - lastInfoWindow.close();
|
|
| 1846 | - }
|
|
| 1847 | - GPSFixDTO latestFixForCompetitor = getBoatFix(competitorDTO, timer.getTime());
|
|
| 1848 | - // TODO find close fix where the mouse was; see BUG 470
|
|
| 1849 | - InfoWindowOptions options = InfoWindowOptions.newInstance();
|
|
| 1850 | - InfoWindow infoWindow = InfoWindow.newInstance(options);
|
|
| 1851 | - infoWindow.setContent(getInfoWindowContent(competitorDTO, latestFixForCompetitor));
|
|
| 1852 | - infoWindow.setPosition(where);
|
|
| 1853 | - lastInfoWindow = infoWindow;
|
|
| 1854 | - infoWindow.open(map);
|
|
| 1855 | - }
|
|
| 1856 | -
|
|
| 1857 | - private String formatPosition(double lat, double lng) {
|
|
| 1858 | - NumberFormat numberFormat = NumberFormat.getFormat("0.00000");
|
|
| 1859 | - String result = numberFormat.format(lat) + " lat, " + numberFormat.format(lng) + " lng";
|
|
| 1860 | - return result;
|
|
| 1861 | - }
|
|
| 1862 | -
|
|
| 1863 | - private void showWindSensorInfoWindow(final WindSensorOverlay windSensorOverlay) {
|
|
| 1864 | - WindSource windSource = windSensorOverlay.getWindSource();
|
|
| 1865 | - WindTrackInfoDTO windTrackInfoDTO = windSensorOverlay.getWindTrackInfoDTO();
|
|
| 1866 | - WindDTO windDTO = windTrackInfoDTO.windFixes.get(0);
|
|
| 1867 | - if(windDTO != null && windDTO.position != null) {
|
|
| 1868 | - if(lastInfoWindow != null) {
|
|
| 1869 | - lastInfoWindow.close();
|
|
| 1870 | - }
|
|
| 1871 | - LatLng where = coordinateSystem.toLatLng(windDTO.position);
|
|
| 1872 | - InfoWindowOptions options = InfoWindowOptions.newInstance();
|
|
| 1873 | - InfoWindow infoWindow = InfoWindow.newInstance(options);
|
|
| 1874 | - infoWindow.setContent(getInfoWindowContent(windSource, windTrackInfoDTO));
|
|
| 1875 | - infoWindow.setPosition(where);
|
|
| 1876 | - lastInfoWindow = infoWindow;
|
|
| 1877 | - infoWindow.open(map);
|
|
| 1878 | - }
|
|
| 1879 | - }
|
|
| 1880 | -
|
|
| 1881 | - private Widget createInfoWindowLabelAndValue(String labelName, String value) {
|
|
| 1882 | - FlowPanel flowPanel = new FlowPanel();
|
|
| 1883 | - Label label = new Label(labelName + ":");
|
|
| 1884 | - label.setWordWrap(false);
|
|
| 1885 | - label.getElement().getStyle().setFloat(Style.Float.LEFT);
|
|
| 1886 | - label.getElement().getStyle().setPadding(3, Style.Unit.PX);
|
|
| 1887 | - label.getElement().getStyle().setFontWeight(Style.FontWeight.BOLD);
|
|
| 1888 | - flowPanel.add(label);
|
|
| 1889 | -
|
|
| 1890 | - Label valueLabel = new Label(value);
|
|
| 1891 | - valueLabel.setWordWrap(false);
|
|
| 1892 | - valueLabel.getElement().getStyle().setFloat(Style.Float.LEFT);
|
|
| 1893 | - valueLabel.getElement().getStyle().setPadding(3, Style.Unit.PX);
|
|
| 1894 | - flowPanel.add(valueLabel);
|
|
| 1895 | -
|
|
| 1896 | - return flowPanel;
|
|
| 1897 | - }
|
|
| 1898 | -
|
|
| 1899 | - private Widget getInfoWindowContent(MarkDTO markDTO) {
|
|
| 1900 | - VerticalPanel vPanel = new VerticalPanel();
|
|
| 1901 | - vPanel.add(createInfoWindowLabelAndValue(stringMessages.mark(), markDTO.getName()));
|
|
| 1902 | - vPanel.add(createInfoWindowLabelAndValue(stringMessages.position(), formatPosition(markDTO.position.getLatDeg(), markDTO.position.getLngDeg())));
|
|
| 1903 | - return vPanel;
|
|
| 1904 | - }
|
|
| 1905 | -
|
|
| 1906 | - private Widget getInfoWindowContent(WindSource windSource, WindTrackInfoDTO windTrackInfoDTO) {
|
|
| 1907 | - WindDTO windDTO = windTrackInfoDTO.windFixes.get(0);
|
|
| 1908 | - NumberFormat numberFormat = NumberFormat.getFormat("0.0");
|
|
| 1909 | - VerticalPanel vPanel = new VerticalPanel();
|
|
| 1910 | - vPanel.add(createInfoWindowLabelAndValue(stringMessages.windSource(), WindSourceTypeFormatter.format(windSource, stringMessages)));
|
|
| 1911 | - vPanel.add(createInfoWindowLabelAndValue(stringMessages.wind(), Math.round(windDTO.dampenedTrueWindFromDeg) + " " + stringMessages.degreesShort()));
|
|
| 1912 | - vPanel.add(createInfoWindowLabelAndValue(stringMessages.windSpeed(), numberFormat.format(windDTO.dampenedTrueWindSpeedInKnots)));
|
|
| 1913 | - vPanel.add(createInfoWindowLabelAndValue(stringMessages.position(), formatPosition(windDTO.position.getLatDeg(), windDTO.position.getLngDeg())));
|
|
| 1914 | - return vPanel;
|
|
| 1915 | - }
|
|
| 1916 | -
|
|
| 1917 | - private Widget getInfoWindowContent(CompetitorDTO competitorDTO, GPSFixDTO lastFix) {
|
|
| 1918 | - final VerticalPanel vPanel = new VerticalPanel();
|
|
| 1919 | - vPanel.setWidth("350px");
|
|
| 1920 | - vPanel.add(createInfoWindowLabelAndValue(stringMessages.competitor(), competitorDTO.getName()));
|
|
| 1921 | - vPanel.add(createInfoWindowLabelAndValue(stringMessages.sailNumber(), competitorDTO.getSailID()));
|
|
| 1922 | - Integer rank = null;
|
|
| 1923 | - if (quickRanks != null) {
|
|
| 1924 | - QuickRankDTO quickRank = quickRanks.get(competitorDTO);
|
|
| 1925 | - if (quickRank != null) {
|
|
| 1926 | - rank = quickRank.rank;
|
|
| 1927 | - }
|
|
| 1928 | - }
|
|
| 1929 | - if (rank != null) {
|
|
| 1930 | - vPanel.add(createInfoWindowLabelAndValue(stringMessages.rank(), String.valueOf(rank)));
|
|
| 1931 | - }
|
|
| 1932 | - SpeedWithBearingDTO speedWithBearing = lastFix.speedWithBearing;
|
|
| 1933 | - if (speedWithBearing == null) {
|
|
| 1934 | - // TODO should we show the boat at all?
|
|
| 1935 | - speedWithBearing = new SpeedWithBearingDTO(0, 0);
|
|
| 1936 | - }
|
|
| 1937 | - vPanel.add(createInfoWindowLabelAndValue(stringMessages.speed(),
|
|
| 1938 | - NumberFormatterFactory.getDecimalFormat(1).format(speedWithBearing.speedInKnots) + " "+stringMessages.knotsUnit()));
|
|
| 1939 | - vPanel.add(createInfoWindowLabelAndValue(stringMessages.bearing(), (int) speedWithBearing.bearingInDegrees + " "+stringMessages.degreesShort()));
|
|
| 1940 | - if (lastFix.degreesBoatToTheWind != null) {
|
|
| 1941 | - vPanel.add(createInfoWindowLabelAndValue(stringMessages.degreesBoatToTheWind(),
|
|
| 1942 | - (int) Math.abs(lastFix.degreesBoatToTheWind) + " " + stringMessages.degreesShort()));
|
|
| 1943 | - }
|
|
| 1944 | - if (!selectedRaces.isEmpty()) {
|
|
| 1945 | - RegattaAndRaceIdentifier race = selectedRaces.get(selectedRaces.size() - 1);
|
|
| 1946 | - if (race != null) {
|
|
| 1947 | - Map<CompetitorDTO, Date> from = new HashMap<CompetitorDTO, Date>();
|
|
| 1948 | - from.put(competitorDTO, fixesAndTails.getFixes(competitorDTO).get(fixesAndTails.getFirstShownFix(competitorDTO)).timepoint);
|
|
| 1949 | - Map<CompetitorDTO, Date> to = new HashMap<CompetitorDTO, Date>();
|
|
| 1950 | - to.put(competitorDTO, getBoatFix(competitorDTO, timer.getTime()).timepoint);
|
|
| 1951 | - sailingService.getDouglasPoints(race, from, to, 3,
|
|
| 1952 | - new AsyncCallback<Map<CompetitorDTO, List<GPSFixDTO>>>() {
|
|
| 1953 | - @Override
|
|
| 1954 | - public void onFailure(Throwable caught) {
|
|
| 1955 | - errorReporter.reportError("Error obtaining douglas positions: " + caught.getMessage(), true /*silentMode */);
|
|
| 1956 | - }
|
|
| 1957 | -
|
|
| 1958 | - @Override
|
|
| 1959 | - public void onSuccess(Map<CompetitorDTO, List<GPSFixDTO>> result) {
|
|
| 1960 | - lastDouglasPeuckerResult = result;
|
|
| 1961 | - if (douglasMarkers != null) {
|
|
| 1962 | - removeAllMarkDouglasPeuckerpoints();
|
|
| 1963 | - }
|
|
| 1964 | - if (!(timer.getPlayState() == PlayStates.Playing)) {
|
|
| 1965 | - if (settings.isShowDouglasPeuckerPoints()) {
|
|
| 1966 | - showMarkDouglasPeuckerPoints(result);
|
|
| 1967 | - }
|
|
| 1968 | - }
|
|
| 1969 | - }
|
|
| 1970 | - });
|
|
| 1971 | - sailingService.getManeuvers(race, from, to,
|
|
| 1972 | - new AsyncCallback<Map<CompetitorDTO, List<ManeuverDTO>>>() {
|
|
| 1973 | - @Override
|
|
| 1974 | - public void onFailure(Throwable caught) {
|
|
| 1975 | - errorReporter.reportError("Error obtaining maneuvers: " + caught.getMessage(), true /*silentMode */);
|
|
| 1976 | - }
|
|
| 1977 | -
|
|
| 1978 | - @Override
|
|
| 1979 | - public void onSuccess(Map<CompetitorDTO, List<ManeuverDTO>> result) {
|
|
| 1980 | - lastManeuverResult = result;
|
|
| 1981 | - if (maneuverMarkers != null) {
|
|
| 1982 | - removeAllManeuverMarkers();
|
|
| 1983 | - }
|
|
| 1984 | - if (!(timer.getPlayState() == PlayStates.Playing)) {
|
|
| 1985 | - showManeuvers(result);
|
|
| 1986 | - }
|
|
| 1987 | - }
|
|
| 1988 | - });
|
|
| 1989 | -
|
|
| 1990 | - }
|
|
| 1991 | - }
|
|
| 1992 | - return vPanel;
|
|
| 1993 | - }
|
|
| 1994 | -
|
|
| 1995 | - private Iterable<CompetitorDTO> getCompetitorsToShow() {
|
|
| 1996 | - Iterable<CompetitorDTO> result;
|
|
| 1997 | - Iterable<CompetitorDTO> selection = competitorSelection.getSelectedCompetitors();
|
|
| 1998 | - if (!settings.isShowOnlySelectedCompetitors() || Util.isEmpty(selection)) {
|
|
| 1999 | - result = competitorSelection.getFilteredCompetitors();
|
|
| 2000 | - } else {
|
|
| 2001 | - result = selection;
|
|
| 2002 | - }
|
|
| 2003 | - return result;
|
|
| 2004 | - }
|
|
| 2005 | -
|
|
| 2006 | - protected void removeAllMarkDouglasPeuckerpoints() {
|
|
| 2007 | - if (douglasMarkers != null) {
|
|
| 2008 | - for (Marker marker : douglasMarkers) {
|
|
| 2009 | - marker.setMap((MapWidget) null);
|
|
| 2010 | - }
|
|
| 2011 | - }
|
|
| 2012 | - douglasMarkers = null;
|
|
| 2013 | - }
|
|
| 2014 | -
|
|
| 2015 | - protected void removeAllManeuverMarkers() {
|
|
| 2016 | - if (maneuverMarkers != null) {
|
|
| 2017 | - for (Marker marker : maneuverMarkers) {
|
|
| 2018 | - marker.setMap((MapWidget) null);
|
|
| 2019 | - }
|
|
| 2020 | - maneuverMarkers = null;
|
|
| 2021 | - }
|
|
| 2022 | - }
|
|
| 2023 | -
|
|
| 2024 | - private void showMarkDouglasPeuckerPoints(Map<CompetitorDTO, List<GPSFixDTO>> gpsFixPointMapForCompetitors) {
|
|
| 2025 | - douglasMarkers = new HashSet<Marker>();
|
|
| 2026 | - if (map != null && gpsFixPointMapForCompetitors != null) {
|
|
| 2027 | - Set<CompetitorDTO> keySet = gpsFixPointMapForCompetitors.keySet();
|
|
| 2028 | - Iterator<CompetitorDTO> iter = keySet.iterator();
|
|
| 2029 | - while (iter.hasNext()) {
|
|
| 2030 | - CompetitorDTO competitorDTO = iter.next();
|
|
| 2031 | - List<GPSFixDTO> gpsFix = gpsFixPointMapForCompetitors.get(competitorDTO);
|
|
| 2032 | - for (GPSFixDTO fix : gpsFix) {
|
|
| 2033 | - LatLng latLng = coordinateSystem.toLatLng(fix.position);
|
|
| 2034 | - MarkerOptions options = MarkerOptions.newInstance();
|
|
| 2035 | - options.setTitle(fix.timepoint+": "+fix.position+", "+fix.speedWithBearing.toString());
|
|
| 2036 | - Marker marker = Marker.newInstance(options);
|
|
| 2037 | - marker.setPosition(latLng);
|
|
| 2038 | - douglasMarkers.add(marker);
|
|
| 2039 | - marker.setMap(map);
|
|
| 2040 | - }
|
|
| 2041 | - }
|
|
| 2042 | - }
|
|
| 2043 | - }
|
|
| 2044 | -
|
|
| 2045 | - private void showManeuvers(Map<CompetitorDTO, List<ManeuverDTO>> maneuvers) {
|
|
| 2046 | - maneuverMarkers = new HashSet<Marker>();
|
|
| 2047 | - if (map != null && maneuvers != null) {
|
|
| 2048 | - Set<CompetitorDTO> keySet = maneuvers.keySet();
|
|
| 2049 | - Iterator<CompetitorDTO> iter = keySet.iterator();
|
|
| 2050 | - while (iter.hasNext()) {
|
|
| 2051 | - CompetitorDTO competitorDTO = iter.next();
|
|
| 2052 | - List<ManeuverDTO> maneuversForCompetitor = maneuvers.get(competitorDTO);
|
|
| 2053 | - for (ManeuverDTO maneuver : maneuversForCompetitor) {
|
|
| 2054 | - if (settings.isShowManeuverType(maneuver.type)) {
|
|
| 2055 | - LatLng latLng = coordinateSystem.toLatLng(maneuver.position);
|
|
| 2056 | - Marker maneuverMarker = raceMapImageManager.maneuverIconsForTypeAndTargetTack.get(new com.sap.sse.common.Util.Pair<ManeuverType, Tack>(maneuver.type, maneuver.newTack));
|
|
| 2057 | - MarkerOptions options = MarkerOptions.newInstance();
|
|
| 2058 | - options.setTitle(maneuver.toString(stringMessages));
|
|
| 2059 | - options.setIcon(maneuverMarker.getIcon_MarkerImage());
|
|
| 2060 | - Marker marker = Marker.newInstance(options);
|
|
| 2061 | - marker.setPosition(latLng);
|
|
| 2062 | - maneuverMarkers.add(marker);
|
|
| 2063 | - marker.setMap(map);
|
|
| 2064 | - }
|
|
| 2065 | - }
|
|
| 2066 | - }
|
|
| 2067 | - }
|
|
| 2068 | - }
|
|
| 2069 | -
|
|
| 2070 | - /**
|
|
| 2071 | - * @param date
|
|
| 2072 | - * the point in time for which to determine the competitor's boat position; approximated by using the fix
|
|
| 2073 | - * from {@link #fixes} whose time point comes closest to this date
|
|
| 2074 | - *
|
|
| 2075 | - * @return The GPS fix for the given competitor from {@link #fixes} that is closest to <code>date</code>, or
|
|
| 2076 | - * <code>null</code> if no fix is available
|
|
| 2077 | - */
|
|
| 2078 | - private GPSFixDTO getBoatFix(CompetitorDTO competitorDTO, Date date) {
|
|
| 2079 | - GPSFixDTO result = null;
|
|
| 2080 | - List<GPSFixDTO> competitorFixes = fixesAndTails.getFixes(competitorDTO);
|
|
| 2081 | - if (competitorFixes != null && !competitorFixes.isEmpty()) {
|
|
| 2082 | - int i = Collections.binarySearch(competitorFixes, new GPSFixDTO(date, null, null, (WindDTO) null, null, null, false),
|
|
| 2083 | - new Comparator<GPSFixDTO>() {
|
|
| 2084 | - @Override
|
|
| 2085 | - public int compare(GPSFixDTO o1, GPSFixDTO o2) {
|
|
| 2086 | - return o1.timepoint.compareTo(o2.timepoint);
|
|
| 2087 | - }
|
|
| 2088 | - });
|
|
| 2089 | - if (i<0) {
|
|
| 2090 | - i = -i-1; // no perfect match; i is now the insertion point
|
|
| 2091 | - // if the insertion point is at the end, use last fix
|
|
| 2092 | - if (i >= competitorFixes.size()) {
|
|
| 2093 | - result = competitorFixes.get(competitorFixes.size()-1);
|
|
| 2094 | - } else if (i == 0) {
|
|
| 2095 | - // if the insertion point is at the beginning, use first fix
|
|
| 2096 | - result = competitorFixes.get(0);
|
|
| 2097 | - } else {
|
|
| 2098 | - // competitorFixes must have at least two elements, and i points neither to the end nor the beginning;
|
|
| 2099 | - // get the fix from i and i+1 whose timepoint is closer to date
|
|
| 2100 | - final GPSFixDTO fixBefore = competitorFixes.get(i-1);
|
|
| 2101 | - final GPSFixDTO fixAfter = competitorFixes.get(i);
|
|
| 2102 | - final GPSFixDTO closer;
|
|
| 2103 | - if (date.getTime() - fixBefore.timepoint.getTime() < fixAfter.timepoint.getTime() - date.getTime()) {
|
|
| 2104 | - closer = fixBefore;
|
|
| 2105 | - } else {
|
|
| 2106 | - closer = fixAfter;
|
|
| 2107 | - }
|
|
| 2108 | - // now compute a weighted average depending on the time difference to "date" (see also bug 1924)
|
|
| 2109 | - double factorForAfter = (double) (date.getTime()-fixBefore.timepoint.getTime()) / (double) (fixAfter.timepoint.getTime() - fixBefore.timepoint.getTime());
|
|
| 2110 | - double factorForBefore = 1-factorForAfter;
|
|
| 2111 | - DegreePosition betweenPosition = new DegreePosition(factorForBefore*fixBefore.position.getLatDeg() + factorForAfter*fixAfter.position.getLatDeg(),
|
|
| 2112 | - factorForBefore*fixBefore.position.getLngDeg() + factorForAfter*fixAfter.position.getLngDeg());
|
|
| 2113 | - final double betweenBearing;
|
|
| 2114 | - if (fixBefore.speedWithBearing == null) {
|
|
| 2115 | - if (fixAfter.speedWithBearing == null) {
|
|
| 2116 | - betweenBearing = 0;
|
|
| 2117 | - } else {
|
|
| 2118 | - betweenBearing = fixAfter.speedWithBearing.bearingInDegrees;
|
|
| 2119 | - }
|
|
| 2120 | - } else if (fixAfter.speedWithBearing == null) {
|
|
| 2121 | - betweenBearing = fixBefore.speedWithBearing.bearingInDegrees;
|
|
| 2122 | - } else {
|
|
| 2123 | - betweenBearing = new ScalableBearing(new DegreeBearingImpl(fixBefore.speedWithBearing.bearingInDegrees)).
|
|
| 2124 | - multiply(factorForBefore).add(new ScalableBearing(new DegreeBearingImpl(fixAfter.speedWithBearing.bearingInDegrees)).
|
|
| 2125 | - multiply(factorForAfter)).divide(1).getDegrees();
|
|
| 2126 | - }
|
|
| 2127 | - SpeedWithBearingDTO betweenSpeed = new SpeedWithBearingDTO(
|
|
| 2128 | - factorForBefore*(fixBefore.speedWithBearing==null?0:fixBefore.speedWithBearing.speedInKnots) +
|
|
| 2129 | - factorForAfter*(fixAfter.speedWithBearing==null?0:fixAfter.speedWithBearing.speedInKnots),
|
|
| 2130 | - betweenBearing);
|
|
| 2131 | - result = new GPSFixDTO(date, betweenPosition, betweenSpeed, closer.degreesBoatToTheWind,
|
|
| 2132 | - closer.tack, closer.legType, fixBefore.extrapolated || fixAfter.extrapolated);
|
|
| 2133 | - }
|
|
| 2134 | - } else {
|
|
| 2135 | - // perfect match
|
|
| 2136 | - final GPSFixDTO fixAfter = competitorFixes.get(i);
|
|
| 2137 | - result = fixAfter;
|
|
| 2138 | - }
|
|
| 2139 | - }
|
|
| 2140 | - return result;
|
|
| 2141 | - }
|
|
| 2142 | -
|
|
| 2143 | - public RaceMapSettings getSettings() {
|
|
| 2144 | - return settings;
|
|
| 2145 | - }
|
|
| 2146 | -
|
|
| 2147 | - @Override
|
|
| 2148 | - public void addedToSelection(CompetitorDTO competitor) {
|
|
| 2149 | - if (settings.isShowOnlySelectedCompetitors()) {
|
|
| 2150 | - if (Util.size(competitorSelection.getSelectedCompetitors()) == 1) {
|
|
| 2151 | - // first competitors selected; remove all others from map
|
|
| 2152 | - Iterator<Map.Entry<CompetitorDTO, BoatOverlay>> i = boatOverlays.entrySet().iterator();
|
|
| 2153 | - while (i.hasNext()) {
|
|
| 2154 | - Entry<CompetitorDTO, BoatOverlay> next = i.next();
|
|
| 2155 | - if (!next.getKey().equals(competitor)) {
|
|
| 2156 | - BoatOverlay boatOverlay = next.getValue();
|
|
| 2157 | - boatOverlay.removeFromMap();
|
|
| 2158 | - fixesAndTails.removeTail(next.getKey());
|
|
| 2159 | - i.remove(); // only this way a ConcurrentModificationException while looping can be avoided
|
|
| 2160 | - }
|
|
| 2161 | - }
|
|
| 2162 | - showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors());
|
|
| 2163 | - } else {
|
|
| 2164 | - // adding a single competitor; may need to re-load data, so refresh:
|
|
| 2165 | - timeChanged(timer.getTime(), null);
|
|
| 2166 | - }
|
|
| 2167 | - } else {
|
|
| 2168 | - // only change highlighting
|
|
| 2169 | - BoatOverlay boatCanvas = boatOverlays.get(competitor);
|
|
| 2170 | - if (boatCanvas != null) {
|
|
| 2171 | - boatCanvas.setSelected(displayHighlighted(competitor));
|
|
| 2172 | - boatCanvas.draw();
|
|
| 2173 | - showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors());
|
|
| 2174 | - } else {
|
|
| 2175 | - // seems like an internal error not to find the lowlighted marker; but maybe the
|
|
| 2176 | - // competitor was added late to the race;
|
|
| 2177 | - // data for newly selected competitor supposedly missing; refresh
|
|
| 2178 | - timeChanged(timer.getTime(), null);
|
|
| 2179 | - }
|
|
| 2180 | - }
|
|
| 2181 | - // Now update tails for all competitors because selection change may also affect all unselected competitors
|
|
| 2182 | - for (CompetitorDTO oneOfAllCompetitors : competitorSelection.getAllCompetitors()) {
|
|
| 2183 | - Polyline tail = fixesAndTails.getTail(oneOfAllCompetitors);
|
|
| 2184 | - if (tail != null) {
|
|
| 2185 | - PolylineOptions newOptions = createTailStyle(oneOfAllCompetitors, displayHighlighted(oneOfAllCompetitors));
|
|
| 2186 | - tail.setOptions(newOptions);
|
|
| 2187 | - }
|
|
| 2188 | - }
|
|
| 2189 | - // Trigger auto-zoom if needed
|
|
| 2190 | - RaceMapZoomSettings zoomSettings = settings.getZoomSettings();
|
|
| 2191 | - if (!zoomSettings.containsZoomType(ZoomTypes.NONE) && zoomSettings.isZoomToSelectedCompetitors()) {
|
|
| 2192 | - zoomMapToNewBounds(zoomSettings.getNewBounds(this));
|
|
| 2193 | - }
|
|
| 2194 | - }
|
|
| 2195 | -
|
|
| 2196 | - @Override
|
|
| 2197 | - public void removedFromSelection(CompetitorDTO competitor) {
|
|
| 2198 | - if (isShowAnyHelperLines()) {
|
|
| 2199 | - // helper lines depend on which competitor is visible, because the *visible* leader is used for
|
|
| 2200 | - // deciding which helper lines to show:
|
|
| 2201 | - timeChanged(timer.getTime(), null);
|
|
| 2202 | - } else {
|
|
| 2203 | - // try a more incremental update otherwise
|
|
| 2204 | - if (settings.isShowOnlySelectedCompetitors()) {
|
|
| 2205 | - // if selection is now empty, show all competitors
|
|
| 2206 | - if (Util.isEmpty(competitorSelection.getSelectedCompetitors())) {
|
|
| 2207 | - timeChanged(timer.getTime(), null);
|
|
| 2208 | - } else {
|
|
| 2209 | - // otherwise remove only deselected competitor's boat images and tail
|
|
| 2210 | - BoatOverlay removedBoatOverlay = boatOverlays.remove(competitor);
|
|
| 2211 | - if (removedBoatOverlay != null) {
|
|
| 2212 | - removedBoatOverlay.removeFromMap();
|
|
| 2213 | - }
|
|
| 2214 | - fixesAndTails.removeTail(competitor);
|
|
| 2215 | - showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors());
|
|
| 2216 | - }
|
|
| 2217 | - } else {
|
|
| 2218 | - // "lowlight" currently selected competitor
|
|
| 2219 | - BoatOverlay boatCanvas = boatOverlays.get(competitor);
|
|
| 2220 | - if (boatCanvas != null) {
|
|
| 2221 | - boatCanvas.setSelected(displayHighlighted(competitor));
|
|
| 2222 | - boatCanvas.draw();
|
|
| 2223 | - }
|
|
| 2224 | - showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors());
|
|
| 2225 | - }
|
|
| 2226 | - }
|
|
| 2227 | - //Trigger auto-zoom if needed
|
|
| 2228 | - RaceMapZoomSettings zoomSettings = settings.getZoomSettings();
|
|
| 2229 | - if (!zoomSettings.containsZoomType(ZoomTypes.NONE) && zoomSettings.isZoomToSelectedCompetitors()) {
|
|
| 2230 | - zoomMapToNewBounds(zoomSettings.getNewBounds(this));
|
|
| 2231 | - }
|
|
| 2232 | - }
|
|
| 2233 | -
|
|
| 2234 | - private boolean isShowAnyHelperLines() {
|
|
| 2235 | - return settings.getHelpLinesSettings().isShowAnyHelperLines();
|
|
| 2236 | - }
|
|
| 2237 | -
|
|
| 2238 | - @Override
|
|
| 2239 | - public String getLocalizedShortName() {
|
|
| 2240 | - return stringMessages.map();
|
|
| 2241 | - }
|
|
| 2242 | -
|
|
| 2243 | - @Override
|
|
| 2244 | - public Widget getEntryWidget() {
|
|
| 2245 | - return this;
|
|
| 2246 | - }
|
|
| 2247 | -
|
|
| 2248 | - @Override
|
|
| 2249 | - public boolean hasSettings() {
|
|
| 2250 | - return true;
|
|
| 2251 | - }
|
|
| 2252 | -
|
|
| 2253 | - @Override
|
|
| 2254 | - public SettingsDialogComponent<RaceMapSettings> getSettingsDialogComponent() {
|
|
| 2255 | - return new RaceMapSettingsDialogComponent(settings, stringMessages, this.showViewSimulation && this.hasPolar);
|
|
| 2256 | - }
|
|
| 2257 | -
|
|
| 2258 | - @Override
|
|
| 2259 | - public void updateSettings(RaceMapSettings newSettings) {
|
|
| 2260 | - boolean maneuverTypeSelectionChanged = false;
|
|
| 2261 | - boolean requiredRedraw = false;
|
|
| 2262 | - for (ManeuverType maneuverType : ManeuverType.values()) {
|
|
| 2263 | - if (newSettings.isShowManeuverType(maneuverType) != settings.isShowManeuverType(maneuverType)) {
|
|
| 2264 | - maneuverTypeSelectionChanged = true;
|
|
| 2265 | - settings.showManeuverType(maneuverType, newSettings.isShowManeuverType(maneuverType));
|
|
| 2266 | - }
|
|
| 2267 | - }
|
|
| 2268 | - if (maneuverTypeSelectionChanged) {
|
|
| 2269 | - if (!(timer.getPlayState() == PlayStates.Playing) && lastManeuverResult != null) {
|
|
| 2270 | - removeAllManeuverMarkers();
|
|
| 2271 | - showManeuvers(lastManeuverResult);
|
|
| 2272 | - }
|
|
| 2273 | - }
|
|
| 2274 | - if (newSettings.isShowDouglasPeuckerPoints() != settings.isShowDouglasPeuckerPoints()) {
|
|
| 2275 | - if (!(timer.getPlayState() == PlayStates.Playing) && lastDouglasPeuckerResult != null && newSettings.isShowDouglasPeuckerPoints()) {
|
|
| 2276 | - settings.setShowDouglasPeuckerPoints(true);
|
|
| 2277 | - removeAllMarkDouglasPeuckerpoints();
|
|
| 2278 | - showMarkDouglasPeuckerPoints(lastDouglasPeuckerResult);
|
|
| 2279 | - } else if (!newSettings.isShowDouglasPeuckerPoints()) {
|
|
| 2280 | - settings.setShowDouglasPeuckerPoints(false);
|
|
| 2281 | - removeAllMarkDouglasPeuckerpoints();
|
|
| 2282 | - }
|
|
| 2283 | - }
|
|
| 2284 | - if (newSettings.getTailLengthInMilliseconds() != settings.getTailLengthInMilliseconds()) {
|
|
| 2285 | - settings.setTailLengthInMilliseconds(newSettings.getTailLengthInMilliseconds());
|
|
| 2286 | - requiredRedraw = true;
|
|
| 2287 | - }
|
|
| 2288 | - if (newSettings.getBuoyZoneRadiusInMeters() != settings.getBuoyZoneRadiusInMeters()) {
|
|
| 2289 | - settings.setBuoyZoneRadiusInMeters(newSettings.getBuoyZoneRadiusInMeters());
|
|
| 2290 | - requiredRedraw = true;
|
|
| 2291 | - }
|
|
| 2292 | - if (newSettings.isShowOnlySelectedCompetitors() != settings.isShowOnlySelectedCompetitors()) {
|
|
| 2293 | - settings.setShowOnlySelectedCompetitors(newSettings.isShowOnlySelectedCompetitors());
|
|
| 2294 | - requiredRedraw = true;
|
|
| 2295 | - }
|
|
| 2296 | - if (newSettings.isShowSelectedCompetitorsInfo() != settings.isShowSelectedCompetitorsInfo()) {
|
|
| 2297 | - settings.setShowSelectedCompetitorsInfo(newSettings.isShowSelectedCompetitorsInfo());
|
|
| 2298 | - requiredRedraw = true;
|
|
| 2299 | - }
|
|
| 2300 | - if (!newSettings.getZoomSettings().equals(settings.getZoomSettings())) {
|
|
| 2301 | - settings.setZoomSettings(newSettings.getZoomSettings());
|
|
| 2302 | - if (!settings.getZoomSettings().containsZoomType(ZoomTypes.NONE)) {
|
|
| 2303 | - removeTransitions();
|
|
| 2304 | - zoomMapToNewBounds(settings.getZoomSettings().getNewBounds(this));
|
|
| 2305 | - }
|
|
| 2306 | - }
|
|
| 2307 | - if (!newSettings.getHelpLinesSettings().equals(settings.getHelpLinesSettings())) {
|
|
| 2308 | - settings.setHelpLinesSettings(newSettings.getHelpLinesSettings());
|
|
| 2309 | - requiredRedraw = true;
|
|
| 2310 | - }
|
|
| 2311 | - if (newSettings.isShowWindStreamletOverlay() != settings.isShowWindStreamletOverlay()) {
|
|
| 2312 | - settings.setShowWindStreamletOverlay(newSettings.isShowWindStreamletOverlay());
|
|
| 2313 | - streamletOverlay.setVisible(newSettings.isShowWindStreamletOverlay());
|
|
| 2314 | - }
|
|
| 2315 | - if (newSettings.isShowWindStreamletColors() != settings.isShowWindStreamletColors()) {
|
|
| 2316 | - settings.setShowWindStreamletColors(newSettings.isShowWindStreamletColors());
|
|
| 2317 | - streamletOverlay.setColors(newSettings.isShowWindStreamletColors());
|
|
| 2318 | - }
|
|
| 2319 | - if (newSettings.isShowSimulationOverlay() != settings.isShowSimulationOverlay()) {
|
|
| 2320 | - settings.setShowSimulationOverlay(newSettings.isShowSimulationOverlay());
|
|
| 2321 | - simulationOverlay.setVisible(newSettings.isShowSimulationOverlay());
|
|
| 2322 | - if (newSettings.isShowSimulationOverlay()) {
|
|
| 2323 | - simulationOverlay.updateLeg(Math.max(lastLegNumber,1), true, -1 /* ensure ui-update */);
|
|
| 2324 | - }
|
|
| 2325 | - }
|
|
| 2326 | - if (newSettings.isWindUp() != settings.isWindUp()) {
|
|
| 2327 | - settings.setWindUp(newSettings.isWindUp());
|
|
| 2328 | - updateCoordinateSystemFromSettings();
|
|
| 2329 | - requiredRedraw = true;
|
|
| 2330 | - }
|
|
| 2331 | - if (requiredRedraw) {
|
|
| 2332 | - redraw();
|
|
| 2333 | - }
|
|
| 2334 | - }
|
|
| 2335 | -
|
|
| 2336 | - public static class BoatsBoundsCalculator extends LatLngBoundsCalculatorForSelected {
|
|
| 2337 | -
|
|
| 2338 | - @Override
|
|
| 2339 | - public LatLngBounds calculateNewBounds(RaceMap forMap) {
|
|
| 2340 | - LatLngBounds newBounds = null;
|
|
| 2341 | - Iterable<CompetitorDTO> selectedCompetitors = forMap.competitorSelection.getSelectedCompetitors();
|
|
| 2342 | - Iterable<CompetitorDTO> competitors = new ArrayList<CompetitorDTO>();
|
|
| 2343 | - if (selectedCompetitors == null || !selectedCompetitors.iterator().hasNext()) {
|
|
| 2344 | - competitors = forMap.getCompetitorsToShow();
|
|
| 2345 | - } else {
|
|
| 2346 | - competitors = isZoomOnlyToSelectedCompetitors(forMap) ? selectedCompetitors : forMap.getCompetitorsToShow();
|
|
| 2347 | - }
|
|
| 2348 | - for (CompetitorDTO competitor : competitors) {
|
|
| 2349 | - try {
|
|
| 2350 | - GPSFixDTO competitorFix = forMap.getBoatFix(competitor, forMap.timer.getTime());
|
|
| 2351 | - Position competitorPosition = competitorFix != null ? competitorFix.position : null;
|
|
| 2352 | - if (competitorPosition != null) {
|
|
| 2353 | - if (newBounds == null) {
|
|
| 2354 | - newBounds = BoundsUtil.getAsBounds(forMap.coordinateSystem.toLatLng(competitorPosition));
|
|
| 2355 | - } else {
|
|
| 2356 | - newBounds = newBounds.extend(forMap.coordinateSystem.toLatLng(competitorPosition));
|
|
| 2357 | - }
|
|
| 2358 | - }
|
|
| 2359 | - } catch (IndexOutOfBoundsException e) {
|
|
| 2360 | - // TODO can't this be predicted and the exception be avoided in the first place?
|
|
| 2361 | - // Catch this in case the competitor has no GPS fixes at the current time (e.g. in race 'Finale 2' of STG)
|
|
| 2362 | - }
|
|
| 2363 | - }
|
|
| 2364 | - return newBounds;
|
|
| 2365 | - }
|
|
| 2366 | -
|
|
| 2367 | - }
|
|
| 2368 | -
|
|
| 2369 | - public static class TailsBoundsCalculator extends LatLngBoundsCalculatorForSelected {
|
|
| 2370 | - @Override
|
|
| 2371 | - public LatLngBounds calculateNewBounds(RaceMap racemap) {
|
|
| 2372 | - LatLngBounds newBounds = null;
|
|
| 2373 | - Iterable<CompetitorDTO> competitors = isZoomOnlyToSelectedCompetitors(racemap) ? racemap.competitorSelection.getSelectedCompetitors() : racemap.getCompetitorsToShow();
|
|
| 2374 | - for (CompetitorDTO competitor : competitors) {
|
|
| 2375 | - Polyline tail = racemap.fixesAndTails.getTail(competitor);
|
|
| 2376 | - LatLngBounds bounds = null;
|
|
| 2377 | - // TODO: Find a replacement for missing Polyline function getBounds() from v2
|
|
| 2378 | - // see also http://stackoverflow.com/questions/3284808/getting-the-bounds-of-a-polyine-in-google-maps-api-v3;
|
|
| 2379 | - // optionally, consider providing a bounds cache with two sorted sets that organize the LatLng objects for O(1) bounds calculation and logarithmic add, ideally O(1) remove
|
|
| 2380 | - if (tail != null && tail.getPath().getLength() >= 1) {
|
|
| 2381 | - bounds = BoundsUtil.getAsBounds(tail.getPath().get(0));
|
|
| 2382 | - for (int i = 1; i < tail.getPath().getLength(); i++) {
|
|
| 2383 | - bounds = bounds.extend(tail.getPath().get(i));
|
|
| 2384 | - }
|
|
| 2385 | - }
|
|
| 2386 | - if (bounds != null) {
|
|
| 2387 | - if (newBounds == null) {
|
|
| 2388 | - newBounds = bounds;
|
|
| 2389 | - } else {
|
|
| 2390 | - newBounds = newBounds.union(bounds);
|
|
| 2391 | - }
|
|
| 2392 | - }
|
|
| 2393 | - }
|
|
| 2394 | - return newBounds;
|
|
| 2395 | - }
|
|
| 2396 | - }
|
|
| 2397 | -
|
|
| 2398 | - public static class CourseMarksBoundsCalculator implements LatLngBoundsCalculator {
|
|
| 2399 | - @Override
|
|
| 2400 | - public LatLngBounds calculateNewBounds(RaceMap forMap) {
|
|
| 2401 | - LatLngBounds newBounds = null;
|
|
| 2402 | - Iterable<MarkDTO> marksToZoom = forMap.markDTOs.values();
|
|
| 2403 | - if (marksToZoom != null) {
|
|
| 2404 | - for (MarkDTO markDTO : marksToZoom) {
|
|
| 2405 | - if (newBounds == null) {
|
|
| 2406 | - newBounds = BoundsUtil.getAsBounds(forMap.coordinateSystem.toLatLng(markDTO.position));
|
|
| 2407 | - } else {
|
|
| 2408 | - newBounds = newBounds.extend(forMap.coordinateSystem.toLatLng(markDTO.position));
|
|
| 2409 | - }
|
|
| 2410 | - }
|
|
| 2411 | - }
|
|
| 2412 | - return newBounds;
|
|
| 2413 | - }
|
|
| 2414 | - }
|
|
| 2415 | -
|
|
| 2416 | - public static class WindSensorsBoundsCalculator implements LatLngBoundsCalculator {
|
|
| 2417 | - @Override
|
|
| 2418 | - public LatLngBounds calculateNewBounds(RaceMap forMap) {
|
|
| 2419 | - LatLngBounds newBounds = null;
|
|
| 2420 | - Collection<WindSensorOverlay> marksToZoom = forMap.windSensorOverlays.values();
|
|
| 2421 | - if (marksToZoom != null) {
|
|
| 2422 | - for (WindSensorOverlay windSensorOverlay : marksToZoom) {
|
|
| 2423 | - if (BoundsUtil.getAsPosition(windSensorOverlay.getLatLngPosition()) != null) {
|
|
| 2424 | - LatLngBounds bounds = BoundsUtil.getAsBounds(windSensorOverlay.getLatLngPosition());
|
|
| 2425 | - if (newBounds == null) {
|
|
| 2426 | - newBounds = bounds;
|
|
| 2427 | - } else {
|
|
| 2428 | - newBounds = newBounds.extend(windSensorOverlay.getLatLngPosition());
|
|
| 2429 | - }
|
|
| 2430 | - }
|
|
| 2431 | - }
|
|
| 2432 | - }
|
|
| 2433 | - return newBounds;
|
|
| 2434 | - }
|
|
| 2435 | - }
|
|
| 2436 | -
|
|
| 2437 | - @Override
|
|
| 2438 | - public void initializeData(boolean showMapControls, boolean showHeaderPanel) {
|
|
| 2439 | - loadMapsAPIV3(showMapControls, showHeaderPanel);
|
|
| 2440 | - }
|
|
| 2441 | -
|
|
| 2442 | - @Override
|
|
| 2443 | - public boolean isDataInitialized() {
|
|
| 2444 | - return isMapInitialized;
|
|
| 2445 | - }
|
|
| 2446 | -
|
|
| 2447 | - @Override
|
|
| 2448 | - public void onResize() {
|
|
| 2449 | - if (map != null) {
|
|
| 2450 | - map.triggerResize();
|
|
| 2451 | - zoomMapToNewBounds(settings.getZoomSettings().getNewBounds(RaceMap.this));
|
|
| 2452 | - }
|
|
| 2453 | - }
|
|
| 2454 | -
|
|
| 2455 | - @Override
|
|
| 2456 | - public void competitorsListChanged(Iterable<CompetitorDTO> competitors) {
|
|
| 2457 | - timeChanged(timer.getTime(), null);
|
|
| 2458 | - }
|
|
| 2459 | -
|
|
| 2460 | - @Override
|
|
| 2461 | - public void filteredCompetitorsListChanged(Iterable<CompetitorDTO> filteredCompetitors) {
|
|
| 2462 | - timeChanged(timer.getTime(), null);
|
|
| 2463 | - }
|
|
| 2464 | -
|
|
| 2465 | - @Override
|
|
| 2466 | - public void filterChanged(FilterSet<CompetitorDTO, ? extends Filter<CompetitorDTO>> oldFilterSet,
|
|
| 2467 | - FilterSet<CompetitorDTO, ? extends Filter<CompetitorDTO>> newFilterSet) {
|
|
| 2468 | - // nothing to do; if the list of filtered competitors has changed, a separate call to filteredCompetitorsListChanged will occur
|
|
| 2469 | - }
|
|
| 2470 | -
|
|
| 2471 | - @Override
|
|
| 2472 | - public PolylineOptions createTailStyle(CompetitorDTO competitor, boolean isHighlighted) {
|
|
| 2473 | - PolylineOptions options = PolylineOptions.newInstance();
|
|
| 2474 | - options.setClickable(true);
|
|
| 2475 | - options.setGeodesic(true);
|
|
| 2476 | - options.setStrokeOpacity(1.0);
|
|
| 2477 | - boolean noCompetitorSelected = Util.isEmpty(competitorSelection.getSelectedCompetitors());
|
|
| 2478 | - if (isHighlighted || noCompetitorSelected) {
|
|
| 2479 | - options.setStrokeColor(competitorSelection.getColor(competitor, raceIdentifier).getAsHtml());
|
|
| 2480 | - } else {
|
|
| 2481 | - options.setStrokeColor(CssColor.make(200, 200, 200).toString());
|
|
| 2482 | - }
|
|
| 2483 | - if (isHighlighted) {
|
|
| 2484 | - options.setStrokeWeight(2);
|
|
| 2485 | - } else {
|
|
| 2486 | - options.setStrokeWeight(1);
|
|
| 2487 | - }
|
|
| 2488 | - options.setZindex(RaceMapOverlaysZIndexes.BOATTAILS_ZINDEX);
|
|
| 2489 | - return options;
|
|
| 2490 | - }
|
|
| 2491 | -
|
|
| 2492 | - @Override
|
|
| 2493 | - public Polyline createTail(final CompetitorDTO competitor, List<LatLng> points) {
|
|
| 2494 | - PolylineOptions options = createTailStyle(competitor, displayHighlighted(competitor));
|
|
| 2495 | - Polyline result = Polyline.newInstance(options);
|
|
| 2496 | -
|
|
| 2497 | - MVCArray<LatLng> pointsAsArray = MVCArray.newInstance(points.toArray(new LatLng[0]));
|
|
| 2498 | - result.setPath(pointsAsArray);
|
|
| 2499 | -
|
|
| 2500 | - result.addClickHandler(new ClickMapHandler() {
|
|
| 2501 | - @Override
|
|
| 2502 | - public void onEvent(ClickMapEvent event) {
|
|
| 2503 | - showCompetitorInfoWindow(competitor, event.getMouseEvent().getLatLng());
|
|
| 2504 | - }
|
|
| 2505 | - });
|
|
| 2506 | - result.addMouseOverHandler(new MouseOverMapHandler() {
|
|
| 2507 | - @Override
|
|
| 2508 | - public void onEvent(MouseOverMapEvent event) {
|
|
| 2509 | - map.setTitle(competitor.getSailID() + ", " + competitor.getName());
|
|
| 2510 | - }
|
|
| 2511 | - });
|
|
| 2512 | - result.addMouseOutMoveHandler(new MouseOutMapHandler() {
|
|
| 2513 | - @Override
|
|
| 2514 | - public void onEvent(MouseOutMapEvent event) {
|
|
| 2515 | - map.setTitle("");
|
|
| 2516 | - }
|
|
| 2517 | - });
|
|
| 2518 | - return result;
|
|
| 2519 | - }
|
|
| 2520 | -
|
|
| 2521 | - @Override
|
|
| 2522 | - public Integer getRank(CompetitorDTO competitor) {
|
|
| 2523 | - final Integer result;
|
|
| 2524 | - QuickRankDTO quickRank = quickRanks.get(competitor);
|
|
| 2525 | - if (quickRank != null) {
|
|
| 2526 | - result = quickRank.rank;
|
|
| 2527 | - } else {
|
|
| 2528 | - result = null;
|
|
| 2529 | - }
|
|
| 2530 | - return result;
|
|
| 2531 | - }
|
|
| 2532 | -
|
|
| 2533 | - private Image createSAPLogo() {
|
|
| 2534 | - ImageResource sapLogoResource = resources.sapLogoOverlay();
|
|
| 2535 | - Image sapLogo = new Image(sapLogoResource);
|
|
| 2536 | - sapLogo.addClickHandler(new ClickHandler() {
|
|
| 2537 | - @Override
|
|
| 2538 | - public void onClick(ClickEvent event) {
|
|
| 2539 | - Window.open("http://www.sap.com", "_blank", null);
|
|
| 2540 | - }
|
|
| 2541 | - });
|
|
| 2542 | - sapLogo.setStyleName("raceBoard-Logo");
|
|
| 2543 | - return sapLogo;
|
|
| 2544 | - }
|
|
| 2545 | -
|
|
| 2546 | - @Override
|
|
| 2547 | - public String getDependentCssClassName() {
|
|
| 2548 | - return "raceMap";
|
|
| 2549 | - }
|
|
| 2550 | -
|
|
| 2551 | - /**
|
|
| 2552 | - * The default zoom bounds are defined by the boats
|
|
| 2553 | - */
|
|
| 2554 | - private LatLngBounds getDefaultZoomBounds() {
|
|
| 2555 | - return new BoatsBoundsCalculator().calculateNewBounds(RaceMap.this);
|
|
| 2556 | - }
|
|
| 2557 | -
|
|
| 2558 | - private MapOptions getMapOptions(final boolean showMapControls, boolean windUp) {
|
|
| 2559 | - MapOptions mapOptions = MapOptions.newInstance();
|
|
| 2560 | - mapOptions.setScrollWheel(true);
|
|
| 2561 | - mapOptions.setMapTypeControl(showMapControls && !windUp);
|
|
| 2562 | - mapOptions.setPanControl(showMapControls);
|
|
| 2563 | - mapOptions.setZoomControl(showMapControls);
|
|
| 2564 | - mapOptions.setScaleControl(true);
|
|
| 2565 | - if (windUp) {
|
|
| 2566 | - mapOptions.setMinZoom(8);
|
|
| 2567 | - } else {
|
|
| 2568 | - mapOptions.setMinZoom(0);
|
|
| 2569 | - }
|
|
| 2570 | - MapTypeStyle[] mapTypeStyles = new MapTypeStyle[4];
|
|
| 2571 | -
|
|
| 2572 | - // hide all transit lines including ferry lines
|
|
| 2573 | - mapTypeStyles[0] = GoogleMapStyleHelper.createHiddenStyle(MapTypeStyleFeatureType.TRANSIT);
|
|
| 2574 | - // hide points of interest
|
|
| 2575 | - mapTypeStyles[1] = GoogleMapStyleHelper.createHiddenStyle(MapTypeStyleFeatureType.POI);
|
|
| 2576 | - // simplify road display
|
|
| 2577 | - mapTypeStyles[2] = GoogleMapStyleHelper.createSimplifiedStyle(MapTypeStyleFeatureType.ROAD);
|
|
| 2578 | - // set water color
|
|
| 2579 | - // To play with the styles, check out http://gmaps-samples-v3.googlecode.com/svn/trunk/styledmaps/wizard/index.html.
|
|
| 2580 | - // To convert an RGB color into the strange hue/saturation/lightness model used by the Google Map use
|
|
| 2581 | - // http://software.stadtwerk.org/google_maps_colorizr/#water/all/123456/.
|
|
| 2582 | - mapTypeStyles[3] = GoogleMapStyleHelper.createColorStyle(MapTypeStyleFeatureType.WATER, new RGBColor(0, 136, 255), 0, -70);
|
|
| 2583 | -
|
|
| 2584 | - MapTypeControlOptions mapTypeControlOptions = MapTypeControlOptions.newInstance();
|
|
| 2585 | - mapTypeControlOptions.setPosition(ControlPosition.BOTTOM_RIGHT);
|
|
| 2586 | - mapOptions.setMapTypeControlOptions(mapTypeControlOptions);
|
|
| 2587 | -
|
|
| 2588 | - mapOptions.setMapTypeStyles(mapTypeStyles);
|
|
| 2589 | - // no need to try to position the scale control; it always ends up at the right bottom corner
|
|
| 2590 | - mapOptions.setStreetViewControl(false);
|
|
| 2591 | - if (showMapControls) {
|
|
| 2592 | - ZoomControlOptions zoomControlOptions = ZoomControlOptions.newInstance();
|
|
| 2593 | - zoomControlOptions.setPosition(ControlPosition.RIGHT_TOP);
|
|
| 2594 | - mapOptions.setZoomControlOptions(zoomControlOptions);
|
|
| 2595 | - PanControlOptions panControlOptions = PanControlOptions.newInstance();
|
|
| 2596 | - panControlOptions.setPosition(ControlPosition.RIGHT_TOP);
|
|
| 2597 | - mapOptions.setPanControlOptions(panControlOptions);
|
|
| 2598 | - }
|
|
| 2599 | - return mapOptions;
|
|
| 2600 | - }
|
|
| 2601 | -}
|
|
| 1 | +package com.sap.sailing.gwt.ui.client.shared.racemap; |
|
| 2 | + |
|
| 3 | +import java.util.ArrayList; |
|
| 4 | +import java.util.Collection; |
|
| 5 | +import java.util.Collections; |
|
| 6 | +import java.util.Comparator; |
|
| 7 | +import java.util.Date; |
|
| 8 | +import java.util.HashMap; |
|
| 9 | +import java.util.HashSet; |
|
| 10 | +import java.util.Iterator; |
|
| 11 | +import java.util.LinkedHashMap; |
|
| 12 | +import java.util.List; |
|
| 13 | +import java.util.Map; |
|
| 14 | +import java.util.Map.Entry; |
|
| 15 | +import java.util.Set; |
|
| 16 | + |
|
| 17 | +import com.google.gwt.canvas.dom.client.CssColor; |
|
| 18 | +import com.google.gwt.core.client.GWT; |
|
| 19 | +import com.google.gwt.core.client.Scheduler; |
|
| 20 | +import com.google.gwt.core.client.Scheduler.ScheduledCommand; |
|
| 21 | +import com.google.gwt.dom.client.Style; |
|
| 22 | +import com.google.gwt.dom.client.Style.Unit; |
|
| 23 | +import com.google.gwt.event.dom.client.ClickEvent; |
|
| 24 | +import com.google.gwt.event.dom.client.ClickHandler; |
|
| 25 | +import com.google.gwt.i18n.client.NumberFormat; |
|
| 26 | +import com.google.gwt.maps.client.LoadApi; |
|
| 27 | +import com.google.gwt.maps.client.LoadApi.LoadLibrary; |
|
| 28 | +import com.google.gwt.maps.client.MapOptions; |
|
| 29 | +import com.google.gwt.maps.client.MapWidget; |
|
| 30 | +import com.google.gwt.maps.client.base.LatLng; |
|
| 31 | +import com.google.gwt.maps.client.base.LatLngBounds; |
|
| 32 | +import com.google.gwt.maps.client.controls.ControlPosition; |
|
| 33 | +import com.google.gwt.maps.client.controls.MapTypeControlOptions; |
|
| 34 | +import com.google.gwt.maps.client.controls.MapTypeStyle; |
|
| 35 | +import com.google.gwt.maps.client.controls.PanControlOptions; |
|
| 36 | +import com.google.gwt.maps.client.controls.ZoomControlOptions; |
|
| 37 | +import com.google.gwt.maps.client.events.bounds.BoundsChangeMapEvent; |
|
| 38 | +import com.google.gwt.maps.client.events.bounds.BoundsChangeMapHandler; |
|
| 39 | +import com.google.gwt.maps.client.events.click.ClickMapEvent; |
|
| 40 | +import com.google.gwt.maps.client.events.click.ClickMapHandler; |
|
| 41 | +import com.google.gwt.maps.client.events.dragend.DragEndMapEvent; |
|
| 42 | +import com.google.gwt.maps.client.events.dragend.DragEndMapHandler; |
|
| 43 | +import com.google.gwt.maps.client.events.idle.IdleMapEvent; |
|
| 44 | +import com.google.gwt.maps.client.events.idle.IdleMapHandler; |
|
| 45 | +import com.google.gwt.maps.client.events.mouseout.MouseOutMapEvent; |
|
| 46 | +import com.google.gwt.maps.client.events.mouseout.MouseOutMapHandler; |
|
| 47 | +import com.google.gwt.maps.client.events.mouseover.MouseOverMapEvent; |
|
| 48 | +import com.google.gwt.maps.client.events.mouseover.MouseOverMapHandler; |
|
| 49 | +import com.google.gwt.maps.client.events.zoom.ZoomChangeMapEvent; |
|
| 50 | +import com.google.gwt.maps.client.events.zoom.ZoomChangeMapHandler; |
|
| 51 | +import com.google.gwt.maps.client.maptypes.MapTypeStyleFeatureType; |
|
| 52 | +import com.google.gwt.maps.client.mvc.MVCArray; |
|
| 53 | +import com.google.gwt.maps.client.overlays.InfoWindow; |
|
| 54 | +import com.google.gwt.maps.client.overlays.InfoWindowOptions; |
|
| 55 | +import com.google.gwt.maps.client.overlays.Marker; |
|
| 56 | +import com.google.gwt.maps.client.overlays.MarkerOptions; |
|
| 57 | +import com.google.gwt.maps.client.overlays.Polygon; |
|
| 58 | +import com.google.gwt.maps.client.overlays.PolygonOptions; |
|
| 59 | +import com.google.gwt.maps.client.overlays.Polyline; |
|
| 60 | +import com.google.gwt.maps.client.overlays.PolylineOptions; |
|
| 61 | +import com.google.gwt.resources.client.ImageResource; |
|
| 62 | +import com.google.gwt.user.client.Window; |
|
| 63 | +import com.google.gwt.user.client.rpc.AsyncCallback; |
|
| 64 | +import com.google.gwt.user.client.ui.AbsolutePanel; |
|
| 65 | +import com.google.gwt.user.client.ui.Button; |
|
| 66 | +import com.google.gwt.user.client.ui.FlowPanel; |
|
| 67 | +import com.google.gwt.user.client.ui.Image; |
|
| 68 | +import com.google.gwt.user.client.ui.Label; |
|
| 69 | +import com.google.gwt.user.client.ui.RequiresResize; |
|
| 70 | +import com.google.gwt.user.client.ui.VerticalPanel; |
|
| 71 | +import com.google.gwt.user.client.ui.Widget; |
|
| 72 | +import com.sap.sailing.domain.common.Bearing; |
|
| 73 | +import com.sap.sailing.domain.common.Bounds; |
|
| 74 | +import com.sap.sailing.domain.common.ManeuverType; |
|
| 75 | +import com.sap.sailing.domain.common.Position; |
|
| 76 | +import com.sap.sailing.domain.common.RaceIdentifier; |
|
| 77 | +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; |
|
| 78 | +import com.sap.sailing.domain.common.Tack; |
|
| 79 | +import com.sap.sailing.domain.common.WindSource; |
|
| 80 | +import com.sap.sailing.domain.common.WindSourceType; |
|
| 81 | +import com.sap.sailing.domain.common.dto.CompetitorDTO; |
|
| 82 | +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; |
|
| 83 | +import com.sap.sailing.domain.common.impl.DegreePosition; |
|
| 84 | +import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing; |
|
| 85 | +import com.sap.sailing.domain.common.scalablevalue.impl.ScalablePosition; |
|
| 86 | +import com.sap.sailing.gwt.ui.actions.GetPolarAction; |
|
| 87 | +import com.sap.sailing.gwt.ui.actions.GetRaceMapDataAction; |
|
| 88 | +import com.sap.sailing.gwt.ui.actions.GetWindInfoAction; |
|
| 89 | +import com.sap.sailing.gwt.ui.client.ClientResources; |
|
| 90 | +import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener; |
|
| 91 | +import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider; |
|
| 92 | +import com.sap.sailing.gwt.ui.client.NumberFormatterFactory; |
|
| 93 | +import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; |
|
| 94 | +import com.sap.sailing.gwt.ui.client.RaceTimesInfoProviderListener; |
|
| 95 | +import com.sap.sailing.gwt.ui.client.RequiresDataInitialization; |
|
| 96 | +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; |
|
| 97 | +import com.sap.sailing.gwt.ui.client.StringMessages; |
|
| 98 | +import com.sap.sailing.gwt.ui.client.WindSourceTypeFormatter; |
|
| 99 | +import com.sap.sailing.gwt.ui.client.shared.filter.QuickRankProvider; |
|
| 100 | +import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapHelpLinesSettings.HelpLineTypes; |
|
| 101 | +import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings.ZoomTypes; |
|
| 102 | +import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil; |
|
| 103 | +import com.sap.sailing.gwt.ui.shared.ControlPointDTO; |
|
| 104 | +import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO; |
|
| 105 | +import com.sap.sailing.gwt.ui.shared.GPSFixDTO; |
|
| 106 | +import com.sap.sailing.gwt.ui.shared.LegInfoDTO; |
|
| 107 | +import com.sap.sailing.gwt.ui.shared.ManeuverDTO; |
|
| 108 | +import com.sap.sailing.gwt.ui.shared.MarkDTO; |
|
| 109 | +import com.sap.sailing.gwt.ui.shared.QuickRankDTO; |
|
| 110 | +import com.sap.sailing.gwt.ui.shared.RaceCourseDTO; |
|
| 111 | +import com.sap.sailing.gwt.ui.shared.RaceMapDataDTO; |
|
| 112 | +import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO; |
|
| 113 | +import com.sap.sailing.gwt.ui.shared.SidelineDTO; |
|
| 114 | +import com.sap.sailing.gwt.ui.shared.SpeedWithBearingDTO; |
|
| 115 | +import com.sap.sailing.gwt.ui.shared.WaypointDTO; |
|
| 116 | +import com.sap.sailing.gwt.ui.shared.WindDTO; |
|
| 117 | +import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO; |
|
| 118 | +import com.sap.sailing.gwt.ui.shared.WindTrackInfoDTO; |
|
| 119 | +import com.sap.sailing.gwt.ui.shared.racemap.GoogleMapAPIKey; |
|
| 120 | +import com.sap.sailing.gwt.ui.shared.racemap.GoogleMapStyleHelper; |
|
| 121 | +import com.sap.sailing.gwt.ui.shared.racemap.RaceSimulationOverlay; |
|
| 122 | +import com.sap.sailing.gwt.ui.shared.racemap.WindStreamletsRaceboardOverlay; |
|
| 123 | +import com.sap.sse.common.Util; |
|
| 124 | +import com.sap.sse.common.Util.Pair; |
|
| 125 | +import com.sap.sse.common.Util.Triple; |
|
| 126 | +import com.sap.sse.common.filter.Filter; |
|
| 127 | +import com.sap.sse.common.filter.FilterSet; |
|
| 128 | +import com.sap.sse.common.impl.RGBColor; |
|
| 129 | +import com.sap.sse.gwt.client.ErrorReporter; |
|
| 130 | +import com.sap.sse.gwt.client.async.AsyncActionsExecutor; |
|
| 131 | +import com.sap.sse.gwt.client.async.MarkedAsyncCallback; |
|
| 132 | +import com.sap.sse.gwt.client.player.TimeListener; |
|
| 133 | +import com.sap.sse.gwt.client.player.Timer; |
|
| 134 | +import com.sap.sse.gwt.client.player.Timer.PlayModes; |
|
| 135 | +import com.sap.sse.gwt.client.player.Timer.PlayStates; |
|
| 136 | +import com.sap.sse.gwt.client.shared.components.Component; |
|
| 137 | +import com.sap.sse.gwt.client.shared.components.SettingsDialog; |
|
| 138 | +import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent; |
|
| 139 | + |
|
| 140 | +public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSelectionChangeListener, RaceSelectionChangeListener, |
|
| 141 | + RaceTimesInfoProviderListener, TailFactory, Component<RaceMapSettings>, RequiresDataInitialization, RequiresResize, QuickRankProvider { |
|
| 142 | + public static final String GET_RACE_MAP_DATA_CATEGORY = "getRaceMapData"; |
|
| 143 | + public static final String GET_WIND_DATA_CATEGORY = "getWindData"; |
|
| 144 | + |
|
| 145 | + private static final String COMPACT_HEADER_STYLE = "compactHeader"; |
|
| 146 | + |
|
| 147 | + private MapWidget map; |
|
| 148 | + |
|
| 149 | + /** |
|
| 150 | + * Always valid, non-<code>null</code>. Must be used to map all coordinates, headings, bearings, and directions |
|
| 151 | + * displayed on the map, including the orientations of any canvases such as boat icons, wind displays etc. that are |
|
| 152 | + * embedded in the map. The coordinate systems facilitates the possibility of transformed displays such as |
|
| 153 | + * rotated and translated versions of the map, implementing the "wind-up" view. |
|
| 154 | + */ |
|
| 155 | + private DelegateCoordinateSystem coordinateSystem; |
|
| 156 | + |
|
| 157 | + private FlowPanel headerPanel; |
|
| 158 | + private AbsolutePanel panelForLeftHeaderLabels; |
|
| 159 | + private AbsolutePanel panelForRightHeaderLabels; |
|
| 160 | + |
|
| 161 | + private final SailingServiceAsync sailingService; |
|
| 162 | + private final ErrorReporter errorReporter; |
|
| 163 | + |
|
| 164 | + private final static ClientResources resources = GWT.create(ClientResources.class); |
|
| 165 | + |
|
| 166 | + /** |
|
| 167 | + * Polyline for the start line (connecting two marks representing the start gate). |
|
| 168 | + */ |
|
| 169 | + private Polyline startLine; |
|
| 170 | + |
|
| 171 | + /** |
|
| 172 | + * Polyline for the finish line (connecting two marks representing the finish gate). |
|
| 173 | + */ |
|
| 174 | + private Polyline finishLine; |
|
| 175 | + |
|
| 176 | + /** |
|
| 177 | + * Polyline for the advantage line (the leading line for the boats, orthogonal to the wind direction; touching the leading boat). |
|
| 178 | + */ |
|
| 179 | + private Polyline advantageLine; |
|
| 180 | + |
|
| 181 | + /** |
|
| 182 | + * The windward of two Polylines representing a triangle between startline and first mark. |
|
| 183 | + */ |
|
| 184 | + private Polyline windwardStartLineMarkToFirstMarkLine; |
|
| 185 | + |
|
| 186 | + /** |
|
| 187 | + * The leeward of two Polylines representing a triangle between startline and first mark. |
|
| 188 | + */ |
|
| 189 | + private Polyline leewardStartLineMarkToFirstMarkLine; |
|
| 190 | + |
|
| 191 | + private class AdvantageLineMouseOverMapHandler implements MouseOverMapHandler { |
|
| 192 | + private double trueWindAngle; |
|
| 193 | + private Date date; |
|
| 194 | + |
|
| 195 | + public AdvantageLineMouseOverMapHandler(double trueWindAngle, Date date) { |
|
| 196 | + this.trueWindAngle = trueWindAngle; |
|
| 197 | + this.date = date; |
|
| 198 | + } |
|
| 199 | + |
|
| 200 | + public void setTrueWindBearing(double trueWindAngle) { |
|
| 201 | + this.trueWindAngle = trueWindAngle; |
|
| 202 | + } |
|
| 203 | + |
|
| 204 | + public void setDate(Date date) { |
|
| 205 | + this.date = date; |
|
| 206 | + } |
|
| 207 | + |
|
| 208 | + @Override |
|
| 209 | + public void onEvent(MouseOverMapEvent event) { |
|
| 210 | + map.setTitle(stringMessages.advantageLine()+" (from "+new DegreeBearingImpl(Math.round(trueWindAngle)).reverse().getDegrees()+"deg"+ |
|
| 211 | + (date == null ? ")" : ", "+ date) + ")"); |
|
| 212 | + } |
|
| 213 | + }; |
|
| 214 | + |
|
| 215 | + private AdvantageLineMouseOverMapHandler advantageLineMouseOverHandler; |
|
| 216 | + |
|
| 217 | + /** |
|
| 218 | + * Polylines for the course middle lines; keys are the two control points delimiting the leg for which the |
|
| 219 | + * {@link Polyline} value shows the course middle line. As only one course middle line is shown even if there |
|
| 220 | + * are multiple legs using the same control points in different directions, using a {@link Set} makes this |
|
| 221 | + * independent of the order of the two control points. If no course middle line is currently being shown for |
|
| 222 | + * a pair of control points, the map will not contain a value for this pair. |
|
| 223 | + */ |
|
| 224 | + private final Map<Set<ControlPointDTO>, Polyline> courseMiddleLines; |
|
| 225 | + |
|
| 226 | + private final Map<SidelineDTO, Polygon> courseSidelines; |
|
| 227 | + |
|
| 228 | + /** |
|
| 229 | + * When the {@link HelpLineTypes#COURSEGEOMETRY} option is selected, little markers will be displayed on the |
|
| 230 | + * lines that show the tooltip text in a little info box linked to the line. When the line is removed by |
|
| 231 | + * {@link #showOrRemoveOrUpdateLine(Polyline, boolean, Position, Position, LineInfoProvider, String)}, these |
|
| 232 | + * overlays need to be removed as well. Also, when the {@link HelpLineTypes#COURSEGEOMETRY} setting is |
|
| 233 | + * deactivated, all these overlays need to go. |
|
| 234 | + */ |
|
| 235 | + private final Map<Polyline, SmallTransparentInfoOverlay> infoOverlaysForLinesForCourseGeometry; |
|
| 236 | + |
|
| 237 | + /** |
|
| 238 | + * Wind data used to display the advantage line. Retrieved by a {@link GetWindInfoAction} execution and used in |
|
| 239 | + * {@link #showAdvantageLine(Iterable, Date)}. |
|
| 240 | + */ |
|
| 241 | + private WindInfoForRaceDTO lastCombinedWindTrackInfoDTO; |
|
| 242 | + |
|
| 243 | + /** |
|
| 244 | + * Manages the cached set of {@link GPSFixDTO}s for the boat positions as well as their graphical counterpart in the |
|
| 245 | + * form of {@link Polyline}s. |
|
| 246 | + */ |
|
| 247 | + private final FixesAndTails fixesAndTails; |
|
| 248 | + |
|
| 249 | + /** |
|
| 250 | + * html5 canvases used as boat display on the map |
|
| 251 | + */ |
|
| 252 | + private final Map<CompetitorDTO, BoatOverlay> boatOverlays; |
|
| 253 | + |
|
| 254 | + /** |
|
| 255 | + * html5 canvases used for competitor info display on the map |
|
| 256 | + */ |
|
| 257 | + private final Map<CompetitorDTO, CompetitorInfoOverlay> competitorInfoOverlays; |
|
| 258 | + |
|
| 259 | + private SmallTransparentInfoOverlay countDownOverlay; |
|
| 260 | + |
|
| 261 | + /** |
|
| 262 | + * Map overlays with html5 canvas used to display wind sensors |
|
| 263 | + */ |
|
| 264 | + private final Map<WindSource, WindSensorOverlay> windSensorOverlays; |
|
| 265 | + |
|
| 266 | + /** |
|
| 267 | + * Map overlays with html5 canvas used to display course marks including buoy zones |
|
| 268 | + */ |
|
| 269 | + private final Map<String, CourseMarkOverlay> courseMarkOverlays; |
|
| 270 | + |
|
| 271 | + private final Map<String, MarkDTO> markDTOs; |
|
| 272 | + |
|
| 273 | + /** |
|
| 274 | + * markers displayed in response to |
|
| 275 | + * {@link SailingServiceAsync#getDouglasPoints(String, String, Map, Map, double, AsyncCallback)} |
|
| 276 | + */ |
|
| 277 | + protected Set<Marker> douglasMarkers; |
|
| 278 | + |
|
| 279 | + /** |
|
| 280 | + * markers displayed in response to |
|
| 281 | + * {@link SailingServiceAsync#getDouglasPoints(String, String, Map, Map, double, AsyncCallback)} |
|
| 282 | + */ |
|
| 283 | + private Set<Marker> maneuverMarkers; |
|
| 284 | + |
|
| 285 | + private Map<CompetitorDTO, List<ManeuverDTO>> lastManeuverResult; |
|
| 286 | + |
|
| 287 | + private Map<CompetitorDTO, List<GPSFixDTO>> lastDouglasPeuckerResult; |
|
| 288 | + |
|
| 289 | + private final CompetitorSelectionProvider competitorSelection; |
|
| 290 | + |
|
| 291 | + private List<RegattaAndRaceIdentifier> selectedRaces; |
|
| 292 | + |
|
| 293 | + /** |
|
| 294 | + * Used to check if the first initial zoom to the mark markers was already done. |
|
| 295 | + */ |
|
| 296 | + private boolean mapFirstZoomDone = false; |
|
| 297 | + |
|
| 298 | + private final Timer timer; |
|
| 299 | + |
|
| 300 | + private RaceTimesInfoDTO lastRaceTimesInfo; |
|
| 301 | + |
|
| 302 | + private InfoWindow lastInfoWindow = null; |
|
| 303 | + |
|
| 304 | + /** |
|
| 305 | + * RPC calls may receive responses out of order if there are multiple calls in-flight at the same time. If the time |
|
| 306 | + * slider is moved quickly it generates many requests for boat positions quickly after each other. Sometimes, |
|
| 307 | + * responses for requests send later may return before the responses to all earlier requests have been received and |
|
| 308 | + * processed. This counter is used to number the requests. When processing of a response for a later request has |
|
| 309 | + * already begun, responses to earlier requests will be ignored. |
|
| 310 | + */ |
|
| 311 | + private int boatPositionRequestIDCounter; |
|
| 312 | + |
|
| 313 | + /** |
|
| 314 | + * Corresponds to {@link #boatPositionRequestIDCounter}. As soon as the processing of a response for a request ID |
|
| 315 | + * begins, this attribute is set to the ID. A response won't be processed if a later response is already being |
|
| 316 | + * processed. |
|
| 317 | + */ |
|
| 318 | + private int startedProcessingRequestID; |
|
| 319 | + |
|
| 320 | + private RaceMapImageManager raceMapImageManager; |
|
| 321 | + |
|
| 322 | + private final RaceMapSettings settings; |
|
| 323 | + |
|
| 324 | + private final StringMessages stringMessages; |
|
| 325 | + |
|
| 326 | + private boolean isMapInitialized; |
|
| 327 | + |
|
| 328 | + private Date lastTimeChangeBeforeInitialization; |
|
| 329 | + |
|
| 330 | + private int lastLegNumber; |
|
| 331 | + |
|
| 332 | + /** |
|
| 333 | + * The last quick ranks received from a call to {@link SailingServiceAsync#getQuickRanks(RaceIdentifier, Date, AsyncCallback)} upon |
|
| 334 | + * the last {@link #timeChanged(Date, Date)} event. Therefore, the ranks listed here correspond to the {@link #timer}'s time. |
|
| 335 | + */ |
|
| 336 | + private LinkedHashMap<CompetitorDTO, QuickRankDTO> quickRanks; |
|
| 337 | + |
|
| 338 | + /** |
|
| 339 | + * Taken from {@link RaceMapDataDTO#competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber}; tells the |
|
| 340 | + * windward distances traveled and the leg numbers in which the respective competitor is. |
|
| 341 | + */ |
|
| 342 | + private LinkedHashMap<CompetitorDTO, Integer> competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber; |
|
| 343 | + |
|
| 344 | + |
|
| 345 | + private final CombinedWindPanel combinedWindPanel; |
|
| 346 | + |
|
| 347 | + private final TrueNorthIndicatorPanel trueNorthIndicatorPanel; |
|
| 348 | + |
|
| 349 | + private final AsyncActionsExecutor asyncActionsExecutor; |
|
| 350 | + |
|
| 351 | + /** |
|
| 352 | + * The map bounds as last received by map callbacks; used to determine whether to suppress the boat animation during zoom/pan |
|
| 353 | + */ |
|
| 354 | + private LatLngBounds currentMapBounds; // bounds to which bounds-changed-handler compares |
|
| 355 | + private int currentZoomLevel; // zoom-level to which bounds-changed-handler compares |
|
| 356 | + |
|
| 357 | + private boolean autoZoomIn = false; // flags auto-zoom-in in progress |
|
| 358 | + private boolean autoZoomOut = false; // flags auto-zoom-out in progress |
|
| 359 | + private int autoZoomLevel; // zoom-level to which auto-zoom-in/-out is zooming |
|
| 360 | + LatLngBounds autoZoomLatLngBounds; // bounds to which auto-zoom-in/-out is panning&zooming |
|
| 361 | + |
|
| 362 | + private RaceSimulationOverlay simulationOverlay; |
|
| 363 | + private WindStreamletsRaceboardOverlay streamletOverlay; |
|
| 364 | + private final boolean showViewStreamlets; |
|
| 365 | + private final boolean showViewStreamletColors; |
|
| 366 | + private final boolean showViewSimulation; |
|
| 367 | + |
|
| 368 | + private static final String GET_POLAR_CATEGORY = "getPolar"; |
|
| 369 | + |
|
| 370 | + /** |
|
| 371 | + * Tells about the availability of polar / VPP data for this race. If available, the simulation feature can be |
|
| 372 | + * offered to the user. |
|
| 373 | + */ |
|
| 374 | + private boolean hasPolar; |
|
| 375 | + |
|
| 376 | + private final RegattaAndRaceIdentifier raceIdentifier; |
|
| 377 | + |
|
| 378 | + /** |
|
| 379 | + * When the user requests wind-up display this may happen at a point where no mark positions are known or when |
|
| 380 | + * no wind direction is known yet. In this case, this flag will be set, and when wind information or course mark |
|
| 381 | + * positions are received later, this flag is checked, and if set, a {@link #updateCoordinateSystemFromSettings()} |
|
| 382 | + * call is issued to make sure that the user's request for a new coordinate system is honored. |
|
| 383 | + */ |
|
| 384 | + private boolean requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown; |
|
| 385 | + |
|
| 386 | + private final boolean showMapControls; |
|
| 387 | + |
|
| 388 | + /** |
|
| 389 | + * Tells whether currently an auto-zoom is in progress; this is used particularly to keep the smooth CSS boat transitions |
|
| 390 | + * active while auto-zooming whereas stopping them seems the better option for manual zooms. |
|
| 391 | + */ |
|
| 392 | + private boolean autoZoomInProgress; |
|
| 393 | + |
|
| 394 | + /** |
|
| 395 | + * Tells whether currently an orientation change is in progress; this is required handle map events during the configuration of the map |
|
| 396 | + * during an orientation change. |
|
| 397 | + */ |
|
| 398 | + private boolean orientationChangeInProgress; |
|
| 399 | + |
|
| 400 | + private final NumberFormat numberFormatOneDecimal = NumberFormat.getFormat("0.0"); |
|
| 401 | + |
|
| 402 | + public RaceMap(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, |
|
| 403 | + ErrorReporter errorReporter, Timer timer, CompetitorSelectionProvider competitorSelection, StringMessages stringMessages, |
|
| 404 | + boolean showMapControls, boolean showViewStreamlets, boolean showViewStreamletColors, boolean showViewSimulation, |
|
| 405 | + RegattaAndRaceIdentifier raceIdentifier, CombinedWindPanelStyle combinedWindPanelStyle, boolean showHeaderPanel) { |
|
| 406 | + this.setSize("100%", "100%"); |
|
| 407 | + this.showMapControls = showMapControls; |
|
| 408 | + this.stringMessages = stringMessages; |
|
| 409 | + this.sailingService = sailingService; |
|
| 410 | + this.raceIdentifier = raceIdentifier; |
|
| 411 | + this.asyncActionsExecutor = asyncActionsExecutor; |
|
| 412 | + this.errorReporter = errorReporter; |
|
| 413 | + this.timer = timer; |
|
| 414 | + timer.addTimeListener(this); |
|
| 415 | + raceMapImageManager = new RaceMapImageManager(); |
|
| 416 | + markDTOs = new HashMap<String, MarkDTO>(); |
|
| 417 | + courseSidelines = new HashMap<>(); |
|
| 418 | + courseMiddleLines = new HashMap<>(); |
|
| 419 | + infoOverlaysForLinesForCourseGeometry = new HashMap<>(); |
|
| 420 | + boatOverlays = new HashMap<CompetitorDTO, BoatOverlay>(); |
|
| 421 | + competitorInfoOverlays = new HashMap<CompetitorDTO, CompetitorInfoOverlay>(); |
|
| 422 | + windSensorOverlays = new HashMap<WindSource, WindSensorOverlay>(); |
|
| 423 | + courseMarkOverlays = new HashMap<String, CourseMarkOverlay>(); |
|
| 424 | + this.competitorSelection = competitorSelection; |
|
| 425 | + competitorSelection.addCompetitorSelectionChangeListener(this); |
|
| 426 | + settings = new RaceMapSettings(); |
|
| 427 | + coordinateSystem = new DelegateCoordinateSystem(new IdentityCoordinateSystem()); |
|
| 428 | + fixesAndTails = new FixesAndTails(coordinateSystem); |
|
| 429 | + updateCoordinateSystemFromSettings(); |
|
| 430 | + lastTimeChangeBeforeInitialization = null; |
|
| 431 | + isMapInitialized = false; |
|
| 432 | + this.showViewStreamlets = showViewStreamlets; |
|
| 433 | + this.showViewStreamletColors = showViewStreamletColors; |
|
| 434 | + this.showViewSimulation = showViewSimulation; |
|
| 435 | + this.hasPolar = false; |
|
| 436 | + headerPanel = new FlowPanel(); |
|
| 437 | + headerPanel.setStyleName("RaceMap-HeaderPanel"); |
|
| 438 | + panelForLeftHeaderLabels = new AbsolutePanel(); |
|
| 439 | + panelForRightHeaderLabels = new AbsolutePanel(); |
|
| 440 | + initializeData(showMapControls, showHeaderPanel); |
|
| 441 | + combinedWindPanel = new CombinedWindPanel(raceMapImageManager, combinedWindPanelStyle, stringMessages, coordinateSystem); |
|
| 442 | + combinedWindPanel.setVisible(false); |
|
| 443 | + trueNorthIndicatorPanel = new TrueNorthIndicatorPanel(this, raceMapImageManager, combinedWindPanelStyle, stringMessages, coordinateSystem); |
|
| 444 | + trueNorthIndicatorPanel.setVisible(true); |
|
| 445 | + orientationChangeInProgress = false; |
|
| 446 | + } |
|
| 447 | + |
|
| 448 | + /** |
|
| 449 | + * The {@link WindDTO#dampenedTrueWindFromDeg} direction if {@link #lastCombinedWindTrackInfoDTO} has a |
|
| 450 | + * {@link WindSourceType#COMBINED} source which has at least one fix recorded; <code>null</code> otherwise. |
|
| 451 | + */ |
|
| 452 | + private Bearing getLastCombinedTrueWindFromDirection() { |
|
| 453 | + if (lastCombinedWindTrackInfoDTO != null) { |
|
| 454 | + for (Entry<WindSource, WindTrackInfoDTO> e : lastCombinedWindTrackInfoDTO.windTrackInfoByWindSource.entrySet()) { |
|
| 455 | + if (e.getKey().getType() == WindSourceType.COMBINED) { |
|
| 456 | + final List<WindDTO> windFixes = e.getValue().windFixes; |
|
| 457 | + if (!windFixes.isEmpty()) { |
|
| 458 | + return new DegreeBearingImpl(windFixes.get(0).dampenedTrueWindFromDeg); |
|
| 459 | + } |
|
| 460 | + } |
|
| 461 | + } |
|
| 462 | + } |
|
| 463 | + return null; |
|
| 464 | + } |
|
| 465 | + |
|
| 466 | + private void updateCoordinateSystemFromSettings() { |
|
| 467 | + final MapOptions mapOptions; |
|
| 468 | + orientationChangeInProgress = true; |
|
| 469 | + if (getSettings().isWindUp()) { |
|
| 470 | + final Position centerOfCourse = getCenterOfCourse(); |
|
| 471 | + if (centerOfCourse != null) { |
|
| 472 | + final Bearing lastCombinedTrueWindFromDirection = getLastCombinedTrueWindFromDirection(); |
|
| 473 | + if (lastCombinedTrueWindFromDirection != null) { |
|
| 474 | + // new equator shall point 90deg right of the "from" wind direction to make wind come from top of map |
|
| 475 | + coordinateSystem.setCoordinateSystem(new RotateAndTranslateCoordinateSystem(centerOfCourse, |
|
| 476 | + lastCombinedTrueWindFromDirection.add(new DegreeBearingImpl(90)))); |
|
| 477 | + if (map != null) { |
|
| 478 | + mapOptions = getMapOptions(showMapControls, /* wind-up */ true); |
|
| 479 | + } else { |
|
| 480 | + mapOptions = null; |
|
| 481 | + } |
|
| 482 | + requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown = false; |
|
| 483 | + } else { |
|
| 484 | + // register callback in case center of course and wind info becomes known |
|
| 485 | + requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown = true; |
|
| 486 | + mapOptions = null; |
|
| 487 | + } |
|
| 488 | + } else { |
|
| 489 | + // register callback in case center of course and wind info becomes known |
|
| 490 | + requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown = true; |
|
| 491 | + mapOptions = null; |
|
| 492 | + } |
|
| 493 | + } else { |
|
| 494 | + if (map != null) { |
|
| 495 | + mapOptions = getMapOptions(showMapControls, /* wind-up */ false); |
|
| 496 | + } else { |
|
| 497 | + mapOptions = null; |
|
| 498 | + } |
|
| 499 | + coordinateSystem.setCoordinateSystem(new IdentityCoordinateSystem()); |
|
| 500 | + } |
|
| 501 | + if (mapOptions != null) { // if no coordinate system change happened that affects an existing map, don't redraw |
|
| 502 | + fixesAndTails.clearTails(); |
|
| 503 | + redraw(); |
|
| 504 | + // zooming and setting options while the event loop is still working doesn't work reliably; defer until event loop returns |
|
| 505 | + Scheduler.get().scheduleDeferred(new ScheduledCommand() { |
|
| 506 | + @Override |
|
| 507 | + public void execute() { |
|
| 508 | + if (map != null) { |
|
| 509 | + map.setOptions(mapOptions); |
|
| 510 | + // ensure zooming to what the settings tell, or defaults if what the settings tell isn't possible right now |
|
| 511 | + mapFirstZoomDone = false; |
|
| 512 | + trueNorthIndicatorPanel.redraw(); |
|
| 513 | + orientationChangeInProgress = false; |
|
| 514 | + } |
|
| 515 | + } |
|
| 516 | + }); |
|
| 517 | + } |
|
| 518 | + } |
|
| 519 | + |
|
| 520 | + private void loadMapsAPIV3(final boolean showMapControls, final boolean showHeaderPanel) { |
|
| 521 | + boolean sensor = true; |
|
| 522 | + |
|
| 523 | + // load all the libs for use in the maps |
|
| 524 | + ArrayList<LoadLibrary> loadLibraries = new ArrayList<LoadApi.LoadLibrary>(); |
|
| 525 | + loadLibraries.add(LoadLibrary.DRAWING); |
|
| 526 | + loadLibraries.add(LoadLibrary.GEOMETRY); |
|
| 527 | + |
|
| 528 | + Runnable onLoad = new Runnable() { |
|
| 529 | + @Override |
|
| 530 | + public void run() { |
|
| 531 | + MapOptions mapOptions = getMapOptions(showMapControls, /* wind up */ false); |
|
| 532 | + map = new MapWidget(mapOptions); |
|
| 533 | + RaceMap.this.add(map, 0, 0); |
|
| 534 | + if (showHeaderPanel) { |
|
| 535 | + Image sapLogo = createSAPLogo(); |
|
| 536 | + RaceMap.this.add(sapLogo); |
|
| 537 | + } |
|
| 538 | + map.setControls(ControlPosition.LEFT_TOP, combinedWindPanel); |
|
| 539 | + map.setControls(ControlPosition.LEFT_TOP, trueNorthIndicatorPanel); |
|
| 540 | + adjustLeftControlsIndent(); |
|
| 541 | + |
|
| 542 | + RaceMap.this.raceMapImageManager.loadMapIcons(map); |
|
| 543 | + map.setSize("100%", "100%"); |
|
| 544 | + map.addZoomChangeHandler(new ZoomChangeMapHandler() { |
|
| 545 | + @Override |
|
| 546 | + public void onEvent(ZoomChangeMapEvent event) { |
|
| 547 | + if (!autoZoomIn && !autoZoomOut && !orientationChangeInProgress) { |
|
| 548 | + // stop automatic zoom after a manual zoom event; automatic zoom in zoomMapToNewBounds will restore old settings |
|
| 549 | + final List<RaceMapZoomSettings.ZoomTypes> emptyList = Collections.emptyList(); |
|
| 550 | + settings.getZoomSettings().setTypesToConsiderOnZoom(emptyList); |
|
| 551 | + } |
|
| 552 | + // TODO bug489 when in wind-up mode, avoid zooming out too far; perhaps zoom back in if zoomed out too far |
|
| 553 | + } |
|
| 554 | + }); |
|
| 555 | + map.addDragEndHandler(new DragEndMapHandler() { |
|
| 556 | + @Override |
|
| 557 | + public void onEvent(DragEndMapEvent event) { |
|
| 558 | + // stop automatic zoom after a manual drag event |
|
| 559 | + autoZoomIn = false; |
|
| 560 | + autoZoomOut = false; |
|
| 561 | + final List<RaceMapZoomSettings.ZoomTypes> emptyList = Collections.emptyList(); |
|
| 562 | + settings.getZoomSettings().setTypesToConsiderOnZoom(emptyList); |
|
| 563 | + } |
|
| 564 | + }); |
|
| 565 | + map.addIdleHandler(new IdleMapHandler() { |
|
| 566 | + @Override |
|
| 567 | + public void onEvent(IdleMapEvent event) { |
|
| 568 | + // the "idle"-event is raised at the end of map-animations |
|
| 569 | + if (autoZoomIn) { |
|
| 570 | + // finalize zoom-in that was started with panTo() in zoomMapToNewBounds() |
|
| 571 | + map.setZoom(autoZoomLevel); |
|
| 572 | + autoZoomIn = false; |
|
| 573 | + } |
|
| 574 | + if (autoZoomOut) { |
|
| 575 | + // finalize zoom-out that was started with setZoom() in zoomMapToNewBounds() |
|
| 576 | + map.panTo(autoZoomLatLngBounds.getCenter()); |
|
| 577 | + autoZoomOut = false; |
|
| 578 | + } |
|
| 579 | + } |
|
| 580 | + }); |
|
| 581 | + map.addBoundsChangeHandler(new BoundsChangeMapHandler() { |
|
| 582 | + @Override |
|
| 583 | + public void onEvent(BoundsChangeMapEvent event) { |
|
| 584 | + int newZoomLevel = map.getZoom(); |
|
| 585 | + if (!isAutoZoomInProgress() && (newZoomLevel != currentZoomLevel)) { |
|
| 586 | + removeTransitions(); |
|
| 587 | + } |
|
| 588 | + if ((streamletOverlay != null) && !map.getBounds().equals(currentMapBounds)) { |
|
| 589 | + streamletOverlay.onBoundsChanged(newZoomLevel != currentZoomLevel); |
|
| 590 | + } |
|
| 591 | + if ((simulationOverlay != null) && !map.getBounds().equals(currentMapBounds)) { |
|
| 592 | + simulationOverlay.onBoundsChanged(newZoomLevel != currentZoomLevel); |
|
| 593 | + } |
|
| 594 | + currentMapBounds = map.getBounds(); |
|
| 595 | + currentZoomLevel = newZoomLevel; |
|
| 596 | + headerPanel.getElement().getStyle().setWidth(map.getOffsetWidth(), Unit.PX); |
|
| 597 | + } |
|
| 598 | + }); |
|
| 599 | + |
|
| 600 | + // If there was a time change before the API was loaded, reset the time |
|
| 601 | + if (lastTimeChangeBeforeInitialization != null) { |
|
| 602 | + timeChanged(lastTimeChangeBeforeInitialization, null); |
|
| 603 | + lastTimeChangeBeforeInitialization = null; |
|
| 604 | + } |
|
| 605 | + // Initialize streamlet canvas for wind visualization; it shouldn't be doing anything unless it's visible |
|
| 606 | + streamletOverlay = new WindStreamletsRaceboardOverlay(getMap(), /* zIndex */ 0, |
|
| 607 | + timer, raceIdentifier, sailingService, asyncActionsExecutor, stringMessages, coordinateSystem); |
|
| 608 | + streamletOverlay.addToMap(); |
|
| 609 | + if (showViewStreamlets) { |
|
| 610 | + streamletOverlay.setColors(showViewStreamletColors); |
|
| 611 | + streamletOverlay.setVisible(true); |
|
| 612 | + } |
|
| 613 | + |
|
| 614 | + if (showViewSimulation) { |
|
| 615 | + // determine availability of polar diagram |
|
| 616 | + setHasPolar(); |
|
| 617 | + // initialize simulation canvas |
|
| 618 | + simulationOverlay = new RaceSimulationOverlay(getMap(), /* zIndex */ 0, raceIdentifier, sailingService, stringMessages, asyncActionsExecutor, coordinateSystem); |
|
| 619 | + simulationOverlay.addToMap(); |
|
| 620 | + simulationOverlay.setVisible(false); |
|
| 621 | + } |
|
| 622 | + if (showHeaderPanel) { |
|
| 623 | + createHeaderPanel(map); |
|
| 624 | + } |
|
| 625 | + createSettingsButton(map); |
|
| 626 | + |
|
| 627 | + // Data has been initialized |
|
| 628 | + RaceMap.this.isMapInitialized = true; |
|
| 629 | + RaceMap.this.redraw(); |
|
| 630 | + trueNorthIndicatorPanel.redraw(); |
|
| 631 | + showAdditionalControls(map); |
|
| 632 | + } |
|
| 633 | + }; |
|
| 634 | + LoadApi.go(onLoad, loadLibraries, sensor, "key="+GoogleMapAPIKey.V3_APIKey); |
|
| 635 | + } |
|
| 636 | + |
|
| 637 | + /** |
|
| 638 | + * Subclasses may define additional stuff to be shown on the map. |
|
| 639 | + */ |
|
| 640 | + protected void showAdditionalControls(MapWidget map) { |
|
| 641 | + } |
|
| 642 | + |
|
| 643 | + private void setHasPolar() { |
|
| 644 | + GetPolarAction getPolar = new GetPolarAction(sailingService, raceIdentifier); |
|
| 645 | + asyncActionsExecutor.execute(getPolar, GET_POLAR_CATEGORY, |
|
| 646 | + new MarkedAsyncCallback<>(new AsyncCallback<Boolean>() { |
|
| 647 | + @Override |
|
| 648 | + public void onFailure(Throwable caught) { |
|
| 649 | + errorReporter.reportError(stringMessages.errorDeterminingPolarAvailability( |
|
| 650 | + raceIdentifier.getRaceName(), caught.getMessage()), /* silent */ true); |
|
| 651 | + } |
|
| 652 | + |
|
| 653 | + @Override |
|
| 654 | + public void onSuccess(Boolean result) { |
|
| 655 | + // store results |
|
| 656 | + hasPolar = result.booleanValue(); |
|
| 657 | + } |
|
| 658 | + })); |
|
| 659 | + |
|
| 660 | + } |
|
| 661 | + |
|
| 662 | + /** |
|
| 663 | + * Creates a header panel where additional information can be displayed by using |
|
| 664 | + * {@link #getLeftHeaderPanel()} or {@link #getRightHeaderPanel()}. |
|
| 665 | + * |
|
| 666 | + * This panel is transparent and configured in such a way that it moves other controls |
|
| 667 | + * down by its height. To achieve the goal of not having added widgets transparent |
|
| 668 | + * this widget consists of two parts: First one is the transparent panel and the |
|
| 669 | + * second one is the panel for the controls. The controls then need to moved onto |
|
| 670 | + * the panel by using CSS. |
|
| 671 | + */ |
|
| 672 | + private void createHeaderPanel(MapWidget map) { |
|
| 673 | + // we need a panel that does not have any transparency to have the |
|
| 674 | + // labels shown in the right color. This panel also needs to have |
|
| 675 | + // a higher z-index than other elements on the map |
|
| 676 | + map.setControls(ControlPosition.TOP_LEFT, panelForLeftHeaderLabels); |
|
| 677 | + panelForLeftHeaderLabels.getElement().getParentElement().getStyle().setProperty("zIndex", "1"); |
|
| 678 | + panelForLeftHeaderLabels.getElement().getStyle().setProperty("overflow", "visible"); |
|
| 679 | + add(panelForRightHeaderLabels); |
|
| 680 | + panelForRightHeaderLabels.getElement().getStyle().setProperty("zIndex", "1"); |
|
| 681 | + panelForRightHeaderLabels.getElement().getStyle().setProperty("overflow", "visible"); |
|
| 682 | + // need to initialize size before css kicks in to make sure |
|
| 683 | + // that controls get positioned right |
|
| 684 | + headerPanel.getElement().getStyle().setHeight(60, Unit.PX); |
|
| 685 | + headerPanel.getElement().getStyle().setWidth(map.getOffsetWidth(), Unit.PX); |
|
| 686 | + // some sort of hack: not positioning TOP_LEFT because then the |
|
| 687 | + // controls at RIGHT would not get the correct top setting |
|
| 688 | + map.setControls(ControlPosition.TOP_RIGHT, headerPanel); |
|
| 689 | + } |
|
| 690 | + |
|
| 691 | + private void createSettingsButton(MapWidget map) { |
|
| 692 | + final Component<RaceMapSettings> component = this; |
|
| 693 | + Button settingsButton = new Button(); |
|
| 694 | + settingsButton.setStyleName("gwt-MapSettingsButton"); |
|
| 695 | + settingsButton.setTitle(stringMessages.settings()); |
|
| 696 | + settingsButton.addClickHandler(new ClickHandler() { |
|
| 697 | + @Override |
|
| 698 | + public void onClick(ClickEvent event) { |
|
| 699 | + new SettingsDialog<RaceMapSettings>(component, stringMessages).show(); |
|
| 700 | + } |
|
| 701 | + }); |
|
| 702 | + map.setControls(ControlPosition.RIGHT_TOP, settingsButton); |
|
| 703 | + } |
|
| 704 | + |
|
| 705 | + private void removeTransitions() { |
|
| 706 | + // remove the canvas animations for boats |
|
| 707 | + for (BoatOverlay boatOverlay : RaceMap.this.getBoatOverlays().values()) { |
|
| 708 | + boatOverlay.removeCanvasPositionAndRotationTransition(); |
|
| 709 | + } |
|
| 710 | + // remove the canvas animations for the info overlays of the selected boats |
|
| 711 | + for (CompetitorInfoOverlay infoOverlay : competitorInfoOverlays.values()) { |
|
| 712 | + infoOverlay.removeCanvasPositionAndRotationTransition(); |
|
| 713 | + } |
|
| 714 | + } |
|
| 715 | + |
|
| 716 | + public void redraw() { |
|
| 717 | + timeChanged(timer.getTime(), null); |
|
| 718 | + } |
|
| 719 | + |
|
| 720 | + Map<CompetitorDTO, BoatOverlay> getBoatOverlays() { |
|
| 721 | + return Collections.unmodifiableMap(boatOverlays); |
|
| 722 | + } |
|
| 723 | + |
|
| 724 | + public MapWidget getMap() { |
|
| 725 | + return map; |
|
| 726 | + } |
|
| 727 | + |
|
| 728 | + /** |
|
| 729 | + * @return the Panel where labels or other controls for the header can be positioned |
|
| 730 | + */ |
|
| 731 | + public AbsolutePanel getLeftHeaderPanel() { |
|
| 732 | + return panelForLeftHeaderLabels; |
|
| 733 | + } |
|
| 734 | + |
|
| 735 | + public AbsolutePanel getRightHeaderPanel() { |
|
| 736 | + return panelForRightHeaderLabels; |
|
| 737 | + } |
|
| 738 | + |
|
| 739 | + @Override |
|
| 740 | + public void onRaceSelectionChange(List<RegattaAndRaceIdentifier> selectedRaces) { |
|
| 741 | + mapFirstZoomDone = false; |
|
| 742 | + // TODO bug 494: reset zoom settings to user preferences |
|
| 743 | + this.selectedRaces = selectedRaces; |
|
| 744 | + } |
|
| 745 | + |
|
| 746 | + @Override |
|
| 747 | + public void raceTimesInfosReceived(Map<RegattaAndRaceIdentifier, RaceTimesInfoDTO> raceTimesInfos, long clientTimeWhenRequestWasSent, Date serverTimeDuringRequest, long clientTimeWhenResponseWasReceived) { |
|
| 748 | + timer.adjustClientServerOffset(clientTimeWhenRequestWasSent, serverTimeDuringRequest, clientTimeWhenResponseWasReceived); |
|
| 749 | + this.lastRaceTimesInfo = raceTimesInfos.get(selectedRaces.get(0)); |
|
| 750 | + } |
|
| 751 | + |
|
| 752 | + /** |
|
| 753 | + * In {@link PlayModes#Live live mode}, when {@link #loadCompleteLeaderboard(Date) loading the leaderboard contents}, <code>null</code> |
|
| 754 | + * is used as time point. The condition for this is encapsulated in this method so others can find out. For example, when a time change |
|
| 755 | + * is signaled due to local offset / delay adjustments, no additional call to {@link #loadCompleteLeaderboard(Date)} would be required |
|
| 756 | + * as <code>null</code> will be passed in any case, not being affected by local time offsets. |
|
| 757 | + */ |
|
| 758 | + private boolean useNullAsTimePoint() { |
|
| 759 | + return timer.getPlayMode() == PlayModes.Live; |
|
| 760 | + } |
|
| 761 | + |
|
| 762 | + @Override |
|
| 763 | + public void timeChanged(final Date newTime, final Date oldTime) { |
|
| 764 | + if (newTime != null && isMapInitialized) { |
|
| 765 | + if (selectedRaces != null && !selectedRaces.isEmpty()) { |
|
| 766 | + RegattaAndRaceIdentifier race = selectedRaces.get(selectedRaces.size() - 1); |
|
| 767 | + final Iterable<CompetitorDTO> competitorsToShow = getCompetitorsToShow(); |
|
| 768 | + |
|
| 769 | + if (race != null) { |
|
| 770 | + final com.sap.sse.common.Util.Triple<Map<CompetitorDTO, Date>, Map<CompetitorDTO, Date>, Map<CompetitorDTO, Boolean>> fromAndToAndOverlap = |
|
| 771 | + fixesAndTails.computeFromAndTo(newTime, competitorsToShow, settings.getEffectiveTailLengthInMilliseconds()); |
|
| 772 | + int requestID = ++boatPositionRequestIDCounter; |
|
| 773 | + // For those competitors for which the tails don't overlap (and therefore will be replaced by the new tail coming from the server) |
|
| 774 | + // we expect some potential delay in computing the full tail. Therefore, in those cases we fire two requests: one fetching only the |
|
| 775 | + // boat positions at newTime with zero tail length; and another one fetching everything else. |
|
| 776 | + GetRaceMapDataAction getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping = getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping(fromAndToAndOverlap, race, newTime); |
|
| 777 | + if (getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping != null) { |
|
| 778 | + asyncActionsExecutor.execute(getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping, GET_RACE_MAP_DATA_CATEGORY, |
|
| 779 | + getRaceMapDataCallback(oldTime, newTime, fromAndToAndOverlap.getC(), competitorsToShow, requestID)); |
|
| 780 | + requestID = ++boatPositionRequestIDCounter; |
|
| 781 | + } |
|
| 782 | + // next, do the full thing; being the later call, if request throttling kicks in, the later call |
|
| 783 | + // supersedes the earlier call which may get dropped then |
|
| 784 | + GetRaceMapDataAction getRaceMapDataAction = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(), race, |
|
| 785 | + useNullAsTimePoint() ? null : newTime, fromAndToAndOverlap.getA(), fromAndToAndOverlap.getB(), /* extrapolate */ true, (settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null)); |
|
| 786 | + asyncActionsExecutor.execute(getRaceMapDataAction, GET_RACE_MAP_DATA_CATEGORY, |
|
| 787 | + getRaceMapDataCallback(oldTime, newTime, fromAndToAndOverlap.getC(), competitorsToShow, requestID)); |
|
| 788 | + |
|
| 789 | + // draw the wind into the map, get the combined wind |
|
| 790 | + List<String> windSourceTypeNames = new ArrayList<String>(); |
|
| 791 | + windSourceTypeNames.add(WindSourceType.EXPEDITION.name()); |
|
| 792 | + windSourceTypeNames.add(WindSourceType.COMBINED.name()); |
|
| 793 | + GetWindInfoAction getWindInfoAction = new GetWindInfoAction(sailingService, race, newTime, 1000L, 1, windSourceTypeNames, |
|
| 794 | + /* onlyUpToNewestEvent==false means get us any data we can get by a best effort */ false); |
|
| 795 | + asyncActionsExecutor.execute(getWindInfoAction, GET_WIND_DATA_CATEGORY, new AsyncCallback<WindInfoForRaceDTO>() { |
|
| 796 | + @Override |
|
| 797 | + public void onFailure(Throwable caught) { |
|
| 798 | + errorReporter.reportError("Error obtaining wind information: " + caught.getMessage(), true /*silentMode */); |
|
| 799 | + } |
|
| 800 | + |
|
| 801 | + @Override |
|
| 802 | + public void onSuccess(WindInfoForRaceDTO windInfo) { |
|
| 803 | + List<com.sap.sse.common.Util.Pair<WindSource, WindTrackInfoDTO>> windSourcesToShow = new ArrayList<com.sap.sse.common.Util.Pair<WindSource, WindTrackInfoDTO>>(); |
|
| 804 | + if (windInfo != null) { |
|
| 805 | + lastCombinedWindTrackInfoDTO = windInfo; |
|
| 806 | + showAdvantageLine(competitorsToShow, newTime); |
|
| 807 | + for (WindSource windSource : windInfo.windTrackInfoByWindSource.keySet()) { |
|
| 808 | + WindTrackInfoDTO windTrackInfoDTO = windInfo.windTrackInfoByWindSource.get(windSource); |
|
| 809 | + switch (windSource.getType()) { |
|
| 810 | + case EXPEDITION: |
|
| 811 | + // we filter out measured wind sources with vary low confidence |
|
| 812 | + if (windTrackInfoDTO.minWindConfidence > 0.0001) { |
|
| 813 | + windSourcesToShow.add(new com.sap.sse.common.Util.Pair<WindSource, WindTrackInfoDTO>(windSource, windTrackInfoDTO)); |
|
| 814 | + } |
|
| 815 | + break; |
|
| 816 | + case COMBINED: |
|
| 817 | + showCombinedWindOnMap(windSource, windTrackInfoDTO); |
|
| 818 | + if (requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown) { |
|
| 819 | + updateCoordinateSystemFromSettings(); |
|
| 820 | + } |
|
| 821 | + break; |
|
| 822 | + default: |
|
| 823 | + // Which wind sources are requested is defined in a list above this |
|
| 824 | + // action. So we throw here an exception to notice a missing source. |
|
| 825 | + throw new UnsupportedOperationException( |
|
| 826 | + "There is currently no support for the enum value '" |
|
| 827 | + + windSource.getType() + "' in this method."); |
|
| 828 | + } |
|
| 829 | + } |
|
| 830 | + } |
|
| 831 | + showWindSensorsOnMap(windSourcesToShow); |
|
| 832 | + } |
|
| 833 | + }); |
|
| 834 | + } |
|
| 835 | + } |
|
| 836 | + } |
|
| 837 | + } |
|
| 838 | + |
|
| 839 | + /** |
|
| 840 | + * We assume that overlapping segments usually don't require a lot of loading time as the most typical case will be to update a longer |
|
| 841 | + * tail with a few new fixes that were received since the last time tick. Non-overlapping position requests typically occur for the |
|
| 842 | + * first request when no fix at all is known for the competitor yet, and when the user has radically moved the time slider to some |
|
| 843 | + * other time such that given the current tail length setting the new tail segment does not overlap with the old one, requiring a full |
|
| 844 | + * load of the entire tail data for that competitor.<p> |
|
| 845 | + * |
|
| 846 | + * For the non-overlapping requests, this method creates a separate request which only loads boat positions, quick ranks, sidelines and |
|
| 847 | + * mark positions for the zero-length interval at <code>newTime</code>, assuming that this will work fairly fast and in particular in |
|
| 848 | + * O(1) time regardless of tail length, compared to fetching the entire tail for all competitors. |
|
| 849 | + */ |
|
| 850 | + private GetRaceMapDataAction getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping( |
|
| 851 | + Triple<Map<CompetitorDTO, Date>, Map<CompetitorDTO, Date>, Map<CompetitorDTO, Boolean>> fromAndToAndOverlap, |
|
| 852 | + RegattaAndRaceIdentifier race, Date newTime) { |
|
| 853 | + Map<CompetitorDTO, Date> fromTimes = new HashMap<>(); |
|
| 854 | + Map<CompetitorDTO, Date> toTimes = new HashMap<>(); |
|
| 855 | + for (Map.Entry<CompetitorDTO, Boolean> e : fromAndToAndOverlap.getC().entrySet()) { |
|
| 856 | + if (!e.getValue()) { |
|
| 857 | + // no overlap; add competitor to request |
|
| 858 | + fromTimes.put(e.getKey(), newTime); |
|
| 859 | + toTimes.put(e.getKey(), newTime); |
|
| 860 | + } |
|
| 861 | + } |
|
| 862 | + final GetRaceMapDataAction result; |
|
| 863 | + if (!fromTimes.isEmpty()) { |
|
| 864 | + result = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(), |
|
| 865 | + race, useNullAsTimePoint() ? null : newTime, fromTimes, toTimes, /* extrapolate */true, (settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null)); |
|
| 866 | + } else { |
|
| 867 | + result = null; |
|
| 868 | + } |
|
| 869 | + return result; |
|
| 870 | + } |
|
| 871 | + |
|
| 872 | + private AsyncCallback<RaceMapDataDTO> getRaceMapDataCallback( |
|
| 873 | + final Date oldTime, |
|
| 874 | + final Date newTime, |
|
| 875 | + final Map<CompetitorDTO, Boolean> hasTailOverlapForCompetitor, |
|
| 876 | + final Iterable<CompetitorDTO> competitorsToShow, final int requestID) { |
|
| 877 | + return new AsyncCallback<RaceMapDataDTO>() { |
|
| 878 | + @Override |
|
| 879 | + public void onFailure(Throwable caught) { |
|
| 880 | + errorReporter.reportError("Error obtaining racemap data: " + caught.getMessage(), true /*silentMode */); |
|
| 881 | + } |
|
| 882 | + |
|
| 883 | + @Override |
|
| 884 | + public void onSuccess(RaceMapDataDTO raceMapDataDTO) { |
|
| 885 | + if (map != null && raceMapDataDTO != null) { |
|
| 886 | + quickRanks = raceMapDataDTO.quickRanks; |
|
| 887 | + competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber = |
|
| 888 | + raceMapDataDTO.competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber; |
|
| 889 | + if (showViewSimulation && settings.isShowSimulationOverlay()) { |
|
| 890 | + lastLegNumber = raceMapDataDTO.coursePositions.currentLegNumber; |
|
| 891 | + simulationOverlay.updateLeg(Math.max(lastLegNumber,1), /* clearCanvas */ false, raceMapDataDTO.simulationResultVersion); |
|
| 892 | + } |
|
| 893 | + // process response only if not received out of order |
|
| 894 | + if (startedProcessingRequestID < requestID) { |
|
| 895 | + startedProcessingRequestID = requestID; |
|
| 896 | + // Do boat specific actions |
|
| 897 | + Map<CompetitorDTO, List<GPSFixDTO>> boatData = raceMapDataDTO.boatPositions; |
|
| 898 | + long timeForPositionTransitionMillis = calculateTimeForPositionTransition(newTime, oldTime); |
|
| 899 | + fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, timeForPositionTransitionMillis); |
|
| 900 | + showBoatsOnMap(newTime, timeForPositionTransitionMillis, getCompetitorsToShow()); |
|
| 901 | + showCompetitorInfoOnMap(newTime, timeForPositionTransitionMillis, competitorSelection.getSelectedFilteredCompetitors()); |
|
| 902 | + if (douglasMarkers != null) { |
|
| 903 | + removeAllMarkDouglasPeuckerpoints(); |
|
| 904 | + } |
|
| 905 | + if (maneuverMarkers != null) { |
|
| 906 | + removeAllManeuverMarkers(); |
|
| 907 | + } |
|
| 908 | + |
|
| 909 | + // Do mark specific actions |
|
| 910 | + showCourseMarksOnMap(raceMapDataDTO.coursePositions); |
|
| 911 | + if (requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown) { |
|
| 912 | + updateCoordinateSystemFromSettings(); |
|
| 913 | + } |
|
| 914 | + showCourseSidelinesOnMap(raceMapDataDTO.courseSidelines); |
|
| 915 | + showStartAndFinishAndCourseMiddleLines(raceMapDataDTO.coursePositions); |
|
| 916 | + showStartLineToFirstMarkTriangle(raceMapDataDTO.coursePositions); |
|
| 917 | + // even though the wind data is retrieved by a separate call, re-draw the advantage line because it needs to |
|
| 918 | + // adjust to new boat positions |
|
| 919 | + showAdvantageLine(competitorsToShow, newTime); |
|
| 920 | + |
|
| 921 | + // Rezoom the map |
|
| 922 | + LatLngBounds zoomToBounds = null; |
|
| 923 | + if (!settings.getZoomSettings().containsZoomType(ZoomTypes.NONE)) { // Auto zoom if setting is not manual |
|
| 924 | + zoomToBounds = settings.getZoomSettings().getNewBounds(RaceMap.this); |
|
| 925 | + if (zoomToBounds == null && !mapFirstZoomDone) { |
|
| 926 | + zoomToBounds = getDefaultZoomBounds(); // the user-specified zoom couldn't find what it was looking for; try defaults once |
|
| 927 | + } |
|
| 928 | + } else if (!mapFirstZoomDone) { // Zoom once to the marks if marks exist |
|
| 929 | + zoomToBounds = new CourseMarksBoundsCalculator().calculateNewBounds(RaceMap.this); |
|
| 930 | + if (zoomToBounds == null) { |
|
| 931 | + zoomToBounds = getDefaultZoomBounds(); // use default zoom, e.g., |
|
| 932 | + } |
|
| 933 | + /* |
|
| 934 | + * Reset the mapZoomedOrPannedSinceLastRaceSelection: In spite of the fact that |
|
| 935 | + * the map was just zoomed to the bounds of the marks, it was not a zoom or pan |
|
| 936 | + * triggered by the user. As a consequence the |
|
| 937 | + * mapZoomedOrPannedSinceLastRaceSelection option has to reset again. |
|
| 938 | + */ |
|
| 939 | + } |
|
| 940 | + zoomMapToNewBounds(zoomToBounds); |
|
| 941 | + mapFirstZoomDone = true; |
|
| 942 | + } |
|
| 943 | + } else { |
|
| 944 | + lastTimeChangeBeforeInitialization = newTime; |
|
| 945 | + } |
|
| 946 | + } |
|
| 947 | + }; |
|
| 948 | + } |
|
| 949 | + |
|
| 950 | + private void showCourseSidelinesOnMap(List<SidelineDTO> sidelinesDTOs) { |
|
| 951 | + if (map != null && sidelinesDTOs != null ) { |
|
| 952 | + Map<SidelineDTO, Polygon> toRemoveSidelines = new HashMap<SidelineDTO, Polygon>(courseSidelines); |
|
| 953 | + for (SidelineDTO sidelineDTO : sidelinesDTOs) { |
|
| 954 | + if (sidelineDTO.getMarks().size() == 2) { // right now we only support sidelines with 2 marks |
|
| 955 | + Polygon sideline = courseSidelines.get(sidelineDTO); |
|
| 956 | + LatLng[] sidelinePoints = new LatLng[sidelineDTO.getMarks().size()]; |
|
| 957 | + int i=0; |
|
| 958 | + for (MarkDTO sidelineMark : sidelineDTO.getMarks()) { |
|
| 959 | + sidelinePoints[i] = coordinateSystem.toLatLng(sidelineMark.position); |
|
| 960 | + i++; |
|
| 961 | + } |
|
| 962 | + if (sideline == null) { |
|
| 963 | + PolygonOptions options = PolygonOptions.newInstance(); |
|
| 964 | + options.setClickable(true); |
|
| 965 | + options.setStrokeColor("#0000FF"); |
|
| 966 | + options.setStrokeWeight(1); |
|
| 967 | + options.setStrokeOpacity(1.0); |
|
| 968 | + options.setFillColor(null); |
|
| 969 | + options.setFillOpacity(1.0); |
|
| 970 | + |
|
| 971 | + sideline = Polygon.newInstance(options); |
|
| 972 | + MVCArray<LatLng> pointsAsArray = MVCArray.newInstance(sidelinePoints); |
|
| 973 | + sideline.setPath(pointsAsArray); |
|
| 974 | + |
|
| 975 | + sideline.addMouseOverHandler(new MouseOverMapHandler() { |
|
| 976 | + @Override |
|
| 977 | + public void onEvent(MouseOverMapEvent event) { |
|
| 978 | + map.setTitle(stringMessages.sideline()); |
|
| 979 | + } |
|
| 980 | + }); |
|
| 981 | + sideline.addMouseOutMoveHandler(new MouseOutMapHandler() { |
|
| 982 | + @Override |
|
| 983 | + public void onEvent(MouseOutMapEvent event) { |
|
| 984 | + map.setTitle(""); |
|
| 985 | + } |
|
| 986 | + }); |
|
| 987 | + courseSidelines.put(sidelineDTO, sideline); |
|
| 988 | + sideline.setMap(map); |
|
| 989 | + } else { |
|
| 990 | + sideline.getPath().removeAt(1); |
|
| 991 | + sideline.getPath().removeAt(0); |
|
| 992 | + sideline.getPath().insertAt(0, sidelinePoints[0]); |
|
| 993 | + sideline.getPath().insertAt(1, sidelinePoints[1]); |
|
| 994 | + toRemoveSidelines.remove(sidelineDTO); |
|
| 995 | + } |
|
| 996 | + } |
|
| 997 | + } |
|
| 998 | + for (SidelineDTO toRemoveSideline : toRemoveSidelines.keySet()) { |
|
| 999 | + Polygon sideline = courseSidelines.remove(toRemoveSideline); |
|
| 1000 | + sideline.setMap(null); |
|
| 1001 | + } |
|
| 1002 | + } |
|
| 1003 | + } |
|
| 1004 | + |
|
| 1005 | + private void showCourseMarksOnMap(CoursePositionsDTO courseDTO) { |
|
| 1006 | + if (map != null && courseDTO != null) { |
|
| 1007 | + WaypointDTO endWaypointForCurrentLegNumber = null; |
|
| 1008 | + if(courseDTO.currentLegNumber > 0 && courseDTO.currentLegNumber <= courseDTO.totalLegsCount) { |
|
| 1009 | + endWaypointForCurrentLegNumber = courseDTO.getEndWaypointForLegNumber(courseDTO.currentLegNumber); |
|
| 1010 | + } |
|
| 1011 | + |
|
| 1012 | + Map<String, CourseMarkOverlay> toRemoveCourseMarks = new HashMap<String, CourseMarkOverlay>(courseMarkOverlays); |
|
| 1013 | + if (courseDTO.marks != null) { |
|
| 1014 | + for (MarkDTO markDTO : courseDTO.marks) { |
|
| 1015 | + boolean isSelected = false; |
|
| 1016 | + if (endWaypointForCurrentLegNumber != null && Util.contains(endWaypointForCurrentLegNumber.controlPoint.getMarks(), markDTO)) { |
|
| 1017 | + isSelected = true; |
|
| 1018 | + } |
|
| 1019 | + CourseMarkOverlay courseMarkOverlay = courseMarkOverlays.get(markDTO.getName()); |
|
| 1020 | + if (courseMarkOverlay == null) { |
|
| 1021 | + courseMarkOverlay = createCourseMarkOverlay(RaceMapOverlaysZIndexes.COURSEMARK_ZINDEX, markDTO); |
|
| 1022 | + courseMarkOverlay.setShowBuoyZone(settings.getHelpLinesSettings().isVisible(HelpLineTypes.BUOYZONE)); |
|
| 1023 | + courseMarkOverlay.setBuoyZoneRadiusInMeter(settings.getBuoyZoneRadiusInMeters()); |
|
| 1024 | + courseMarkOverlay.setSelected(isSelected); |
|
| 1025 | + courseMarkOverlays.put(markDTO.getName(), courseMarkOverlay); |
|
| 1026 | + markDTOs.put(markDTO.getName(), markDTO); |
|
| 1027 | + courseMarkOverlay.addToMap(); |
|
| 1028 | + } else { |
|
| 1029 | + courseMarkOverlay.setMarkPosition(markDTO.position); |
|
| 1030 | + courseMarkOverlay.setShowBuoyZone(settings.getHelpLinesSettings().isVisible(HelpLineTypes.BUOYZONE)); |
|
| 1031 | + courseMarkOverlay.setBuoyZoneRadiusInMeter(settings.getBuoyZoneRadiusInMeters()); |
|
| 1032 | + courseMarkOverlay.setSelected(isSelected); |
|
| 1033 | + courseMarkOverlay.draw(); |
|
| 1034 | + toRemoveCourseMarks.remove(markDTO.getName()); |
|
| 1035 | + } |
|
| 1036 | + } |
|
| 1037 | + } |
|
| 1038 | + for (String toRemoveMarkName : toRemoveCourseMarks.keySet()) { |
|
| 1039 | + CourseMarkOverlay removedOverlay = courseMarkOverlays.remove(toRemoveMarkName); |
|
| 1040 | + if(removedOverlay != null) { |
|
| 1041 | + removedOverlay.removeFromMap(); |
|
| 1042 | + } |
|
| 1043 | + } |
|
| 1044 | + } |
|
| 1045 | + } |
|
| 1046 | + |
|
| 1047 | + /** |
|
| 1048 | + * Based on the mark positions in {@link #courseMarkOverlays}' values this method determines the center of gravity of these marks' |
|
| 1049 | + * {@link CourseMarkOverlay#getPosition() positions}. |
|
| 1050 | + */ |
|
| 1051 | + private Position getCenterOfCourse() { |
|
| 1052 | + ScalablePosition center = null; |
|
| 1053 | + int count = 0; |
|
| 1054 | + for (CourseMarkOverlay markOverlay : courseMarkOverlays.values()) { |
|
| 1055 | + ScalablePosition markPosition = new ScalablePosition(markOverlay.getPosition()); |
|
| 1056 | + if (center == null) { |
|
| 1057 | + center = markPosition; |
|
| 1058 | + } else { |
|
| 1059 | + center.add(markPosition); |
|
| 1060 | + } |
|
| 1061 | + count++; |
|
| 1062 | + } |
|
| 1063 | + return center == null ? null : center.divide(count); |
|
| 1064 | + } |
|
| 1065 | + |
|
| 1066 | + private void showCombinedWindOnMap(WindSource windSource, WindTrackInfoDTO windTrackInfoDTO) { |
|
| 1067 | + if (map != null) { |
|
| 1068 | + combinedWindPanel.setWindInfo(windTrackInfoDTO, windSource); |
|
| 1069 | + combinedWindPanel.redraw(); |
|
| 1070 | + } |
|
| 1071 | + } |
|
| 1072 | + |
|
| 1073 | + private void showWindSensorsOnMap(List<com.sap.sse.common.Util.Pair<WindSource, WindTrackInfoDTO>> windSensorsList) { |
|
| 1074 | + if (map != null) { |
|
| 1075 | + Set<WindSource> toRemoveWindSources = new HashSet<WindSource>(windSensorOverlays.keySet()); |
|
| 1076 | + for (com.sap.sse.common.Util.Pair<WindSource, WindTrackInfoDTO> windSourcePair : windSensorsList) { |
|
| 1077 | + WindSource windSource = windSourcePair.getA(); |
|
| 1078 | + WindTrackInfoDTO windTrackInfoDTO = windSourcePair.getB(); |
|
| 1079 | + |
|
| 1080 | + WindSensorOverlay windSensorOverlay = windSensorOverlays.get(windSource); |
|
| 1081 | + if (windSensorOverlay == null) { |
|
| 1082 | + windSensorOverlay = createWindSensorOverlay(RaceMapOverlaysZIndexes.WINDSENSOR_ZINDEX, windSource, windTrackInfoDTO); |
|
| 1083 | + windSensorOverlays.put(windSource, windSensorOverlay); |
|
| 1084 | + windSensorOverlay.addToMap(); |
|
| 1085 | + } else { |
|
| 1086 | + windSensorOverlay.setWindInfo(windTrackInfoDTO, windSource); |
|
| 1087 | + windSensorOverlay.draw(); |
|
| 1088 | + toRemoveWindSources.remove(windSource); |
|
| 1089 | + } |
|
| 1090 | + } |
|
| 1091 | + for (WindSource toRemoveWindSource : toRemoveWindSources) { |
|
| 1092 | + WindSensorOverlay removedWindSensorOverlay = windSensorOverlays.remove(toRemoveWindSource); |
|
| 1093 | + if (removedWindSensorOverlay != null) { |
|
| 1094 | + removedWindSensorOverlay.removeFromMap(); |
|
| 1095 | + } |
|
| 1096 | + } |
|
| 1097 | + } |
|
| 1098 | + } |
|
| 1099 | + |
|
| 1100 | + private void showCompetitorInfoOnMap(final Date newTime, final long timeForPositionTransitionMillis, final Iterable<CompetitorDTO> competitorsToShow) { |
|
| 1101 | + if (map != null) { |
|
| 1102 | + if (settings.isShowSelectedCompetitorsInfo()) { |
|
| 1103 | + Set<CompetitorDTO> toRemoveCompetorInfoOverlays = new HashSet<CompetitorDTO>( |
|
| 1104 | + competitorInfoOverlays.keySet()); |
|
| 1105 | + for (CompetitorDTO competitorDTO : competitorsToShow) { |
|
| 1106 | + if (fixesAndTails.hasFixesFor(competitorDTO)) { |
|
| 1107 | + GPSFixDTO lastBoatFix = getBoatFix(competitorDTO, newTime); |
|
| 1108 | + if (lastBoatFix != null) { |
|
| 1109 | + CompetitorInfoOverlay competitorInfoOverlay = competitorInfoOverlays.get(competitorDTO); |
|
| 1110 | + if (competitorInfoOverlay == null) { |
|
| 1111 | + competitorInfoOverlay = createCompetitorInfoOverlay(RaceMapOverlaysZIndexes.INFO_OVERLAY_ZINDEX, competitorDTO); |
|
| 1112 | + competitorInfoOverlays.put(competitorDTO, competitorInfoOverlay); |
|
| 1113 | + competitorInfoOverlay.setPosition(lastBoatFix.position, timeForPositionTransitionMillis); |
|
| 1114 | + competitorInfoOverlay.addToMap(); |
|
| 1115 | + } else { |
|
| 1116 | + competitorInfoOverlay.setPosition(lastBoatFix.position, timeForPositionTransitionMillis); |
|
| 1117 | + competitorInfoOverlay.draw(); |
|
| 1118 | + } |
|
| 1119 | + toRemoveCompetorInfoOverlays.remove(competitorDTO); |
|
| 1120 | + } |
|
| 1121 | + } |
|
| 1122 | + } |
|
| 1123 | + for (CompetitorDTO toRemoveCompetorDTO : toRemoveCompetorInfoOverlays) { |
|
| 1124 | + CompetitorInfoOverlay competitorInfoOverlay = competitorInfoOverlays.get(toRemoveCompetorDTO); |
|
| 1125 | + competitorInfoOverlay.removeFromMap(); |
|
| 1126 | + competitorInfoOverlays.remove(toRemoveCompetorDTO); |
|
| 1127 | + } |
|
| 1128 | + } else { |
|
| 1129 | + // remove all overlays |
|
| 1130 | + for (CompetitorInfoOverlay competitorInfoOverlay : competitorInfoOverlays.values()) { |
|
| 1131 | + competitorInfoOverlay.removeFromMap(); |
|
| 1132 | + } |
|
| 1133 | + competitorInfoOverlays.clear(); |
|
| 1134 | + } |
|
| 1135 | + } |
|
| 1136 | + } |
|
| 1137 | + |
|
| 1138 | + private long calculateTimeForPositionTransition(final Date newTime, final Date oldTime) { |
|
| 1139 | + final long timeForPositionTransitionMillis; |
|
| 1140 | + boolean hasTimeJumped = oldTime != null && Math.abs(oldTime.getTime() - newTime.getTime()) > 3*timer.getRefreshInterval(); |
|
| 1141 | + if (timer.getPlayState() == PlayStates.Playing && !hasTimeJumped) { |
|
| 1142 | + // choose 130% of the refresh interval as transition period to make it unlikely that the transition |
|
| 1143 | + // stops before the next update has been received |
|
| 1144 | + timeForPositionTransitionMillis = 1300 * timer.getRefreshInterval() / 1000; |
|
| 1145 | + } else { |
|
| 1146 | + timeForPositionTransitionMillis = -1; // -1 means 'no transition |
|
| 1147 | + } |
|
| 1148 | + return timeForPositionTransitionMillis; |
|
| 1149 | + } |
|
| 1150 | + |
|
| 1151 | + private void showBoatsOnMap(final Date newTime, final long timeForPositionTransitionMillis, final Iterable<CompetitorDTO> competitorsToShow) { |
|
| 1152 | + if (map != null) { |
|
| 1153 | + Date tailsFromTime = new Date(newTime.getTime() - settings.getEffectiveTailLengthInMilliseconds()); |
|
| 1154 | + Date tailsToTime = newTime; |
|
| 1155 | + Set<CompetitorDTO> competitorDTOsOfUnusedTails = new HashSet<CompetitorDTO>(fixesAndTails.getCompetitorsWithTails()); |
|
| 1156 | + Set<CompetitorDTO> competitorDTOsOfUnusedBoatCanvases = new HashSet<CompetitorDTO>(boatOverlays.keySet()); |
|
| 1157 | + for (CompetitorDTO competitorDTO : competitorsToShow) { |
|
| 1158 | + if (fixesAndTails.hasFixesFor(competitorDTO)) { |
|
| 1159 | + Polyline tail = fixesAndTails.getTail(competitorDTO); |
|
| 1160 | + if (tail == null) { |
|
| 1161 | + tail = fixesAndTails.createTailAndUpdateIndices(competitorDTO, tailsFromTime, tailsToTime, this); |
|
| 1162 | + } else { |
|
| 1163 | + fixesAndTails.updateTail(tail, competitorDTO, tailsFromTime, tailsToTime, |
|
| 1164 | + (int) (timeForPositionTransitionMillis==-1?-1:timeForPositionTransitionMillis/2)); |
|
| 1165 | + competitorDTOsOfUnusedTails.remove(competitorDTO); |
|
| 1166 | + PolylineOptions newOptions = createTailStyle(competitorDTO, displayHighlighted(competitorDTO)); |
|
| 1167 | + tail.setOptions(newOptions); |
|
| 1168 | + } |
|
| 1169 | + boolean usedExistingBoatCanvas = updateBoatCanvasForCompetitor(competitorDTO, newTime, timeForPositionTransitionMillis); |
|
| 1170 | + if (usedExistingBoatCanvas) { |
|
| 1171 | + competitorDTOsOfUnusedBoatCanvases.remove(competitorDTO); |
|
| 1172 | + } |
|
| 1173 | + } |
|
| 1174 | + } |
|
| 1175 | + for (CompetitorDTO unusedBoatCanvasCompetitorDTO : competitorDTOsOfUnusedBoatCanvases) { |
|
| 1176 | + BoatOverlay boatCanvas = boatOverlays.get(unusedBoatCanvasCompetitorDTO); |
|
| 1177 | + boatCanvas.removeFromMap(); |
|
| 1178 | + boatOverlays.remove(unusedBoatCanvasCompetitorDTO); |
|
| 1179 | + } |
|
| 1180 | + for (CompetitorDTO unusedTailCompetitorDTO : competitorDTOsOfUnusedTails) { |
|
| 1181 | + fixesAndTails.removeTail(unusedTailCompetitorDTO); |
|
| 1182 | + } |
|
| 1183 | + } |
|
| 1184 | + } |
|
| 1185 | + |
|
| 1186 | + /** |
|
| 1187 | + * This algorithm is limited to distances such that dlon < pi/2, i.e., those that extend around less than one |
|
| 1188 | + * quarter of the circumference of the earth in longitude. A completely general, but more complicated algorithm is |
|
| 1189 | + * necessary if greater distances are allowed. |
|
| 1190 | + */ |
|
| 1191 | + public LatLng calculatePositionAlongRhumbline(LatLng position, double bearingDeg, double distanceInKm) { |
|
| 1192 | + double distianceRad = distanceInKm / 6371.0; // r = 6371 means earth's radius in km |
|
| 1193 | + double lat1 = position.getLatitude() / 180. * Math.PI; |
|
| 1194 | + double lon1 = position.getLongitude() / 180. * Math.PI; |
|
| 1195 | + double bearingRad = bearingDeg / 180. * Math.PI; |
|
| 1196 | + double lat2 = Math.asin(Math.sin(lat1) * Math.cos(distianceRad) + |
|
| 1197 | + Math.cos(lat1) * Math.sin(distianceRad) * Math.cos(bearingRad)); |
|
| 1198 | + double lon2 = lon1 + Math.atan2(Math.sin(bearingRad)*Math.sin(distianceRad)*Math.cos(lat1), |
|
| 1199 | + Math.cos(distianceRad)-Math.sin(lat1)*Math.sin(lat2)); |
|
| 1200 | + lon2 = (lon2+3*Math.PI) % (2*Math.PI) - Math.PI; // normalize to -180..+180� |
|
| 1201 | + // position is already in LatLng space, so no mapping through coordinateSystem is required here |
|
| 1202 | + return LatLng.newInstance(lat2 / Math.PI * 180., lon2 / Math.PI * 180.); |
|
| 1203 | + } |
|
| 1204 | + |
|
| 1205 | + /** |
|
| 1206 | + * Returns a pair whose first component is the leg number (one-based) of the competitor returned as the second component. |
|
| 1207 | + */ |
|
| 1208 | + private com.sap.sse.common.Util.Pair<Integer, CompetitorDTO> getFarthestAheadVisibleCompetitorWithOneBasedLegNumber( |
|
| 1209 | + Iterable<CompetitorDTO> competitorsToShow) { |
|
| 1210 | + CompetitorDTO leadingCompetitorDTO = null; |
|
| 1211 | + int legOfLeaderCompetitor = -1; |
|
| 1212 | + // this only works because the quickRanks are sorted |
|
| 1213 | + for (Entry<CompetitorDTO, Integer> competitorsByWindwardDistanceTraveledAndOneBasedLegNumber : |
|
| 1214 | + competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber.entrySet()) { |
|
| 1215 | + if (Util.contains(competitorsToShow, competitorsByWindwardDistanceTraveledAndOneBasedLegNumber.getKey()) && |
|
| 1216 | + competitorsByWindwardDistanceTraveledAndOneBasedLegNumber.getValue() != null) { |
|
| 1217 | + leadingCompetitorDTO = competitorsByWindwardDistanceTraveledAndOneBasedLegNumber.getKey(); |
|
| 1218 | + legOfLeaderCompetitor = competitorsByWindwardDistanceTraveledAndOneBasedLegNumber.getValue(); |
|
| 1219 | + return new com.sap.sse.common.Util.Pair<Integer, CompetitorDTO>(legOfLeaderCompetitor, leadingCompetitorDTO); |
|
| 1220 | + } |
|
| 1221 | + } |
|
| 1222 | + return null; |
|
| 1223 | + } |
|
| 1224 | + |
|
| 1225 | + private void showAdvantageLine(Iterable<CompetitorDTO> competitorsToShow, Date date) { |
|
| 1226 | + if (map != null && lastRaceTimesInfo != null && quickRanks != null && lastCombinedWindTrackInfoDTO != null) { |
|
| 1227 | + boolean drawAdvantageLine = false; |
|
| 1228 | + if (settings.getHelpLinesSettings().isVisible(HelpLineTypes.ADVANTAGELINE)) { |
|
| 1229 | + // find competitor with highest rank |
|
| 1230 | + com.sap.sse.common.Util.Pair<Integer, CompetitorDTO> visibleLeaderInfo = getFarthestAheadVisibleCompetitorWithOneBasedLegNumber(competitorsToShow); |
|
| 1231 | + // the boat fix may be null; may mean that no positions were loaded yet for the leading visible boat; |
|
| 1232 | + // don't show anything |
|
| 1233 | + GPSFixDTO lastBoatFix = null; |
|
| 1234 | + boolean isVisibleLeaderInfoComplete = false; |
|
| 1235 | + boolean isLegTypeKnown = false; |
|
| 1236 | + WindTrackInfoDTO windDataForLegMiddle = null; |
|
| 1237 | + LegInfoDTO legInfoDTO = null; |
|
| 1238 | + if (visibleLeaderInfo != null |
|
| 1239 | + && visibleLeaderInfo.getA() > 0 |
|
| 1240 | + && visibleLeaderInfo.getA() <= lastRaceTimesInfo.getLegInfos().size() |
|
| 1241 | + // get wind at middle of leg for leading visible competitor |
|
| 1242 | + && (windDataForLegMiddle = lastCombinedWindTrackInfoDTO |
|
| 1243 | + .getCombinedWindOnLegMiddle(visibleLeaderInfo.getA() - 1)) != null |
|
| 1244 | + && !windDataForLegMiddle.windFixes.isEmpty()) { |
|
| 1245 | + isVisibleLeaderInfoComplete = true; |
|
| 1246 | + legInfoDTO = lastRaceTimesInfo.getLegInfos().get(visibleLeaderInfo.getA() - 1); |
|
| 1247 | + if (legInfoDTO.legType != null) { |
|
| 1248 | + isLegTypeKnown = true; |
|
| 1249 | + } |
|
| 1250 | + lastBoatFix = getBoatFix(visibleLeaderInfo.getB(), date); |
|
| 1251 | + } |
|
| 1252 | + if (isVisibleLeaderInfoComplete && isLegTypeKnown && lastBoatFix != null && lastBoatFix.speedWithBearing != null) { |
|
| 1253 | + double advantageLineLengthInKm = 1.0; // TODO this should probably rather scale with the visible |
|
| 1254 | + // area of the map; bug 616 |
|
| 1255 | + double distanceFromBoatPositionInKm = visibleLeaderInfo.getB().getBoatClass() |
|
| 1256 | + .getHullLengthInMeters() / 1000.; // one hull length |
|
| 1257 | + // implement and use Position.translateRhumb() |
|
| 1258 | + double bearingOfBoatInDeg = lastBoatFix.speedWithBearing.bearingInDegrees; |
|
| 1259 | + LatLng boatPosition = coordinateSystem.toLatLng(lastBoatFix.position); |
|
| 1260 | + LatLng posAheadOfFirstBoat = calculatePositionAlongRhumbline(boatPosition, |
|
| 1261 | + coordinateSystem.mapDegreeBearing(bearingOfBoatInDeg), distanceFromBoatPositionInKm); |
|
| 1262 | + final WindDTO windFix = windDataForLegMiddle.windFixes.get(0); |
|
| 1263 | + double bearingOfCombinedWindInDeg = windFix.trueWindBearingDeg; |
|
| 1264 | + double rotatedBearingDeg1 = 0.0; |
|
| 1265 | + double rotatedBearingDeg2 = 0.0; |
|
| 1266 | + switch (legInfoDTO.legType) { |
|
| 1267 | + case UPWIND: |
|
| 1268 | + case DOWNWIND: { |
|
| 1269 | + rotatedBearingDeg1 = bearingOfCombinedWindInDeg + 90.0; |
|
| 1270 | + if (rotatedBearingDeg1 >= 360.0) { |
|
| 1271 | + rotatedBearingDeg1 -= 360.0; |
|
| 1272 | + } |
|
| 1273 | + rotatedBearingDeg2 = bearingOfCombinedWindInDeg - 90.0; |
|
| 1274 | + if (rotatedBearingDeg2 < 0.0) { |
|
| 1275 | + rotatedBearingDeg2 += 360.0; |
|
| 1276 | + } |
|
| 1277 | + } |
|
| 1278 | + break; |
|
| 1279 | + case REACHING: { |
|
| 1280 | + rotatedBearingDeg1 = legInfoDTO.legBearingInDegrees + 90.0; |
|
| 1281 | + if (rotatedBearingDeg1 >= 360.0) { |
|
| 1282 | + rotatedBearingDeg1 -= 360.0; |
|
| 1283 | + } |
|
| 1284 | + rotatedBearingDeg2 = legInfoDTO.legBearingInDegrees - 90.0; |
|
| 1285 | + if (rotatedBearingDeg2 < 0.0) { |
|
| 1286 | + rotatedBearingDeg2 += 360.0; |
|
| 1287 | + } |
|
| 1288 | + } |
|
| 1289 | + break; |
|
| 1290 | + } |
|
| 1291 | + LatLng advantageLinePos1 = calculatePositionAlongRhumbline(posAheadOfFirstBoat, |
|
| 1292 | + coordinateSystem.mapDegreeBearing(rotatedBearingDeg1), advantageLineLengthInKm / 2.0); |
|
| 1293 | + LatLng advantageLinePos2 = calculatePositionAlongRhumbline(posAheadOfFirstBoat, |
|
| 1294 | + coordinateSystem.mapDegreeBearing(rotatedBearingDeg2), advantageLineLengthInKm / 2.0); |
|
| 1295 | + if (advantageLine == null) { |
|
| 1296 | + PolylineOptions options = PolylineOptions.newInstance(); |
|
| 1297 | + options.setClickable(true); |
|
| 1298 | + options.setGeodesic(true); |
|
| 1299 | + options.setStrokeColor("#000000"); |
|
| 1300 | + options.setStrokeWeight(1); |
|
| 1301 | + options.setStrokeOpacity(0.5); |
|
| 1302 | + |
|
| 1303 | + advantageLine = Polyline.newInstance(options); |
|
| 1304 | + MVCArray<LatLng> pointsAsArray = MVCArray.newInstance(); |
|
| 1305 | + pointsAsArray.insertAt(0, advantageLinePos1); |
|
| 1306 | + pointsAsArray.insertAt(1, advantageLinePos2); |
|
| 1307 | + advantageLine.setPath(pointsAsArray); |
|
| 1308 | + advantageLine.setMap(map); |
|
| 1309 | + Hoverline advantageHoverline = new Hoverline(advantageLine, options, this); |
|
| 1310 | + |
|
| 1311 | + advantageLineMouseOverHandler = new AdvantageLineMouseOverMapHandler( |
|
| 1312 | + bearingOfCombinedWindInDeg, new Date(windFix.measureTimepoint)); |
|
| 1313 | + advantageLine.addMouseOverHandler(advantageLineMouseOverHandler); |
|
| 1314 | + advantageHoverline.addMouseOutMoveHandler(new MouseOutMapHandler() { |
|
| 1315 | + @Override |
|
| 1316 | + public void onEvent(MouseOutMapEvent event) { |
|
| 1317 | + map.setTitle(""); |
|
| 1318 | + } |
|
| 1319 | + }); |
|
| 1320 | + } else { |
|
| 1321 | + advantageLine.getPath().removeAt(1); |
|
| 1322 | + advantageLine.getPath().removeAt(0); |
|
| 1323 | + advantageLine.getPath().insertAt(0, advantageLinePos1); |
|
| 1324 | + advantageLine.getPath().insertAt(1, advantageLinePos2); |
|
| 1325 | + advantageLineMouseOverHandler.setTrueWindBearing(bearingOfCombinedWindInDeg); |
|
| 1326 | + advantageLineMouseOverHandler.setDate(new Date(windFix.measureTimepoint)); |
|
| 1327 | + } |
|
| 1328 | + drawAdvantageLine = true; |
|
| 1329 | + } |
|
| 1330 | + } |
|
| 1331 | + if (!drawAdvantageLine) { |
|
| 1332 | + if (advantageLine != null) { |
|
| 1333 | + advantageLine.setMap(null); |
|
| 1334 | + advantageLine = null; |
|
| 1335 | + } |
|
| 1336 | + } |
|
| 1337 | + } |
|
| 1338 | + } |
|
| 1339 | + |
|
| 1340 | + private final StringBuilder windwardStartLineMarkToFirstMarkLineText = new StringBuilder(); |
|
| 1341 | + private final StringBuilder leewardStartLineMarkToFirstMarkLineText = new StringBuilder(); |
|
| 1342 | + |
|
| 1343 | + private void showStartLineToFirstMarkTriangle(final CoursePositionsDTO courseDTO){ |
|
| 1344 | + final List<Position> startMarkPositions = courseDTO.getStartMarkPositions(); |
|
| 1345 | + final Position windwardStartLinePosition = startMarkPositions.get(0); |
|
| 1346 | + final Position leewardStartLinePosition = startMarkPositions.get(1); |
|
| 1347 | + final Position firstMarkPosition = courseDTO.waypointPositions.get(1); |
|
| 1348 | + windwardStartLineMarkToFirstMarkLineText.replace(0, windwardStartLineMarkToFirstMarkLineText.length(), |
|
| 1349 | + stringMessages.startLineToFirstMarkTriangle(numberFormatOneDecimal |
|
| 1350 | + .format(windwardStartLinePosition.getDistance(firstMarkPosition) |
|
| 1351 | + .getMeters()))); |
|
| 1352 | + leewardStartLineMarkToFirstMarkLineText.replace(0, leewardStartLineMarkToFirstMarkLineText.length(), |
|
| 1353 | + stringMessages.startLineToFirstMarkTriangle(numberFormatOneDecimal |
|
| 1354 | + .format(leewardStartLinePosition.getDistance(firstMarkPosition) |
|
| 1355 | + .getMeters()))); |
|
| 1356 | + final LineInfoProvider windwardStartLineMarkToFirstMarkLineInfoProvider = new LineInfoProvider() { |
|
| 1357 | + @Override |
|
| 1358 | + public String getLineInfo() { |
|
| 1359 | + return windwardStartLineMarkToFirstMarkLineText.toString(); |
|
| 1360 | + } |
|
| 1361 | + }; |
|
| 1362 | + final LineInfoProvider leewardStartLineMarkToFirstMarkLineInfoProvider = new LineInfoProvider() { |
|
| 1363 | + @Override |
|
| 1364 | + public String getLineInfo() { |
|
| 1365 | + return leewardStartLineMarkToFirstMarkLineText.toString(); |
|
| 1366 | + } |
|
| 1367 | + }; |
|
| 1368 | + windwardStartLineMarkToFirstMarkLine = showOrRemoveOrUpdateLine(windwardStartLineMarkToFirstMarkLine, /* showLine */ |
|
| 1369 | + (settings.getHelpLinesSettings().isVisible(HelpLineTypes.STARTLINETOFIRSTMARKTRIANGLE)) |
|
| 1370 | + && startMarkPositions.size() > 1 && courseDTO.waypointPositions.size() > 1, |
|
| 1371 | + windwardStartLinePosition, firstMarkPosition, windwardStartLineMarkToFirstMarkLineInfoProvider, |
|
| 1372 | + "grey"); |
|
| 1373 | + leewardStartLineMarkToFirstMarkLine = showOrRemoveOrUpdateLine(leewardStartLineMarkToFirstMarkLine, /* showLine */ |
|
| 1374 | + (settings.getHelpLinesSettings().isVisible(HelpLineTypes.STARTLINETOFIRSTMARKTRIANGLE)) |
|
| 1375 | + && startMarkPositions.size() > 1 && courseDTO.waypointPositions.size() > 1, |
|
| 1376 | + leewardStartLinePosition, firstMarkPosition, leewardStartLineMarkToFirstMarkLineInfoProvider, |
|
| 1377 | + "grey"); |
|
| 1378 | + } |
|
| 1379 | + |
|
| 1380 | + private final StringBuilder startLineAdvantageText = new StringBuilder(); |
|
| 1381 | + private final StringBuilder finishLineAdvantageText = new StringBuilder(); |
|
| 1382 | + final LineInfoProvider startLineInfoProvider = new LineInfoProvider() { |
|
| 1383 | + @Override |
|
| 1384 | + public String getLineInfo() { |
|
| 1385 | + return stringMessages.startLine()+startLineAdvantageText; |
|
| 1386 | + } |
|
| 1387 | + }; |
|
| 1388 | + final LineInfoProvider finishLineInfoProvider = new LineInfoProvider() { |
|
| 1389 | + @Override |
|
| 1390 | + public String getLineInfo() { |
|
| 1391 | + return stringMessages.finishLine()+finishLineAdvantageText; |
|
| 1392 | + } |
|
| 1393 | + }; |
|
| 1394 | + |
|
| 1395 | + private void showStartAndFinishAndCourseMiddleLines(final CoursePositionsDTO courseDTO) { |
|
| 1396 | + if (map != null && courseDTO != null && courseDTO.course != null && courseDTO.course.waypoints != null && |
|
| 1397 | + !courseDTO.course.waypoints.isEmpty()) { |
|
| 1398 | + // draw the start line |
|
| 1399 | + final WaypointDTO startWaypoint = courseDTO.course.waypoints.get(0); |
|
| 1400 | + updateCountdownCanvas(startWaypoint); |
|
| 1401 | + final int numberOfStartWaypointMarks = courseDTO.getStartMarkPositions() == null ? 0 : courseDTO.getStartMarkPositions().size(); |
|
| 1402 | + final int numberOfFinishWaypointMarks = courseDTO.getFinishMarkPositions() == null ? 0 : courseDTO.getFinishMarkPositions().size(); |
|
| 1403 | + final Position startLineLeftPosition = numberOfStartWaypointMarks == 0 ? null : courseDTO.getStartMarkPositions().get(0); |
|
| 1404 | + final Position startLineRightPosition = numberOfStartWaypointMarks < 2 ? null : courseDTO.getStartMarkPositions().get(1); |
|
| 1405 | + if (courseDTO.startLineAngleToCombinedWind != null) { |
|
| 1406 | + startLineAdvantageText.replace(0, startLineAdvantageText.length(), " "+stringMessages.lineAngleToWindAndAdvantage( |
|
| 1407 | + NumberFormat.getFormat("0.0").format(courseDTO.startLineLengthInMeters), |
|
| 1408 | + NumberFormat.getFormat("0.0").format(Math.abs(courseDTO.startLineAngleToCombinedWind)), |
|
| 1409 | + courseDTO.startLineAdvantageousSide.name().charAt(0)+courseDTO.startLineAdvantageousSide.name().substring(1).toLowerCase(), |
|
| 1410 | + NumberFormat.getFormat("0.0").format(courseDTO.startLineAdvantageInMeters))); |
|
| 1411 | + } else { |
|
| 1412 | + startLineAdvantageText.delete(0, startLineAdvantageText.length()); |
|
| 1413 | + } |
|
| 1414 | + final boolean showStartLineBasedOnCurrentLeg = numberOfStartWaypointMarks == 2 && courseDTO.currentLegNumber <= 1; |
|
| 1415 | + final boolean showFinishLineBasedOnCurrentLeg = numberOfFinishWaypointMarks == 2 && courseDTO.currentLegNumber == courseDTO.totalLegsCount; |
|
| 1416 | + // show the line when STARTLINE is selected and the current leg is around the start leg, |
|
| 1417 | + // or when COURSEGEOMETRY is selected and the finish line isn't equal and wouldn't be shown at the same time based on the current leg. |
|
| 1418 | + // With this, if COURSEGEOMETRY is selected and start and finish line are equal, the start line will not be displayed if |
|
| 1419 | + // based on the race progress the finish line is to be preferred, so only the finish line will be shown. |
|
| 1420 | + final boolean reallyShowStartLine = |
|
| 1421 | + (settings.getHelpLinesSettings().isVisible(HelpLineTypes.STARTLINE) && showStartLineBasedOnCurrentLeg) || |
|
| 1422 | + (settings.getHelpLinesSettings().isVisible(HelpLineTypes.COURSEGEOMETRY) && |
|
| 1423 | + (!showFinishLineBasedOnCurrentLeg || !startLineEqualsFinishLine(courseDTO))); |
|
| 1424 | + // show the line when FINISHLINE is selected and the current leg is the last leg, |
|
| 1425 | + // or when COURSEGEOMETRY is selected and the start line isn't equal or the current leg is the last leg. |
|
| 1426 | + // With this, if COURSEGEOMETRY is selected and start and finish line are equal, the start line will be displayed unless |
|
| 1427 | + // the finish line should take precedence based on race progress. |
|
| 1428 | + final boolean reallyShowFinishLine = showFinishLineBasedOnCurrentLeg && |
|
| 1429 | + (!showStartLineBasedOnCurrentLeg || !startLineEqualsFinishLine(courseDTO)) && |
|
| 1430 | + (settings.getHelpLinesSettings().isVisible(HelpLineTypes.FINISHLINE) && showFinishLineBasedOnCurrentLeg) || |
|
| 1431 | + (settings.getHelpLinesSettings().isVisible(HelpLineTypes.COURSEGEOMETRY) && |
|
| 1432 | + (!startLineEqualsFinishLine(courseDTO) || showFinishLineBasedOnCurrentLeg)); |
|
| 1433 | + startLine = showOrRemoveOrUpdateLine(startLine, reallyShowStartLine, |
|
| 1434 | + startLineLeftPosition, startLineRightPosition, startLineInfoProvider, "#ffffff"); |
|
| 1435 | + // draw the finish line |
|
| 1436 | + final Position finishLineLeftPosition = numberOfFinishWaypointMarks == 0 ? null : courseDTO.getFinishMarkPositions().get(0); |
|
| 1437 | + final Position finishLineRightPosition = numberOfFinishWaypointMarks < 2 ? null : courseDTO.getFinishMarkPositions().get(1); |
|
| 1438 | + if (courseDTO.finishLineAngleToCombinedWind != null) { |
|
| 1439 | + finishLineAdvantageText.replace(0, finishLineAdvantageText.length(), " "+stringMessages.lineAngleToWindAndAdvantage( |
|
| 1440 | + NumberFormat.getFormat("0.0").format(courseDTO.finishLineLengthInMeters), |
|
| 1441 | + NumberFormat.getFormat("0.0").format(Math.abs(courseDTO.finishLineAngleToCombinedWind)), |
|
| 1442 | + courseDTO.finishLineAdvantageousSide.name().charAt(0)+courseDTO.finishLineAdvantageousSide.name().substring(1).toLowerCase(), |
|
| 1443 | + NumberFormat.getFormat("0.0").format(courseDTO.finishLineAdvantageInMeters))); |
|
| 1444 | + } else { |
|
| 1445 | + finishLineAdvantageText.delete(0, finishLineAdvantageText.length()); |
|
| 1446 | + } |
|
| 1447 | + finishLine = showOrRemoveOrUpdateLine(finishLine, reallyShowFinishLine, |
|
| 1448 | + finishLineLeftPosition, finishLineRightPosition, finishLineInfoProvider, "#000000"); |
|
| 1449 | + // the control point pairs for which we already decided whether or not |
|
| 1450 | + // to show a course middle line for; values tell whether to show the line and for which zero-based |
|
| 1451 | + // start waypoint index to do so; when for an equal control point pair multiple decisions with different |
|
| 1452 | + // outcome are made, a decision to show the line overrules the decision to not show it (OR-semantics) |
|
| 1453 | + final Map<Set<ControlPointDTO>, Pair<Boolean, Integer>> keysAlreadyHandled = new HashMap<>(); |
|
| 1454 | + for (int zeroBasedIndexOfStartWaypoint = 0; zeroBasedIndexOfStartWaypoint<courseDTO.waypointPositions.size()-1; zeroBasedIndexOfStartWaypoint++) { |
|
| 1455 | + final Set<ControlPointDTO> key = getCourseMiddleLinesKey(courseDTO, zeroBasedIndexOfStartWaypoint); |
|
| 1456 | + boolean showCourseMiddleLine = keysAlreadyHandled.containsKey(key) && keysAlreadyHandled.get(key).getA() || |
|
| 1457 | + settings.getHelpLinesSettings().isVisible(HelpLineTypes.COURSEGEOMETRY) || |
|
| 1458 | + (settings.getHelpLinesSettings().isVisible(HelpLineTypes.COURSEMIDDLELINE) |
|
| 1459 | + && courseDTO.currentLegNumber > 0 |
|
| 1460 | + && courseDTO.currentLegNumber-1 == zeroBasedIndexOfStartWaypoint); |
|
| 1461 | + keysAlreadyHandled.put(key, new Pair<>(showCourseMiddleLine, zeroBasedIndexOfStartWaypoint)); |
|
| 1462 | + } |
|
| 1463 | + Set<Set<ControlPointDTO>> keysToConsider = new HashSet<>(keysAlreadyHandled.keySet()); |
|
| 1464 | + keysToConsider.addAll(courseMiddleLines.keySet()); |
|
| 1465 | + for (final Set<ControlPointDTO> key : keysToConsider) { |
|
| 1466 | + final int zeroBasedIndexOfStartWaypoint = keysAlreadyHandled.containsKey(key) ? |
|
| 1467 | + keysAlreadyHandled.get(key).getB() : 0; // if not handled, the line will be removed, so the waypoint index doesn't matter |
|
| 1468 | + final Pair<Boolean, Integer> showLineAndZeroBasedIndexOfStartWaypoint = keysAlreadyHandled.get(key); |
|
| 1469 | + final boolean showCourseMiddleLine = showLineAndZeroBasedIndexOfStartWaypoint != null && showLineAndZeroBasedIndexOfStartWaypoint.getA(); |
|
| 1470 | + courseMiddleLines.put(key, showOrRemoveCourseMiddleLine(courseDTO, courseMiddleLines.get(key), zeroBasedIndexOfStartWaypoint, showCourseMiddleLine)); |
|
| 1471 | + } |
|
| 1472 | + } |
|
| 1473 | + } |
|
| 1474 | + |
|
| 1475 | + private boolean startLineEqualsFinishLine(CoursePositionsDTO courseDTO) { |
|
| 1476 | + final List<WaypointDTO> waypoints; |
|
| 1477 | + return courseDTO != null && courseDTO.course != null && |
|
| 1478 | + (waypoints = courseDTO.course.waypoints) != null && |
|
| 1479 | + waypoints.get(0).controlPoint.equals(waypoints.get(waypoints.size()-1).controlPoint); |
|
| 1480 | + } |
|
| 1481 | + |
|
| 1482 | + /** |
|
| 1483 | + * Given a zero-based index into <code>courseDTO</code>'s {@link RaceCourseDTO#waypoints waypoints list} that denotes the start |
|
| 1484 | + * waypoint of the leg in question, returns a key that can be used for the {@link #courseMiddleLines} map, consisting of a set |
|
| 1485 | + * that holds the two {@link ControlPointDTO}s representing the start and finish control point of that leg. |
|
| 1486 | + */ |
|
| 1487 | + private Set<ControlPointDTO> getCourseMiddleLinesKey(final CoursePositionsDTO courseDTO, |
|
| 1488 | + final int zeroBasedIndexOfStartWaypoint) { |
|
| 1489 | + ControlPointDTO startControlPoint = courseDTO.course.waypoints.get(zeroBasedIndexOfStartWaypoint).controlPoint; |
|
| 1490 | + ControlPointDTO endControlPoint = courseDTO.course.waypoints.get(zeroBasedIndexOfStartWaypoint+1).controlPoint; |
|
| 1491 | + final Set<ControlPointDTO> key = new HashSet<>(); |
|
| 1492 | + key.add(startControlPoint); |
|
| 1493 | + key.add(endControlPoint); |
|
| 1494 | + return key; |
|
| 1495 | + } |
|
| 1496 | + |
|
| 1497 | + /** |
|
| 1498 | + * @param showLine |
|
| 1499 | + * tells whether or not to show the line; if the <code>lineToShowOrRemoveOrUpdate</code> references a |
|
| 1500 | + * line but the line shall not be shown, the line is removed from the map; conversely, if the line is not |
|
| 1501 | + * yet shown but shall be, a new line is created, added to the map and returned. If the line is shown and |
|
| 1502 | + * shall continue to be shown, the line is returned after updating its vertex coordinates. |
|
| 1503 | + * @return <code>null</code> if the line is not shown; the polyline object representing the line being displayed |
|
| 1504 | + * otherwise |
|
| 1505 | + */ |
|
| 1506 | + private Polyline showOrRemoveCourseMiddleLine(final CoursePositionsDTO courseDTO, Polyline lineToShowOrRemoveOrUpdate, |
|
| 1507 | + final int zeroBasedIndexOfStartWaypoint, final boolean showLine) { |
|
| 1508 | + final Position position1DTO = courseDTO.waypointPositions.get(zeroBasedIndexOfStartWaypoint); |
|
| 1509 | + final Position position2DTO = courseDTO.waypointPositions.get(zeroBasedIndexOfStartWaypoint+1); |
|
| 1510 | + final LineInfoProvider lineInfoProvider = new LineInfoProvider() { |
|
| 1511 | + @Override |
|
| 1512 | + public String getLineInfo() { |
|
| 1513 | + final StringBuilder sb = new StringBuilder(); |
|
| 1514 | + sb.append(stringMessages.courseMiddleLine()); |
|
| 1515 | + sb.append('\n'); |
|
| 1516 | + sb.append(NumberFormat.getFormat("0").format( |
|
| 1517 | + Math.abs(position1DTO.getDistance(position2DTO).getMeters()))+stringMessages.metersUnit()); |
|
| 1518 | + if (lastCombinedWindTrackInfoDTO != null) { |
|
| 1519 | + final WindTrackInfoDTO windTrackAtLegMiddle = lastCombinedWindTrackInfoDTO.getCombinedWindOnLegMiddle(zeroBasedIndexOfStartWaypoint); |
|
| 1520 | + if (windTrackAtLegMiddle != null && windTrackAtLegMiddle.windFixes != null && !windTrackAtLegMiddle.windFixes.isEmpty()) { |
|
| 1521 | + WindDTO windAtLegMiddle = windTrackAtLegMiddle.windFixes.get(0); |
|
| 1522 | + final double legBearingDeg = position1DTO.getBearingGreatCircle(position2DTO).getDegrees(); |
|
| 1523 | + final String diff = NumberFormat.getFormat("0.0").format( |
|
| 1524 | + Math.min(Math.abs(windAtLegMiddle.dampenedTrueWindBearingDeg-legBearingDeg), |
|
| 1525 | + Math.abs(windAtLegMiddle.dampenedTrueWindFromDeg-legBearingDeg))); |
|
| 1526 | + sb.append(", "); |
|
| 1527 | + sb.append(stringMessages.degreesToWind(diff)); |
|
| 1528 | + } |
|
| 1529 | + } |
|
| 1530 | + return sb.toString(); |
|
| 1531 | + } |
|
| 1532 | + }; |
|
| 1533 | + return showOrRemoveOrUpdateLine(lineToShowOrRemoveOrUpdate, showLine, position1DTO, position2DTO, lineInfoProvider, "#2268a0"); |
|
| 1534 | + } |
|
| 1535 | + |
|
| 1536 | + private interface LineInfoProvider { |
|
| 1537 | + String getLineInfo(); |
|
| 1538 | + } |
|
| 1539 | + |
|
| 1540 | + /** |
|
| 1541 | + * @param showLine |
|
| 1542 | + * tells whether or not to show the line; if the <code>lineToShowOrRemoveOrUpdate</code> references a |
|
| 1543 | + * line but the line shall not be shown, the line is removed from the map; conversely, if the line is not |
|
| 1544 | + * yet shown but shall be, a new line is created, added to the map and returned. If the line is shown and |
|
| 1545 | + * shall continue to be shown, the line is returned after updating its vertex coordinates. |
|
| 1546 | + * @return <code>null</code> if the line is not shown; the polyline object representing the line being displayed |
|
| 1547 | + * otherwise |
|
| 1548 | + */ |
|
| 1549 | + private Polyline showOrRemoveOrUpdateLine(Polyline lineToShowOrRemoveOrUpdate, final boolean showLine, |
|
| 1550 | + final Position position1DTO, final Position position2DTO, final LineInfoProvider lineInfoProvider, String lineColorRGB) { |
|
| 1551 | + if (showLine) { |
|
| 1552 | + LatLng courseMiddleLinePoint1 = coordinateSystem.toLatLng(position1DTO); |
|
| 1553 | + LatLng courseMiddleLinePoint2 = coordinateSystem.toLatLng(position2DTO); |
|
| 1554 | + final MVCArray<LatLng> pointsAsArray; |
|
| 1555 | + if (lineToShowOrRemoveOrUpdate == null) { |
|
| 1556 | + PolylineOptions options = PolylineOptions.newInstance(); |
|
| 1557 | + options.setClickable(true); |
|
| 1558 | + options.setGeodesic(true); |
|
| 1559 | + options.setStrokeColor(lineColorRGB); |
|
| 1560 | + options.setStrokeWeight(1); |
|
| 1561 | + options.setStrokeOpacity(1.0); |
|
| 1562 | + pointsAsArray = MVCArray.newInstance(); |
|
| 1563 | + lineToShowOrRemoveOrUpdate = Polyline.newInstance(options); |
|
| 1564 | + lineToShowOrRemoveOrUpdate.setPath(pointsAsArray); |
|
| 1565 | + lineToShowOrRemoveOrUpdate.setMap(map); |
|
| 1566 | + Hoverline lineToShowOrRemoveOrUpdateHoverline = new Hoverline(lineToShowOrRemoveOrUpdate, options, this); |
|
| 1567 | + lineToShowOrRemoveOrUpdate.addMouseOverHandler(new MouseOverMapHandler() { |
|
| 1568 | + @Override |
|
| 1569 | + public void onEvent(MouseOverMapEvent event) { |
|
| 1570 | + map.setTitle(lineInfoProvider.getLineInfo()); |
|
| 1571 | + } |
|
| 1572 | + }); |
|
| 1573 | + lineToShowOrRemoveOrUpdateHoverline.addMouseOutMoveHandler(new MouseOutMapHandler() { |
|
| 1574 | + @Override |
|
| 1575 | + public void onEvent(MouseOutMapEvent event) { |
|
| 1576 | + map.setTitle(""); |
|
| 1577 | + } |
|
| 1578 | + }); |
|
| 1579 | + } else { |
|
| 1580 | + pointsAsArray = lineToShowOrRemoveOrUpdate.getPath(); |
|
| 1581 | + pointsAsArray.removeAt(1); |
|
| 1582 | + pointsAsArray.removeAt(0); |
|
| 1583 | + } |
|
| 1584 | + adjustInfoOverlayForVisibleLine(lineToShowOrRemoveOrUpdate, position1DTO, position2DTO, lineInfoProvider); |
|
| 1585 | + pointsAsArray.insertAt(0, courseMiddleLinePoint1); |
|
| 1586 | + pointsAsArray.insertAt(1, courseMiddleLinePoint2); |
|
| 1587 | + } else { |
|
| 1588 | + if (lineToShowOrRemoveOrUpdate != null) { |
|
| 1589 | + lineToShowOrRemoveOrUpdate.setMap(null); |
|
| 1590 | + adjustInfoOverlayForRemovedLine(lineToShowOrRemoveOrUpdate); |
|
| 1591 | + lineToShowOrRemoveOrUpdate = null; |
|
| 1592 | + } |
|
| 1593 | + } |
|
| 1594 | + return lineToShowOrRemoveOrUpdate; |
|
| 1595 | + } |
|
| 1596 | + |
|
| 1597 | + private void adjustInfoOverlayForRemovedLine(Polyline lineToShowOrRemoveOrUpdate) { |
|
| 1598 | + SmallTransparentInfoOverlay infoOverlay = infoOverlaysForLinesForCourseGeometry.remove(lineToShowOrRemoveOrUpdate); |
|
| 1599 | + if (infoOverlay != null) { |
|
| 1600 | + infoOverlay.removeFromMap(); |
|
| 1601 | + } |
|
| 1602 | + } |
|
| 1603 | + |
|
| 1604 | + private void adjustInfoOverlayForVisibleLine(Polyline lineToShowOrRemoveOrUpdate, final Position position1DTO, |
|
| 1605 | + final Position position2DTO, final LineInfoProvider lineInfoProvider) { |
|
| 1606 | + SmallTransparentInfoOverlay infoOverlay = infoOverlaysForLinesForCourseGeometry.get(lineToShowOrRemoveOrUpdate); |
|
| 1607 | + if (getSettings().getHelpLinesSettings().isVisible(HelpLineTypes.COURSEGEOMETRY)) { |
|
| 1608 | + if (infoOverlay == null) { |
|
| 1609 | + infoOverlay = new SmallTransparentInfoOverlay(map, RaceMapOverlaysZIndexes.INFO_OVERLAY_ZINDEX, lineInfoProvider.getLineInfo(), coordinateSystem); |
|
| 1610 | + infoOverlaysForLinesForCourseGeometry.put(lineToShowOrRemoveOrUpdate, infoOverlay); |
|
| 1611 | + infoOverlay.addToMap(); |
|
| 1612 | + } else { |
|
| 1613 | + infoOverlay.setInfoText(lineInfoProvider.getLineInfo()); |
|
| 1614 | + } |
|
| 1615 | + infoOverlay.setPosition(position1DTO.translateGreatCircle(position1DTO.getBearingGreatCircle(position2DTO), |
|
| 1616 | + position1DTO.getDistance(position2DTO).scale(0.5)), /* transition time */ -1); |
|
| 1617 | + infoOverlay.draw(); |
|
| 1618 | + } else { |
|
| 1619 | + if (infoOverlay != null) { |
|
| 1620 | + infoOverlay.removeFromMap(); |
|
| 1621 | + infoOverlaysForLinesForCourseGeometry.remove(lineToShowOrRemoveOrUpdate); |
|
| 1622 | + } |
|
| 1623 | + } |
|
| 1624 | + } |
|
| 1625 | + |
|
| 1626 | + /** |
|
| 1627 | + * If, according to {@link #lastRaceTimesInfo} and {@link #timer} the race is |
|
| 1628 | + * still in the pre-start phase, show a {@link SmallTransparentInfoOverlay} at the |
|
| 1629 | + * start line that shows the count down. |
|
| 1630 | + */ |
|
| 1631 | + private void updateCountdownCanvas(WaypointDTO startWaypoint) { |
|
| 1632 | + if (!settings.isShowSelectedCompetitorsInfo() || startWaypoint == null || Util.isEmpty(startWaypoint.controlPoint.getMarks()) |
|
| 1633 | + || lastRaceTimesInfo == null || lastRaceTimesInfo.startOfRace == null || timer.getTime().after(lastRaceTimesInfo.startOfRace)) { |
|
| 1634 | + if (countDownOverlay != null) { |
|
| 1635 | + countDownOverlay.removeFromMap(); |
|
| 1636 | + countDownOverlay = null; |
|
| 1637 | + } |
|
| 1638 | + } else { |
|
| 1639 | + long timeToStartInMs = lastRaceTimesInfo.startOfRace.getTime() - timer.getTime().getTime(); |
|
| 1640 | + String countDownText = timeToStartInMs > 1000 ? stringMessages.timeToStart(DateAndTimeFormatterUtil |
|
| 1641 | + .formatElapsedTime(timeToStartInMs)) : stringMessages.start(); |
|
| 1642 | + if (countDownOverlay == null) { |
|
| 1643 | + countDownOverlay = new SmallTransparentInfoOverlay(map, RaceMapOverlaysZIndexes.INFO_OVERLAY_ZINDEX, |
|
| 1644 | + countDownText, coordinateSystem); |
|
| 1645 | + countDownOverlay.addToMap(); |
|
| 1646 | + } else { |
|
| 1647 | + countDownOverlay.setInfoText(countDownText); |
|
| 1648 | + } |
|
| 1649 | + countDownOverlay.setPosition(startWaypoint.controlPoint.getMarks().iterator().next().position, /* transition time */ -1); |
|
| 1650 | + countDownOverlay.draw(); |
|
| 1651 | + } |
|
| 1652 | + } |
|
| 1653 | + |
|
| 1654 | + // Google scales coordinates so that the globe-tile has mercator-latitude [-pi, +pi], i.e. tile height of 2*pi |
|
| 1655 | + // mercator-latitude pi corresponds to geo-latitude of approx. 85.0998 (where Google cuts off the map visualization) |
|
| 1656 | + // official documentation: http://developers.google.com/maps/documentation/javascript/maptypes#TileCoordinates |
|
| 1657 | + private double getMercatorLatitude(double lat) { |
|
| 1658 | + // cutting-off for latitudes close to +-90 degrees is recommended (to avoid division by zero) |
|
| 1659 | + double sine = Math.max(-0.9999, Math.min(0.9999, Math.sin(Math.PI * lat / 180))); |
|
| 1660 | + return Math.log((1 + sine) / (1 - sine)) / 2; |
|
| 1661 | + } |
|
| 1662 | + |
|
| 1663 | + private int getZoomLevel(LatLngBounds bounds) { |
|
| 1664 | + int GLOBE_PXSIZE = 256; // a constant in Google's map projection |
|
| 1665 | + int MAX_ZOOM = 20; // maximum zoom-level that should be automatically selected |
|
| 1666 | + double LOG2 = Math.log(2.0); |
|
| 1667 | + double deltaLng = bounds.getNorthEast().getLongitude() - bounds.getSouthWest().getLongitude(); |
|
| 1668 | + double deltaLat = getMercatorLatitude(bounds.getNorthEast().getLatitude()) - getMercatorLatitude(bounds.getSouthWest().getLatitude()); |
|
| 1669 | + if ((deltaLng == 0) && (deltaLat == 0)) { |
|
| 1670 | + return MAX_ZOOM; |
|
| 1671 | + } |
|
| 1672 | + if (deltaLng < 0) { |
|
| 1673 | + deltaLng += 360; |
|
| 1674 | + } |
|
| 1675 | + int zoomLng = (int) Math.floor(Math.log(map.getDiv().getClientWidth() * 360 / deltaLng / GLOBE_PXSIZE) / LOG2); |
|
| 1676 | + int zoomLat = (int) Math.floor(Math.log(map.getDiv().getClientHeight() * 2 * Math.PI / deltaLat / GLOBE_PXSIZE) / LOG2); |
|
| 1677 | + return Math.min(Math.min(zoomLat, zoomLng), MAX_ZOOM); |
|
| 1678 | + } |
|
| 1679 | + |
|
| 1680 | + private void zoomMapToNewBounds(LatLngBounds newBounds) { |
|
| 1681 | + if (newBounds != null) { |
|
| 1682 | + LatLngBounds currentMapBounds; |
|
| 1683 | + if (map.getBounds() == null |
|
| 1684 | + || !BoundsUtil.contains((currentMapBounds = map.getBounds()), newBounds) |
|
| 1685 | + || graticuleAreaRatio(currentMapBounds, newBounds) > 10) { |
|
| 1686 | + // only change bounds if the new bounds don't fit into the current map zoom |
|
| 1687 | + Iterable<ZoomTypes> oldZoomSettings = settings.getZoomSettings().getTypesToConsiderOnZoom(); |
|
| 1688 | + setAutoZoomInProgress(true); |
|
| 1689 | + autoZoomLatLngBounds = newBounds; |
|
| 1690 | + int newZoomLevel = getZoomLevel(autoZoomLatLngBounds); |
|
| 1691 | + if (newZoomLevel != map.getZoom()) { |
|
| 1692 | + // distinguish between zoom-in and zoom-out, because the sequence of panTo() and setZoom() |
|
| 1693 | + // appears different on the screen due to map-animations |
|
| 1694 | + // following sequences keep the selected boats allways visible: |
|
| 1695 | + // zoom-in : 1. panTo(), 2. setZoom() |
|
| 1696 | + // zoom-out: 1. setZoom(), 2. panTo() |
|
| 1697 | + autoZoomIn = newZoomLevel > map.getZoom(); |
|
| 1698 | + autoZoomOut = !autoZoomIn; |
|
| 1699 | + autoZoomLevel = newZoomLevel; |
|
| 1700 | + removeTransitions(); |
|
| 1701 | + if (autoZoomIn) { |
|
| 1702 | + map.panTo(autoZoomLatLngBounds.getCenter()); |
|
| 1703 | + } else { |
|
| 1704 | + map.setZoom(autoZoomLevel); |
|
| 1705 | + } |
|
| 1706 | + } else { |
|
| 1707 | + map.panTo(autoZoomLatLngBounds.getCenter()); |
|
| 1708 | + } |
|
| 1709 | + settings.getZoomSettings().setTypesToConsiderOnZoom(oldZoomSettings); |
|
| 1710 | + setAutoZoomInProgress(false); |
|
| 1711 | + } |
|
| 1712 | + } |
|
| 1713 | + } |
|
| 1714 | + |
|
| 1715 | + private double graticuleAreaRatio(LatLngBounds containing, LatLngBounds contained) { |
|
| 1716 | + assert BoundsUtil.contains(containing, contained); |
|
| 1717 | + double containingAreaRatio = getGraticuleArea(containing) / getGraticuleArea(contained); |
|
| 1718 | + return containingAreaRatio; |
|
| 1719 | + } |
|
| 1720 | + |
|
| 1721 | + /** |
|
| 1722 | + * A much simplified "area" calculation for a {@link Bounds} object, multiplying the differences in latitude and longitude degrees. |
|
| 1723 | + * The result therefore is in the order of magnitude of 60*60 square nautical miles. |
|
| 1724 | + */ |
|
| 1725 | + private double getGraticuleArea(LatLngBounds bounds) { |
|
| 1726 | + return ((BoundsUtil.isCrossesDateLine(bounds) ? bounds.getNorthEast().getLongitude()+360 : bounds.getNorthEast().getLongitude())-bounds.getSouthWest().getLongitude()) * |
|
| 1727 | + (bounds.getNorthEast().getLatitude() - bounds.getSouthWest().getLatitude()); |
|
| 1728 | + } |
|
| 1729 | + |
|
| 1730 | + private void setAutoZoomInProgress(boolean autoZoomInProgress) { |
|
| 1731 | + this.autoZoomInProgress = autoZoomInProgress; |
|
| 1732 | + } |
|
| 1733 | + |
|
| 1734 | + boolean isAutoZoomInProgress() { |
|
| 1735 | + return autoZoomInProgress; |
|
| 1736 | + } |
|
| 1737 | + |
|
| 1738 | + /** |
|
| 1739 | + * @param timeForPositionTransitionMillis use -1 to not animate the position transition, e.g., during map zoom or non-play |
|
| 1740 | + */ |
|
| 1741 | + private boolean updateBoatCanvasForCompetitor(CompetitorDTO competitorDTO, Date date, long timeForPositionTransitionMillis) { |
|
| 1742 | + boolean usedExistingCanvas = false; |
|
| 1743 | + GPSFixDTO lastBoatFix = getBoatFix(competitorDTO, date); |
|
| 1744 | + if (lastBoatFix != null) { |
|
| 1745 | + BoatOverlay boatOverlay = boatOverlays.get(competitorDTO); |
|
| 1746 | + if (boatOverlay == null) { |
|
| 1747 | + boatOverlay = createBoatOverlay(RaceMapOverlaysZIndexes.BOATS_ZINDEX, competitorDTO, displayHighlighted(competitorDTO)); |
|
| 1748 | + boatOverlays.put(competitorDTO, boatOverlay); |
|
| 1749 | + boatOverlay.setSelected(displayHighlighted(competitorDTO)); |
|
| 1750 | + boatOverlay.setBoatFix(lastBoatFix, timeForPositionTransitionMillis); |
|
| 1751 | + boatOverlay.addToMap(); |
|
| 1752 | + } else { |
|
| 1753 | + usedExistingCanvas = true; |
|
| 1754 | + boatOverlay.setSelected(displayHighlighted(competitorDTO)); |
|
| 1755 | + boatOverlay.setBoatFix(lastBoatFix, timeForPositionTransitionMillis); |
|
| 1756 | + boatOverlay.draw(); |
|
| 1757 | + } |
|
| 1758 | + } |
|
| 1759 | + |
|
| 1760 | + return usedExistingCanvas; |
|
| 1761 | + } |
|
| 1762 | + |
|
| 1763 | + private boolean displayHighlighted(CompetitorDTO competitorDTO) { |
|
| 1764 | + return !settings.isShowOnlySelectedCompetitors() && competitorSelection.isSelected(competitorDTO); |
|
| 1765 | + } |
|
| 1766 | + |
|
| 1767 | + protected CourseMarkOverlay createCourseMarkOverlay(int zIndex, final MarkDTO markDTO) { |
|
| 1768 | + final CourseMarkOverlay courseMarkOverlay = new CourseMarkOverlay(map, zIndex, markDTO, coordinateSystem); |
|
| 1769 | + courseMarkOverlay.addClickHandler(new ClickMapHandler() { |
|
| 1770 | + @Override |
|
| 1771 | + public void onEvent(ClickMapEvent event) { |
|
| 1772 | + LatLng latlng = courseMarkOverlay.getMarkLatLngPosition(); |
|
| 1773 | + showMarkInfoWindow(markDTO, latlng); |
|
| 1774 | + } |
|
| 1775 | + }); |
|
| 1776 | + return courseMarkOverlay; |
|
| 1777 | + } |
|
| 1778 | + |
|
| 1779 | + private CompetitorInfoOverlay createCompetitorInfoOverlay(int zIndex, final CompetitorDTO competitorDTO) { |
|
| 1780 | + String infoText = competitorDTO.getSailID() == null || competitorDTO.getSailID().isEmpty() ? competitorDTO.getName() : competitorDTO.getSailID(); |
|
| 1781 | + return new CompetitorInfoOverlay(map, zIndex, competitorSelection.getColor(competitorDTO, raceIdentifier), infoText, coordinateSystem); |
|
| 1782 | + } |
|
| 1783 | + |
|
| 1784 | + private BoatOverlay createBoatOverlay(int zIndex, final CompetitorDTO competitorDTO, boolean highlighted) { |
|
| 1785 | + final BoatOverlay boatCanvas = new BoatOverlay(map, zIndex, competitorDTO, competitorSelection.getColor(competitorDTO, raceIdentifier), coordinateSystem); |
|
| 1786 | + boatCanvas.setSelected(highlighted); |
|
| 1787 | + boatCanvas.addClickHandler(new ClickMapHandler() { |
|
| 1788 | + @Override |
|
| 1789 | + public void onEvent(ClickMapEvent event) { |
|
| 1790 | + if (lastInfoWindow != null) { |
|
| 1791 | + lastInfoWindow.close(); |
|
| 1792 | + } |
|
| 1793 | + GPSFixDTO latestFixForCompetitor = getBoatFix(competitorDTO, timer.getTime()); |
|
| 1794 | + LatLng where = coordinateSystem.toLatLng(latestFixForCompetitor.position); |
|
| 1795 | + InfoWindowOptions options = InfoWindowOptions.newInstance(); |
|
| 1796 | + InfoWindow infoWindow = InfoWindow.newInstance(options); |
|
| 1797 | + infoWindow.setContent(getInfoWindowContent(competitorDTO, latestFixForCompetitor)); |
|
| 1798 | + infoWindow.setPosition(where); |
|
| 1799 | + lastInfoWindow = infoWindow; |
|
| 1800 | + infoWindow.open(map); |
|
| 1801 | + } |
|
| 1802 | + }); |
|
| 1803 | + |
|
| 1804 | + boatCanvas.addMouseOverHandler(new MouseOverMapHandler() { |
|
| 1805 | + @Override |
|
| 1806 | + public void onEvent(MouseOverMapEvent event) { |
|
| 1807 | + map.setTitle(competitorDTO.getSailID()); |
|
| 1808 | + } |
|
| 1809 | + }); |
|
| 1810 | + boatCanvas.addMouseOutMoveHandler(new MouseOutMapHandler() { |
|
| 1811 | + @Override |
|
| 1812 | + public void onEvent(MouseOutMapEvent event) { |
|
| 1813 | + map.setTitle(""); |
|
| 1814 | + } |
|
| 1815 | + }); |
|
| 1816 | + |
|
| 1817 | + return boatCanvas; |
|
| 1818 | + } |
|
| 1819 | + |
|
| 1820 | + protected WindSensorOverlay createWindSensorOverlay(int zIndex, final WindSource windSource, final WindTrackInfoDTO windTrackInfoDTO) { |
|
| 1821 | + final WindSensorOverlay windSensorOverlay = new WindSensorOverlay(map, zIndex, raceMapImageManager, stringMessages, coordinateSystem); |
|
| 1822 | + windSensorOverlay.setWindInfo(windTrackInfoDTO, windSource); |
|
| 1823 | + windSensorOverlay.addClickHandler(new ClickMapHandler() { |
|
| 1824 | + @Override |
|
| 1825 | + public void onEvent(ClickMapEvent event) { |
|
| 1826 | + showWindSensorInfoWindow(windSensorOverlay); |
|
| 1827 | + } |
|
| 1828 | + }); |
|
| 1829 | + return windSensorOverlay; |
|
| 1830 | + } |
|
| 1831 | + |
|
| 1832 | + private void showMarkInfoWindow(MarkDTO markDTO, LatLng position) { |
|
| 1833 | + if(lastInfoWindow != null) { |
|
| 1834 | + lastInfoWindow.close(); |
|
| 1835 | + } |
|
| 1836 | + InfoWindowOptions options = InfoWindowOptions.newInstance(); |
|
| 1837 | + InfoWindow infoWindow = InfoWindow.newInstance(options); |
|
| 1838 | + infoWindow.setContent(getInfoWindowContent(markDTO)); |
|
| 1839 | + infoWindow.setPosition(position); |
|
| 1840 | + lastInfoWindow = infoWindow; |
|
| 1841 | + infoWindow.open(map); |
|
| 1842 | + } |
|
| 1843 | + |
|
| 1844 | + private void showCompetitorInfoWindow(final CompetitorDTO competitorDTO, LatLng where) { |
|
| 1845 | + if(lastInfoWindow != null) { |
|
| 1846 | + lastInfoWindow.close(); |
|
| 1847 | + } |
|
| 1848 | + GPSFixDTO latestFixForCompetitor = getBoatFix(competitorDTO, timer.getTime()); |
|
| 1849 | + // TODO find close fix where the mouse was; see BUG 470 |
|
| 1850 | + InfoWindowOptions options = InfoWindowOptions.newInstance(); |
|
| 1851 | + InfoWindow infoWindow = InfoWindow.newInstance(options); |
|
| 1852 | + infoWindow.setContent(getInfoWindowContent(competitorDTO, latestFixForCompetitor)); |
|
| 1853 | + infoWindow.setPosition(where); |
|
| 1854 | + lastInfoWindow = infoWindow; |
|
| 1855 | + infoWindow.open(map); |
|
| 1856 | + } |
|
| 1857 | + |
|
| 1858 | + private String formatPosition(double lat, double lng) { |
|
| 1859 | + NumberFormat numberFormat = NumberFormat.getFormat("0.00000"); |
|
| 1860 | + String result = numberFormat.format(lat) + " lat, " + numberFormat.format(lng) + " lng"; |
|
| 1861 | + return result; |
|
| 1862 | + } |
|
| 1863 | + |
|
| 1864 | + private void showWindSensorInfoWindow(final WindSensorOverlay windSensorOverlay) { |
|
| 1865 | + WindSource windSource = windSensorOverlay.getWindSource(); |
|
| 1866 | + WindTrackInfoDTO windTrackInfoDTO = windSensorOverlay.getWindTrackInfoDTO(); |
|
| 1867 | + WindDTO windDTO = windTrackInfoDTO.windFixes.get(0); |
|
| 1868 | + if(windDTO != null && windDTO.position != null) { |
|
| 1869 | + if(lastInfoWindow != null) { |
|
| 1870 | + lastInfoWindow.close(); |
|
| 1871 | + } |
|
| 1872 | + LatLng where = coordinateSystem.toLatLng(windDTO.position); |
|
| 1873 | + InfoWindowOptions options = InfoWindowOptions.newInstance(); |
|
| 1874 | + InfoWindow infoWindow = InfoWindow.newInstance(options); |
|
| 1875 | + infoWindow.setContent(getInfoWindowContent(windSource, windTrackInfoDTO)); |
|
| 1876 | + infoWindow.setPosition(where); |
|
| 1877 | + lastInfoWindow = infoWindow; |
|
| 1878 | + infoWindow.open(map); |
|
| 1879 | + } |
|
| 1880 | + } |
|
| 1881 | + |
|
| 1882 | + private Widget createInfoWindowLabelAndValue(String labelName, String value) { |
|
| 1883 | + FlowPanel flowPanel = new FlowPanel(); |
|
| 1884 | + Label label = new Label(labelName + ":"); |
|
| 1885 | + label.setWordWrap(false); |
|
| 1886 | + label.getElement().getStyle().setFloat(Style.Float.LEFT); |
|
| 1887 | + label.getElement().getStyle().setPadding(3, Style.Unit.PX); |
|
| 1888 | + label.getElement().getStyle().setFontWeight(Style.FontWeight.BOLD); |
|
| 1889 | + flowPanel.add(label); |
|
| 1890 | + |
|
| 1891 | + Label valueLabel = new Label(value); |
|
| 1892 | + valueLabel.setWordWrap(false); |
|
| 1893 | + valueLabel.getElement().getStyle().setFloat(Style.Float.LEFT); |
|
| 1894 | + valueLabel.getElement().getStyle().setPadding(3, Style.Unit.PX); |
|
| 1895 | + flowPanel.add(valueLabel); |
|
| 1896 | + |
|
| 1897 | + return flowPanel; |
|
| 1898 | + } |
|
| 1899 | + |
|
| 1900 | + private Widget getInfoWindowContent(MarkDTO markDTO) { |
|
| 1901 | + VerticalPanel vPanel = new VerticalPanel(); |
|
| 1902 | + vPanel.add(createInfoWindowLabelAndValue(stringMessages.mark(), markDTO.getName())); |
|
| 1903 | + vPanel.add(createInfoWindowLabelAndValue(stringMessages.position(), formatPosition(markDTO.position.getLatDeg(), markDTO.position.getLngDeg()))); |
|
| 1904 | + return vPanel; |
|
| 1905 | + } |
|
| 1906 | + |
|
| 1907 | + private Widget getInfoWindowContent(WindSource windSource, WindTrackInfoDTO windTrackInfoDTO) { |
|
| 1908 | + WindDTO windDTO = windTrackInfoDTO.windFixes.get(0); |
|
| 1909 | + NumberFormat numberFormat = NumberFormat.getFormat("0.0"); |
|
| 1910 | + VerticalPanel vPanel = new VerticalPanel(); |
|
| 1911 | + vPanel.add(createInfoWindowLabelAndValue(stringMessages.windSource(), WindSourceTypeFormatter.format(windSource, stringMessages))); |
|
| 1912 | + vPanel.add(createInfoWindowLabelAndValue(stringMessages.wind(), Math.round(windDTO.dampenedTrueWindFromDeg) + " " + stringMessages.degreesShort())); |
|
| 1913 | + vPanel.add(createInfoWindowLabelAndValue(stringMessages.windSpeed(), numberFormat.format(windDTO.dampenedTrueWindSpeedInKnots))); |
|
| 1914 | + vPanel.add(createInfoWindowLabelAndValue(stringMessages.position(), formatPosition(windDTO.position.getLatDeg(), windDTO.position.getLngDeg()))); |
|
| 1915 | + return vPanel; |
|
| 1916 | + } |
|
| 1917 | + |
|
| 1918 | + private Widget getInfoWindowContent(CompetitorDTO competitorDTO, GPSFixDTO lastFix) { |
|
| 1919 | + final VerticalPanel vPanel = new VerticalPanel(); |
|
| 1920 | + vPanel.setWidth("350px"); |
|
| 1921 | + vPanel.add(createInfoWindowLabelAndValue(stringMessages.competitor(), competitorDTO.getName())); |
|
| 1922 | + vPanel.add(createInfoWindowLabelAndValue(stringMessages.sailNumber(), competitorDTO.getSailID())); |
|
| 1923 | + Integer rank = null; |
|
| 1924 | + if (quickRanks != null) { |
|
| 1925 | + QuickRankDTO quickRank = quickRanks.get(competitorDTO); |
|
| 1926 | + if (quickRank != null) { |
|
| 1927 | + rank = quickRank.rank; |
|
| 1928 | + } |
|
| 1929 | + } |
|
| 1930 | + if (rank != null) { |
|
| 1931 | + vPanel.add(createInfoWindowLabelAndValue(stringMessages.rank(), String.valueOf(rank))); |
|
| 1932 | + } |
|
| 1933 | + SpeedWithBearingDTO speedWithBearing = lastFix.speedWithBearing; |
|
| 1934 | + if (speedWithBearing == null) { |
|
| 1935 | + // TODO should we show the boat at all? |
|
| 1936 | + speedWithBearing = new SpeedWithBearingDTO(0, 0); |
|
| 1937 | + } |
|
| 1938 | + vPanel.add(createInfoWindowLabelAndValue(stringMessages.speed(), |
|
| 1939 | + NumberFormatterFactory.getDecimalFormat(1).format(speedWithBearing.speedInKnots) + " "+stringMessages.knotsUnit())); |
|
| 1940 | + vPanel.add(createInfoWindowLabelAndValue(stringMessages.bearing(), (int) speedWithBearing.bearingInDegrees + " "+stringMessages.degreesShort())); |
|
| 1941 | + if (lastFix.degreesBoatToTheWind != null) { |
|
| 1942 | + vPanel.add(createInfoWindowLabelAndValue(stringMessages.degreesBoatToTheWind(), |
|
| 1943 | + (int) Math.abs(lastFix.degreesBoatToTheWind) + " " + stringMessages.degreesShort())); |
|
| 1944 | + } |
|
| 1945 | + if (!selectedRaces.isEmpty()) { |
|
| 1946 | + RegattaAndRaceIdentifier race = selectedRaces.get(selectedRaces.size() - 1); |
|
| 1947 | + if (race != null) { |
|
| 1948 | + Map<CompetitorDTO, Date> from = new HashMap<CompetitorDTO, Date>(); |
|
| 1949 | + from.put(competitorDTO, fixesAndTails.getFixes(competitorDTO).get(fixesAndTails.getFirstShownFix(competitorDTO)).timepoint); |
|
| 1950 | + Map<CompetitorDTO, Date> to = new HashMap<CompetitorDTO, Date>(); |
|
| 1951 | + to.put(competitorDTO, getBoatFix(competitorDTO, timer.getTime()).timepoint); |
|
| 1952 | + sailingService.getDouglasPoints(race, from, to, 3, |
|
| 1953 | + new AsyncCallback<Map<CompetitorDTO, List<GPSFixDTO>>>() { |
|
| 1954 | + @Override |
|
| 1955 | + public void onFailure(Throwable caught) { |
|
| 1956 | + errorReporter.reportError("Error obtaining douglas positions: " + caught.getMessage(), true /*silentMode */); |
|
| 1957 | + } |
|
| 1958 | + |
|
| 1959 | + @Override |
|
| 1960 | + public void onSuccess(Map<CompetitorDTO, List<GPSFixDTO>> result) { |
|
| 1961 | + lastDouglasPeuckerResult = result; |
|
| 1962 | + if (douglasMarkers != null) { |
|
| 1963 | + removeAllMarkDouglasPeuckerpoints(); |
|
| 1964 | + } |
|
| 1965 | + if (!(timer.getPlayState() == PlayStates.Playing)) { |
|
| 1966 | + if (settings.isShowDouglasPeuckerPoints()) { |
|
| 1967 | + showMarkDouglasPeuckerPoints(result); |
|
| 1968 | + } |
|
| 1969 | + } |
|
| 1970 | + } |
|
| 1971 | + }); |
|
| 1972 | + sailingService.getManeuvers(race, from, to, |
|
| 1973 | + new AsyncCallback<Map<CompetitorDTO, List<ManeuverDTO>>>() { |
|
| 1974 | + @Override |
|
| 1975 | + public void onFailure(Throwable caught) { |
|
| 1976 | + errorReporter.reportError("Error obtaining maneuvers: " + caught.getMessage(), true /*silentMode */); |
|
| 1977 | + } |
|
| 1978 | + |
|
| 1979 | + @Override |
|
| 1980 | + public void onSuccess(Map<CompetitorDTO, List<ManeuverDTO>> result) { |
|
| 1981 | + lastManeuverResult = result; |
|
| 1982 | + if (maneuverMarkers != null) { |
|
| 1983 | + removeAllManeuverMarkers(); |
|
| 1984 | + } |
|
| 1985 | + if (!(timer.getPlayState() == PlayStates.Playing)) { |
|
| 1986 | + showManeuvers(result); |
|
| 1987 | + } |
|
| 1988 | + } |
|
| 1989 | + }); |
|
| 1990 | + |
|
| 1991 | + } |
|
| 1992 | + } |
|
| 1993 | + return vPanel; |
|
| 1994 | + } |
|
| 1995 | + |
|
| 1996 | + private Iterable<CompetitorDTO> getCompetitorsToShow() { |
|
| 1997 | + Iterable<CompetitorDTO> result; |
|
| 1998 | + Iterable<CompetitorDTO> selection = competitorSelection.getSelectedCompetitors(); |
|
| 1999 | + if (!settings.isShowOnlySelectedCompetitors() || Util.isEmpty(selection)) { |
|
| 2000 | + result = competitorSelection.getFilteredCompetitors(); |
|
| 2001 | + } else { |
|
| 2002 | + result = selection; |
|
| 2003 | + } |
|
| 2004 | + return result; |
|
| 2005 | + } |
|
| 2006 | + |
|
| 2007 | + protected void removeAllMarkDouglasPeuckerpoints() { |
|
| 2008 | + if (douglasMarkers != null) { |
|
| 2009 | + for (Marker marker : douglasMarkers) { |
|
| 2010 | + marker.setMap((MapWidget) null); |
|
| 2011 | + } |
|
| 2012 | + } |
|
| 2013 | + douglasMarkers = null; |
|
| 2014 | + } |
|
| 2015 | + |
|
| 2016 | + protected void removeAllManeuverMarkers() { |
|
| 2017 | + if (maneuverMarkers != null) { |
|
| 2018 | + for (Marker marker : maneuverMarkers) { |
|
| 2019 | + marker.setMap((MapWidget) null); |
|
| 2020 | + } |
|
| 2021 | + maneuverMarkers = null; |
|
| 2022 | + } |
|
| 2023 | + } |
|
| 2024 | + |
|
| 2025 | + private void showMarkDouglasPeuckerPoints(Map<CompetitorDTO, List<GPSFixDTO>> gpsFixPointMapForCompetitors) { |
|
| 2026 | + douglasMarkers = new HashSet<Marker>(); |
|
| 2027 | + if (map != null && gpsFixPointMapForCompetitors != null) { |
|
| 2028 | + Set<CompetitorDTO> keySet = gpsFixPointMapForCompetitors.keySet(); |
|
| 2029 | + Iterator<CompetitorDTO> iter = keySet.iterator(); |
|
| 2030 | + while (iter.hasNext()) { |
|
| 2031 | + CompetitorDTO competitorDTO = iter.next(); |
|
| 2032 | + List<GPSFixDTO> gpsFix = gpsFixPointMapForCompetitors.get(competitorDTO); |
|
| 2033 | + for (GPSFixDTO fix : gpsFix) { |
|
| 2034 | + LatLng latLng = coordinateSystem.toLatLng(fix.position); |
|
| 2035 | + MarkerOptions options = MarkerOptions.newInstance(); |
|
| 2036 | + options.setTitle(fix.timepoint+": "+fix.position+", "+fix.speedWithBearing.toString()); |
|
| 2037 | + Marker marker = Marker.newInstance(options); |
|
| 2038 | + marker.setPosition(latLng); |
|
| 2039 | + douglasMarkers.add(marker); |
|
| 2040 | + marker.setMap(map); |
|
| 2041 | + } |
|
| 2042 | + } |
|
| 2043 | + } |
|
| 2044 | + } |
|
| 2045 | + |
|
| 2046 | + private void showManeuvers(Map<CompetitorDTO, List<ManeuverDTO>> maneuvers) { |
|
| 2047 | + maneuverMarkers = new HashSet<Marker>(); |
|
| 2048 | + if (map != null && maneuvers != null) { |
|
| 2049 | + Set<CompetitorDTO> keySet = maneuvers.keySet(); |
|
| 2050 | + Iterator<CompetitorDTO> iter = keySet.iterator(); |
|
| 2051 | + while (iter.hasNext()) { |
|
| 2052 | + CompetitorDTO competitorDTO = iter.next(); |
|
| 2053 | + List<ManeuverDTO> maneuversForCompetitor = maneuvers.get(competitorDTO); |
|
| 2054 | + for (ManeuverDTO maneuver : maneuversForCompetitor) { |
|
| 2055 | + if (settings.isShowManeuverType(maneuver.type)) { |
|
| 2056 | + LatLng latLng = coordinateSystem.toLatLng(maneuver.position); |
|
| 2057 | + Marker maneuverMarker = raceMapImageManager.maneuverIconsForTypeAndTargetTack.get(new com.sap.sse.common.Util.Pair<ManeuverType, Tack>(maneuver.type, maneuver.newTack)); |
|
| 2058 | + MarkerOptions options = MarkerOptions.newInstance(); |
|
| 2059 | + options.setTitle(maneuver.toString(stringMessages)); |
|
| 2060 | + options.setIcon(maneuverMarker.getIcon_MarkerImage()); |
|
| 2061 | + Marker marker = Marker.newInstance(options); |
|
| 2062 | + marker.setPosition(latLng); |
|
| 2063 | + maneuverMarkers.add(marker); |
|
| 2064 | + marker.setMap(map); |
|
| 2065 | + } |
|
| 2066 | + } |
|
| 2067 | + } |
|
| 2068 | + } |
|
| 2069 | + } |
|
| 2070 | + |
|
| 2071 | + /** |
|
| 2072 | + * @param date |
|
| 2073 | + * the point in time for which to determine the competitor's boat position; approximated by using the fix |
|
| 2074 | + * from {@link #fixes} whose time point comes closest to this date |
|
| 2075 | + * |
|
| 2076 | + * @return The GPS fix for the given competitor from {@link #fixes} that is closest to <code>date</code>, or |
|
| 2077 | + * <code>null</code> if no fix is available |
|
| 2078 | + */ |
|
| 2079 | + private GPSFixDTO getBoatFix(CompetitorDTO competitorDTO, Date date) { |
|
| 2080 | + GPSFixDTO result = null; |
|
| 2081 | + List<GPSFixDTO> competitorFixes = fixesAndTails.getFixes(competitorDTO); |
|
| 2082 | + if (competitorFixes != null && !competitorFixes.isEmpty()) { |
|
| 2083 | + int i = Collections.binarySearch(competitorFixes, new GPSFixDTO(date, null, null, (WindDTO) null, null, null, false), |
|
| 2084 | + new Comparator<GPSFixDTO>() { |
|
| 2085 | + @Override |
|
| 2086 | + public int compare(GPSFixDTO o1, GPSFixDTO o2) { |
|
| 2087 | + return o1.timepoint.compareTo(o2.timepoint); |
|
| 2088 | + } |
|
| 2089 | + }); |
|
| 2090 | + if (i<0) { |
|
| 2091 | + i = -i-1; // no perfect match; i is now the insertion point |
|
| 2092 | + // if the insertion point is at the end, use last fix |
|
| 2093 | + if (i >= competitorFixes.size()) { |
|
| 2094 | + result = competitorFixes.get(competitorFixes.size()-1); |
|
| 2095 | + } else if (i == 0) { |
|
| 2096 | + // if the insertion point is at the beginning, use first fix |
|
| 2097 | + result = competitorFixes.get(0); |
|
| 2098 | + } else { |
|
| 2099 | + // competitorFixes must have at least two elements, and i points neither to the end nor the beginning; |
|
| 2100 | + // get the fix from i and i+1 whose timepoint is closer to date |
|
| 2101 | + final GPSFixDTO fixBefore = competitorFixes.get(i-1); |
|
| 2102 | + final GPSFixDTO fixAfter = competitorFixes.get(i); |
|
| 2103 | + final GPSFixDTO closer; |
|
| 2104 | + if (date.getTime() - fixBefore.timepoint.getTime() < fixAfter.timepoint.getTime() - date.getTime()) { |
|
| 2105 | + closer = fixBefore; |
|
| 2106 | + } else { |
|
| 2107 | + closer = fixAfter; |
|
| 2108 | + } |
|
| 2109 | + // now compute a weighted average depending on the time difference to "date" (see also bug 1924) |
|
| 2110 | + double factorForAfter = (double) (date.getTime()-fixBefore.timepoint.getTime()) / (double) (fixAfter.timepoint.getTime() - fixBefore.timepoint.getTime()); |
|
| 2111 | + double factorForBefore = 1-factorForAfter; |
|
| 2112 | + DegreePosition betweenPosition = new DegreePosition(factorForBefore*fixBefore.position.getLatDeg() + factorForAfter*fixAfter.position.getLatDeg(), |
|
| 2113 | + factorForBefore*fixBefore.position.getLngDeg() + factorForAfter*fixAfter.position.getLngDeg()); |
|
| 2114 | + final double betweenBearing; |
|
| 2115 | + if (fixBefore.speedWithBearing == null) { |
|
| 2116 | + if (fixAfter.speedWithBearing == null) { |
|
| 2117 | + betweenBearing = 0; |
|
| 2118 | + } else { |
|
| 2119 | + betweenBearing = fixAfter.speedWithBearing.bearingInDegrees; |
|
| 2120 | + } |
|
| 2121 | + } else if (fixAfter.speedWithBearing == null) { |
|
| 2122 | + betweenBearing = fixBefore.speedWithBearing.bearingInDegrees; |
|
| 2123 | + } else { |
|
| 2124 | + betweenBearing = new ScalableBearing(new DegreeBearingImpl(fixBefore.speedWithBearing.bearingInDegrees)). |
|
| 2125 | + multiply(factorForBefore).add(new ScalableBearing(new DegreeBearingImpl(fixAfter.speedWithBearing.bearingInDegrees)). |
|
| 2126 | + multiply(factorForAfter)).divide(1).getDegrees(); |
|
| 2127 | + } |
|
| 2128 | + SpeedWithBearingDTO betweenSpeed = new SpeedWithBearingDTO( |
|
| 2129 | + factorForBefore*(fixBefore.speedWithBearing==null?0:fixBefore.speedWithBearing.speedInKnots) + |
|
| 2130 | + factorForAfter*(fixAfter.speedWithBearing==null?0:fixAfter.speedWithBearing.speedInKnots), |
|
| 2131 | + betweenBearing); |
|
| 2132 | + result = new GPSFixDTO(date, betweenPosition, betweenSpeed, closer.degreesBoatToTheWind, |
|
| 2133 | + closer.tack, closer.legType, fixBefore.extrapolated || fixAfter.extrapolated); |
|
| 2134 | + } |
|
| 2135 | + } else { |
|
| 2136 | + // perfect match |
|
| 2137 | + final GPSFixDTO fixAfter = competitorFixes.get(i); |
|
| 2138 | + result = fixAfter; |
|
| 2139 | + } |
|
| 2140 | + } |
|
| 2141 | + return result; |
|
| 2142 | + } |
|
| 2143 | + |
|
| 2144 | + public RaceMapSettings getSettings() { |
|
| 2145 | + return settings; |
|
| 2146 | + } |
|
| 2147 | + |
|
| 2148 | + @Override |
|
| 2149 | + public void addedToSelection(CompetitorDTO competitor) { |
|
| 2150 | + if (settings.isShowOnlySelectedCompetitors()) { |
|
| 2151 | + if (Util.size(competitorSelection.getSelectedCompetitors()) == 1) { |
|
| 2152 | + // first competitors selected; remove all others from map |
|
| 2153 | + Iterator<Map.Entry<CompetitorDTO, BoatOverlay>> i = boatOverlays.entrySet().iterator(); |
|
| 2154 | + while (i.hasNext()) { |
|
| 2155 | + Entry<CompetitorDTO, BoatOverlay> next = i.next(); |
|
| 2156 | + if (!next.getKey().equals(competitor)) { |
|
| 2157 | + BoatOverlay boatOverlay = next.getValue(); |
|
| 2158 | + boatOverlay.removeFromMap(); |
|
| 2159 | + fixesAndTails.removeTail(next.getKey()); |
|
| 2160 | + i.remove(); // only this way a ConcurrentModificationException while looping can be avoided |
|
| 2161 | + } |
|
| 2162 | + } |
|
| 2163 | + showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors()); |
|
| 2164 | + } else { |
|
| 2165 | + // adding a single competitor; may need to re-load data, so refresh: |
|
| 2166 | + timeChanged(timer.getTime(), null); |
|
| 2167 | + } |
|
| 2168 | + } else { |
|
| 2169 | + // only change highlighting |
|
| 2170 | + BoatOverlay boatCanvas = boatOverlays.get(competitor); |
|
| 2171 | + if (boatCanvas != null) { |
|
| 2172 | + boatCanvas.setSelected(displayHighlighted(competitor)); |
|
| 2173 | + boatCanvas.draw(); |
|
| 2174 | + showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors()); |
|
| 2175 | + } else { |
|
| 2176 | + // seems like an internal error not to find the lowlighted marker; but maybe the |
|
| 2177 | + // competitor was added late to the race; |
|
| 2178 | + // data for newly selected competitor supposedly missing; refresh |
|
| 2179 | + timeChanged(timer.getTime(), null); |
|
| 2180 | + } |
|
| 2181 | + } |
|
| 2182 | + // Now update tails for all competitors because selection change may also affect all unselected competitors |
|
| 2183 | + for (CompetitorDTO oneOfAllCompetitors : competitorSelection.getAllCompetitors()) { |
|
| 2184 | + Polyline tail = fixesAndTails.getTail(oneOfAllCompetitors); |
|
| 2185 | + if (tail != null) { |
|
| 2186 | + PolylineOptions newOptions = createTailStyle(oneOfAllCompetitors, displayHighlighted(oneOfAllCompetitors)); |
|
| 2187 | + tail.setOptions(newOptions); |
|
| 2188 | + } |
|
| 2189 | + } |
|
| 2190 | + // Trigger auto-zoom if needed |
|
| 2191 | + RaceMapZoomSettings zoomSettings = settings.getZoomSettings(); |
|
| 2192 | + if (!zoomSettings.containsZoomType(ZoomTypes.NONE) && zoomSettings.isZoomToSelectedCompetitors()) { |
|
| 2193 | + zoomMapToNewBounds(zoomSettings.getNewBounds(this)); |
|
| 2194 | + } |
|
| 2195 | + } |
|
| 2196 | + |
|
| 2197 | + @Override |
|
| 2198 | + public void removedFromSelection(CompetitorDTO competitor) { |
|
| 2199 | + if (isShowAnyHelperLines()) { |
|
| 2200 | + // helper lines depend on which competitor is visible, because the *visible* leader is used for |
|
| 2201 | + // deciding which helper lines to show: |
|
| 2202 | + timeChanged(timer.getTime(), null); |
|
| 2203 | + } else { |
|
| 2204 | + // try a more incremental update otherwise |
|
| 2205 | + if (settings.isShowOnlySelectedCompetitors()) { |
|
| 2206 | + // if selection is now empty, show all competitors |
|
| 2207 | + if (Util.isEmpty(competitorSelection.getSelectedCompetitors())) { |
|
| 2208 | + timeChanged(timer.getTime(), null); |
|
| 2209 | + } else { |
|
| 2210 | + // otherwise remove only deselected competitor's boat images and tail |
|
| 2211 | + BoatOverlay removedBoatOverlay = boatOverlays.remove(competitor); |
|
| 2212 | + if (removedBoatOverlay != null) { |
|
| 2213 | + removedBoatOverlay.removeFromMap(); |
|
| 2214 | + } |
|
| 2215 | + fixesAndTails.removeTail(competitor); |
|
| 2216 | + showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors()); |
|
| 2217 | + } |
|
| 2218 | + } else { |
|
| 2219 | + // "lowlight" currently selected competitor |
|
| 2220 | + BoatOverlay boatCanvas = boatOverlays.get(competitor); |
|
| 2221 | + if (boatCanvas != null) { |
|
| 2222 | + boatCanvas.setSelected(displayHighlighted(competitor)); |
|
| 2223 | + boatCanvas.draw(); |
|
| 2224 | + } |
|
| 2225 | + showCompetitorInfoOnMap(timer.getTime(), -1, competitorSelection.getSelectedFilteredCompetitors()); |
|
| 2226 | + } |
|
| 2227 | + } |
|
| 2228 | + //Trigger auto-zoom if needed |
|
| 2229 | + RaceMapZoomSettings zoomSettings = settings.getZoomSettings(); |
|
| 2230 | + if (!zoomSettings.containsZoomType(ZoomTypes.NONE) && zoomSettings.isZoomToSelectedCompetitors()) { |
|
| 2231 | + zoomMapToNewBounds(zoomSettings.getNewBounds(this)); |
|
| 2232 | + } |
|
| 2233 | + } |
|
| 2234 | + |
|
| 2235 | + private boolean isShowAnyHelperLines() { |
|
| 2236 | + return settings.getHelpLinesSettings().isShowAnyHelperLines(); |
|
| 2237 | + } |
|
| 2238 | + |
|
| 2239 | + @Override |
|
| 2240 | + public String getLocalizedShortName() { |
|
| 2241 | + return stringMessages.map(); |
|
| 2242 | + } |
|
| 2243 | + |
|
| 2244 | + @Override |
|
| 2245 | + public Widget getEntryWidget() { |
|
| 2246 | + return this; |
|
| 2247 | + } |
|
| 2248 | + |
|
| 2249 | + @Override |
|
| 2250 | + public boolean hasSettings() { |
|
| 2251 | + return true; |
|
| 2252 | + } |
|
| 2253 | + |
|
| 2254 | + @Override |
|
| 2255 | + public SettingsDialogComponent<RaceMapSettings> getSettingsDialogComponent() { |
|
| 2256 | + return new RaceMapSettingsDialogComponent(settings, stringMessages, this.showViewSimulation && this.hasPolar); |
|
| 2257 | + } |
|
| 2258 | + |
|
| 2259 | + @Override |
|
| 2260 | + public void updateSettings(RaceMapSettings newSettings) { |
|
| 2261 | + boolean maneuverTypeSelectionChanged = false; |
|
| 2262 | + boolean requiredRedraw = false; |
|
| 2263 | + for (ManeuverType maneuverType : ManeuverType.values()) { |
|
| 2264 | + if (newSettings.isShowManeuverType(maneuverType) != settings.isShowManeuverType(maneuverType)) { |
|
| 2265 | + maneuverTypeSelectionChanged = true; |
|
| 2266 | + settings.showManeuverType(maneuverType, newSettings.isShowManeuverType(maneuverType)); |
|
| 2267 | + } |
|
| 2268 | + } |
|
| 2269 | + if (maneuverTypeSelectionChanged) { |
|
| 2270 | + if (!(timer.getPlayState() == PlayStates.Playing) && lastManeuverResult != null) { |
|
| 2271 | + removeAllManeuverMarkers(); |
|
| 2272 | + showManeuvers(lastManeuverResult); |
|
| 2273 | + } |
|
| 2274 | + } |
|
| 2275 | + if (newSettings.isShowDouglasPeuckerPoints() != settings.isShowDouglasPeuckerPoints()) { |
|
| 2276 | + if (!(timer.getPlayState() == PlayStates.Playing) && lastDouglasPeuckerResult != null && newSettings.isShowDouglasPeuckerPoints()) { |
|
| 2277 | + settings.setShowDouglasPeuckerPoints(true); |
|
| 2278 | + removeAllMarkDouglasPeuckerpoints(); |
|
| 2279 | + showMarkDouglasPeuckerPoints(lastDouglasPeuckerResult); |
|
| 2280 | + } else if (!newSettings.isShowDouglasPeuckerPoints()) { |
|
| 2281 | + settings.setShowDouglasPeuckerPoints(false); |
|
| 2282 | + removeAllMarkDouglasPeuckerpoints(); |
|
| 2283 | + } |
|
| 2284 | + } |
|
| 2285 | + if (newSettings.getTailLengthInMilliseconds() != settings.getTailLengthInMilliseconds()) { |
|
| 2286 | + settings.setTailLengthInMilliseconds(newSettings.getTailLengthInMilliseconds()); |
|
| 2287 | + requiredRedraw = true; |
|
| 2288 | + } |
|
| 2289 | + if (newSettings.getBuoyZoneRadiusInMeters() != settings.getBuoyZoneRadiusInMeters()) { |
|
| 2290 | + settings.setBuoyZoneRadiusInMeters(newSettings.getBuoyZoneRadiusInMeters()); |
|
| 2291 | + requiredRedraw = true; |
|
| 2292 | + } |
|
| 2293 | + if (newSettings.isShowOnlySelectedCompetitors() != settings.isShowOnlySelectedCompetitors()) { |
|
| 2294 | + settings.setShowOnlySelectedCompetitors(newSettings.isShowOnlySelectedCompetitors()); |
|
| 2295 | + requiredRedraw = true; |
|
| 2296 | + } |
|
| 2297 | + if (newSettings.isShowSelectedCompetitorsInfo() != settings.isShowSelectedCompetitorsInfo()) { |
|
| 2298 | + settings.setShowSelectedCompetitorsInfo(newSettings.isShowSelectedCompetitorsInfo()); |
|
| 2299 | + requiredRedraw = true; |
|
| 2300 | + } |
|
| 2301 | + if (!newSettings.getZoomSettings().equals(settings.getZoomSettings())) { |
|
| 2302 | + settings.setZoomSettings(newSettings.getZoomSettings()); |
|
| 2303 | + if (!settings.getZoomSettings().containsZoomType(ZoomTypes.NONE)) { |
|
| 2304 | + removeTransitions(); |
|
| 2305 | + zoomMapToNewBounds(settings.getZoomSettings().getNewBounds(this)); |
|
| 2306 | + } |
|
| 2307 | + } |
|
| 2308 | + if (!newSettings.getHelpLinesSettings().equals(settings.getHelpLinesSettings())) { |
|
| 2309 | + settings.setHelpLinesSettings(newSettings.getHelpLinesSettings()); |
|
| 2310 | + requiredRedraw = true; |
|
| 2311 | + } |
|
| 2312 | + if (newSettings.isShowWindStreamletOverlay() != settings.isShowWindStreamletOverlay()) { |
|
| 2313 | + settings.setShowWindStreamletOverlay(newSettings.isShowWindStreamletOverlay()); |
|
| 2314 | + streamletOverlay.setVisible(newSettings.isShowWindStreamletOverlay()); |
|
| 2315 | + } |
|
| 2316 | + if (newSettings.isShowWindStreamletColors() != settings.isShowWindStreamletColors()) { |
|
| 2317 | + settings.setShowWindStreamletColors(newSettings.isShowWindStreamletColors()); |
|
| 2318 | + streamletOverlay.setColors(newSettings.isShowWindStreamletColors()); |
|
| 2319 | + } |
|
| 2320 | + if (newSettings.isShowSimulationOverlay() != settings.isShowSimulationOverlay()) { |
|
| 2321 | + settings.setShowSimulationOverlay(newSettings.isShowSimulationOverlay()); |
|
| 2322 | + simulationOverlay.setVisible(newSettings.isShowSimulationOverlay()); |
|
| 2323 | + if (newSettings.isShowSimulationOverlay()) { |
|
| 2324 | + simulationOverlay.updateLeg(Math.max(lastLegNumber,1), true, -1 /* ensure ui-update */); |
|
| 2325 | + } |
|
| 2326 | + } |
|
| 2327 | + if (newSettings.isWindUp() != settings.isWindUp()) { |
|
| 2328 | + settings.setWindUp(newSettings.isWindUp()); |
|
| 2329 | + updateCoordinateSystemFromSettings(); |
|
| 2330 | + requiredRedraw = true; |
|
| 2331 | + } |
|
| 2332 | + if (newSettings.getTransparentHoverlines() != settings.getTransparentHoverlines()) { |
|
| 2333 | + settings.setTransparentHoverlines(newSettings.getTransparentHoverlines()); |
|
| 2334 | + } |
|
| 2335 | + if (newSettings.getHoverlineStrokeWeight() != settings.getHoverlineStrokeWeight()) { |
|
| 2336 | + settings.setHoverlineStrokeWeight(newSettings.getHoverlineStrokeWeight()); |
|
| 2337 | + } |
|
| 2338 | + if (requiredRedraw) { |
|
| 2339 | + redraw(); |
|
| 2340 | + } |
|
| 2341 | + } |
|
| 2342 | + |
|
| 2343 | + public static class BoatsBoundsCalculator extends LatLngBoundsCalculatorForSelected { |
|
| 2344 | + |
|
| 2345 | + @Override |
|
| 2346 | + public LatLngBounds calculateNewBounds(RaceMap forMap) { |
|
| 2347 | + LatLngBounds newBounds = null; |
|
| 2348 | + Iterable<CompetitorDTO> selectedCompetitors = forMap.competitorSelection.getSelectedCompetitors(); |
|
| 2349 | + Iterable<CompetitorDTO> competitors = new ArrayList<CompetitorDTO>(); |
|
| 2350 | + if (selectedCompetitors == null || !selectedCompetitors.iterator().hasNext()) { |
|
| 2351 | + competitors = forMap.getCompetitorsToShow(); |
|
| 2352 | + } else { |
|
| 2353 | + competitors = isZoomOnlyToSelectedCompetitors(forMap) ? selectedCompetitors : forMap.getCompetitorsToShow(); |
|
| 2354 | + } |
|
| 2355 | + for (CompetitorDTO competitor : competitors) { |
|
| 2356 | + try { |
|
| 2357 | + GPSFixDTO competitorFix = forMap.getBoatFix(competitor, forMap.timer.getTime()); |
|
| 2358 | + Position competitorPosition = competitorFix != null ? competitorFix.position : null; |
|
| 2359 | + if (competitorPosition != null) { |
|
| 2360 | + if (newBounds == null) { |
|
| 2361 | + newBounds = BoundsUtil.getAsBounds(forMap.coordinateSystem.toLatLng(competitorPosition)); |
|
| 2362 | + } else { |
|
| 2363 | + newBounds = newBounds.extend(forMap.coordinateSystem.toLatLng(competitorPosition)); |
|
| 2364 | + } |
|
| 2365 | + } |
|
| 2366 | + } catch (IndexOutOfBoundsException e) { |
|
| 2367 | + // TODO can't this be predicted and the exception be avoided in the first place? |
|
| 2368 | + // Catch this in case the competitor has no GPS fixes at the current time (e.g. in race 'Finale 2' of STG) |
|
| 2369 | + } |
|
| 2370 | + } |
|
| 2371 | + return newBounds; |
|
| 2372 | + } |
|
| 2373 | + |
|
| 2374 | + } |
|
| 2375 | + |
|
| 2376 | + public static class TailsBoundsCalculator extends LatLngBoundsCalculatorForSelected { |
|
| 2377 | + @Override |
|
| 2378 | + public LatLngBounds calculateNewBounds(RaceMap racemap) { |
|
| 2379 | + LatLngBounds newBounds = null; |
|
| 2380 | + Iterable<CompetitorDTO> competitors = isZoomOnlyToSelectedCompetitors(racemap) ? racemap.competitorSelection.getSelectedCompetitors() : racemap.getCompetitorsToShow(); |
|
| 2381 | + for (CompetitorDTO competitor : competitors) { |
|
| 2382 | + Polyline tail = racemap.fixesAndTails.getTail(competitor); |
|
| 2383 | + LatLngBounds bounds = null; |
|
| 2384 | + // TODO: Find a replacement for missing Polyline function getBounds() from v2 |
|
| 2385 | + // see also http://stackoverflow.com/questions/3284808/getting-the-bounds-of-a-polyine-in-google-maps-api-v3; |
|
| 2386 | + // optionally, consider providing a bounds cache with two sorted sets that organize the LatLng objects for O(1) bounds calculation and logarithmic add, ideally O(1) remove |
|
| 2387 | + if (tail != null && tail.getPath().getLength() >= 1) { |
|
| 2388 | + bounds = BoundsUtil.getAsBounds(tail.getPath().get(0)); |
|
| 2389 | + for (int i = 1; i < tail.getPath().getLength(); i++) { |
|
| 2390 | + bounds = bounds.extend(tail.getPath().get(i)); |
|
| 2391 | + } |
|
| 2392 | + } |
|
| 2393 | + if (bounds != null) { |
|
| 2394 | + if (newBounds == null) { |
|
| 2395 | + newBounds = bounds; |
|
| 2396 | + } else { |
|
| 2397 | + newBounds = newBounds.union(bounds); |
|
| 2398 | + } |
|
| 2399 | + } |
|
| 2400 | + } |
|
| 2401 | + return newBounds; |
|
| 2402 | + } |
|
| 2403 | + } |
|
| 2404 | + |
|
| 2405 | + public static class CourseMarksBoundsCalculator implements LatLngBoundsCalculator { |
|
| 2406 | + @Override |
|
| 2407 | + public LatLngBounds calculateNewBounds(RaceMap forMap) { |
|
| 2408 | + LatLngBounds newBounds = null; |
|
| 2409 | + Iterable<MarkDTO> marksToZoom = forMap.markDTOs.values(); |
|
| 2410 | + if (marksToZoom != null) { |
|
| 2411 | + for (MarkDTO markDTO : marksToZoom) { |
|
| 2412 | + if (newBounds == null) { |
|
| 2413 | + newBounds = BoundsUtil.getAsBounds(forMap.coordinateSystem.toLatLng(markDTO.position)); |
|
| 2414 | + } else { |
|
| 2415 | + newBounds = newBounds.extend(forMap.coordinateSystem.toLatLng(markDTO.position)); |
|
| 2416 | + } |
|
| 2417 | + } |
|
| 2418 | + } |
|
| 2419 | + return newBounds; |
|
| 2420 | + } |
|
| 2421 | + } |
|
| 2422 | + |
|
| 2423 | + public static class WindSensorsBoundsCalculator implements LatLngBoundsCalculator { |
|
| 2424 | + @Override |
|
| 2425 | + public LatLngBounds calculateNewBounds(RaceMap forMap) { |
|
| 2426 | + LatLngBounds newBounds = null; |
|
| 2427 | + Collection<WindSensorOverlay> marksToZoom = forMap.windSensorOverlays.values(); |
|
| 2428 | + if (marksToZoom != null) { |
|
| 2429 | + for (WindSensorOverlay windSensorOverlay : marksToZoom) { |
|
| 2430 | + if (BoundsUtil.getAsPosition(windSensorOverlay.getLatLngPosition()) != null) { |
|
| 2431 | + LatLngBounds bounds = BoundsUtil.getAsBounds(windSensorOverlay.getLatLngPosition()); |
|
| 2432 | + if (newBounds == null) { |
|
| 2433 | + newBounds = bounds; |
|
| 2434 | + } else { |
|
| 2435 | + newBounds = newBounds.extend(windSensorOverlay.getLatLngPosition()); |
|
| 2436 | + } |
|
| 2437 | + } |
|
| 2438 | + } |
|
| 2439 | + } |
|
| 2440 | + return newBounds; |
|
| 2441 | + } |
|
| 2442 | + } |
|
| 2443 | + |
|
| 2444 | + @Override |
|
| 2445 | + public void initializeData(boolean showMapControls, boolean showHeaderPanel) { |
|
| 2446 | + loadMapsAPIV3(showMapControls, showHeaderPanel); |
|
| 2447 | + } |
|
| 2448 | + |
|
| 2449 | + @Override |
|
| 2450 | + public boolean isDataInitialized() { |
|
| 2451 | + return isMapInitialized; |
|
| 2452 | + } |
|
| 2453 | + |
|
| 2454 | + @Override |
|
| 2455 | + public void onResize() { |
|
| 2456 | + if (map != null) { |
|
| 2457 | + map.triggerResize(); |
|
| 2458 | + zoomMapToNewBounds(settings.getZoomSettings().getNewBounds(RaceMap.this)); |
|
| 2459 | + } |
|
| 2460 | + // Adjust RaceMap headers to avoid overlapping based on the RaceMap width |
|
| 2461 | + boolean isCompactHeader = this.getOffsetWidth() <= 600; |
|
| 2462 | + getLeftHeaderPanel().setStyleName(COMPACT_HEADER_STYLE, isCompactHeader); |
|
| 2463 | + getRightHeaderPanel().setStyleName(COMPACT_HEADER_STYLE, isCompactHeader); |
|
| 2464 | + |
|
| 2465 | + // Adjust combined wind and true north indicator panel indent, based on the RaceMap height |
|
| 2466 | + if (combinedWindPanel.getParent() != null && trueNorthIndicatorPanel.getParent() != null) { |
|
| 2467 | + this.adjustLeftControlsIndent(); |
|
| 2468 | + } |
|
| 2469 | + } |
|
| 2470 | + |
|
| 2471 | + private void adjustLeftControlsIndent() { |
|
| 2472 | + combinedWindPanel.getParent().setStyleName("CombinedWindPanelParentDiv"); |
|
| 2473 | + trueNorthIndicatorPanel.getParent().setStyleName("TrueNorthIndicatorPanelParentDiv"); |
|
| 2474 | + String leftControlsIndentStyle = getLeftControlsIndentStyle(); |
|
| 2475 | + if (leftControlsIndentStyle != null) { |
|
| 2476 | + combinedWindPanel.getParent().addStyleName(leftControlsIndentStyle); |
|
| 2477 | + trueNorthIndicatorPanel.getParent().addStyleName(leftControlsIndentStyle); |
|
| 2478 | + } |
|
| 2479 | + } |
|
| 2480 | + |
|
| 2481 | + @Override |
|
| 2482 | + public void competitorsListChanged(Iterable<CompetitorDTO> competitors) { |
|
| 2483 | + timeChanged(timer.getTime(), null); |
|
| 2484 | + } |
|
| 2485 | + |
|
| 2486 | + @Override |
|
| 2487 | + public void filteredCompetitorsListChanged(Iterable<CompetitorDTO> filteredCompetitors) { |
|
| 2488 | + timeChanged(timer.getTime(), null); |
|
| 2489 | + } |
|
| 2490 | + |
|
| 2491 | + @Override |
|
| 2492 | + public void filterChanged(FilterSet<CompetitorDTO, ? extends Filter<CompetitorDTO>> oldFilterSet, |
|
| 2493 | + FilterSet<CompetitorDTO, ? extends Filter<CompetitorDTO>> newFilterSet) { |
|
| 2494 | + // nothing to do; if the list of filtered competitors has changed, a separate call to filteredCompetitorsListChanged will occur |
|
| 2495 | + } |
|
| 2496 | + |
|
| 2497 | + @Override |
|
| 2498 | + public PolylineOptions createTailStyle(CompetitorDTO competitor, boolean isHighlighted) { |
|
| 2499 | + PolylineOptions options = PolylineOptions.newInstance(); |
|
| 2500 | + options.setClickable(true); |
|
| 2501 | + options.setGeodesic(true); |
|
| 2502 | + options.setStrokeOpacity(1.0); |
|
| 2503 | + boolean noCompetitorSelected = Util.isEmpty(competitorSelection.getSelectedCompetitors()); |
|
| 2504 | + if (isHighlighted || noCompetitorSelected) { |
|
| 2505 | + options.setStrokeColor(competitorSelection.getColor(competitor, raceIdentifier).getAsHtml()); |
|
| 2506 | + } else { |
|
| 2507 | + options.setStrokeColor(CssColor.make(200, 200, 200).toString()); |
|
| 2508 | + } |
|
| 2509 | + if (isHighlighted) { |
|
| 2510 | + options.setStrokeWeight(2); |
|
| 2511 | + } else { |
|
| 2512 | + options.setStrokeWeight(1); |
|
| 2513 | + } |
|
| 2514 | + options.setZindex(RaceMapOverlaysZIndexes.BOATTAILS_ZINDEX); |
|
| 2515 | + return options; |
|
| 2516 | + } |
|
| 2517 | + |
|
| 2518 | + @Override |
|
| 2519 | + public Polyline createTail(final CompetitorDTO competitor, List<LatLng> points) { |
|
| 2520 | + PolylineOptions options = createTailStyle(competitor, displayHighlighted(competitor)); |
|
| 2521 | + Polyline result = Polyline.newInstance(options); |
|
| 2522 | + MVCArray<LatLng> pointsAsArray = MVCArray.newInstance(points.toArray(new LatLng[0])); |
|
| 2523 | + result.setPath(pointsAsArray); |
|
| 2524 | + result.setMap(map); |
|
| 2525 | + Hoverline resultHoverline = new Hoverline(result, options, this); |
|
| 2526 | + final ClickMapHandler clickHandler = new ClickMapHandler() { |
|
| 2527 | + @Override |
|
| 2528 | + public void onEvent(ClickMapEvent event) { |
|
| 2529 | + showCompetitorInfoWindow(competitor, event.getMouseEvent().getLatLng()); |
|
| 2530 | + } |
|
| 2531 | + }; |
|
| 2532 | + result.addClickHandler(clickHandler); |
|
| 2533 | + resultHoverline.addClickHandler(clickHandler); |
|
| 2534 | + result.addMouseOverHandler(new MouseOverMapHandler() { |
|
| 2535 | + @Override |
|
| 2536 | + public void onEvent(MouseOverMapEvent event) { |
|
| 2537 | + map.setTitle(competitor.getSailID() + ", " + competitor.getName()); |
|
| 2538 | + } |
|
| 2539 | + }); |
|
| 2540 | + resultHoverline.addMouseOutMoveHandler(new MouseOutMapHandler() { |
|
| 2541 | + @Override |
|
| 2542 | + public void onEvent(MouseOutMapEvent event) { |
|
| 2543 | + map.setTitle(""); |
|
| 2544 | + } |
|
| 2545 | + }); |
|
| 2546 | + return result; |
|
| 2547 | + } |
|
| 2548 | + |
|
| 2549 | + @Override |
|
| 2550 | + public Integer getRank(CompetitorDTO competitor) { |
|
| 2551 | + final Integer result; |
|
| 2552 | + QuickRankDTO quickRank = quickRanks.get(competitor); |
|
| 2553 | + if (quickRank != null) { |
|
| 2554 | + result = quickRank.rank; |
|
| 2555 | + } else { |
|
| 2556 | + result = null; |
|
| 2557 | + } |
|
| 2558 | + return result; |
|
| 2559 | + } |
|
| 2560 | + |
|
| 2561 | + private Image createSAPLogo() { |
|
| 2562 | + ImageResource sapLogoResource = resources.sapLogoOverlay(); |
|
| 2563 | + Image sapLogo = new Image(sapLogoResource); |
|
| 2564 | + sapLogo.addClickHandler(new ClickHandler() { |
|
| 2565 | + @Override |
|
| 2566 | + public void onClick(ClickEvent event) { |
|
| 2567 | + Window.open("http://www.sap.com", "_blank", null); |
|
| 2568 | + } |
|
| 2569 | + }); |
|
| 2570 | + sapLogo.setStyleName("raceBoard-Logo"); |
|
| 2571 | + return sapLogo; |
|
| 2572 | + } |
|
| 2573 | + |
|
| 2574 | + @Override |
|
| 2575 | + public String getDependentCssClassName() { |
|
| 2576 | + return "raceMap"; |
|
| 2577 | + } |
|
| 2578 | + |
|
| 2579 | + /** |
|
| 2580 | + * The default zoom bounds are defined by the boats |
|
| 2581 | + */ |
|
| 2582 | + private LatLngBounds getDefaultZoomBounds() { |
|
| 2583 | + return new BoatsBoundsCalculator().calculateNewBounds(RaceMap.this); |
|
| 2584 | + } |
|
| 2585 | + |
|
| 2586 | + private MapOptions getMapOptions(final boolean showMapControls, boolean windUp) { |
|
| 2587 | + MapOptions mapOptions = MapOptions.newInstance(); |
|
| 2588 | + mapOptions.setScrollWheel(true); |
|
| 2589 | + mapOptions.setMapTypeControl(showMapControls && !windUp); |
|
| 2590 | + mapOptions.setPanControl(showMapControls); |
|
| 2591 | + mapOptions.setZoomControl(showMapControls); |
|
| 2592 | + mapOptions.setScaleControl(true); |
|
| 2593 | + if (windUp) { |
|
| 2594 | + mapOptions.setMinZoom(8); |
|
| 2595 | + } else { |
|
| 2596 | + mapOptions.setMinZoom(0); |
|
| 2597 | + } |
|
| 2598 | + MapTypeStyle[] mapTypeStyles = new MapTypeStyle[4]; |
|
| 2599 | + |
|
| 2600 | + // hide all transit lines including ferry lines |
|
| 2601 | + mapTypeStyles[0] = GoogleMapStyleHelper.createHiddenStyle(MapTypeStyleFeatureType.TRANSIT); |
|
| 2602 | + // hide points of interest |
|
| 2603 | + mapTypeStyles[1] = GoogleMapStyleHelper.createHiddenStyle(MapTypeStyleFeatureType.POI); |
|
| 2604 | + // simplify road display |
|
| 2605 | + mapTypeStyles[2] = GoogleMapStyleHelper.createSimplifiedStyle(MapTypeStyleFeatureType.ROAD); |
|
| 2606 | + // set water color |
|
| 2607 | + // To play with the styles, check out http://gmaps-samples-v3.googlecode.com/svn/trunk/styledmaps/wizard/index.html. |
|
| 2608 | + // To convert an RGB color into the strange hue/saturation/lightness model used by the Google Map use |
|
| 2609 | + // http://software.stadtwerk.org/google_maps_colorizr/#water/all/123456/. |
|
| 2610 | + mapTypeStyles[3] = GoogleMapStyleHelper.createColorStyle(MapTypeStyleFeatureType.WATER, new RGBColor(0, 136, 255), 0, -70); |
|
| 2611 | + |
|
| 2612 | + MapTypeControlOptions mapTypeControlOptions = MapTypeControlOptions.newInstance(); |
|
| 2613 | + mapTypeControlOptions.setPosition(ControlPosition.BOTTOM_RIGHT); |
|
| 2614 | + mapOptions.setMapTypeControlOptions(mapTypeControlOptions); |
|
| 2615 | + |
|
| 2616 | + mapOptions.setMapTypeStyles(mapTypeStyles); |
|
| 2617 | + // no need to try to position the scale control; it always ends up at the right bottom corner |
|
| 2618 | + mapOptions.setStreetViewControl(false); |
|
| 2619 | + if (showMapControls) { |
|
| 2620 | + ZoomControlOptions zoomControlOptions = ZoomControlOptions.newInstance(); |
|
| 2621 | + zoomControlOptions.setPosition(ControlPosition.RIGHT_TOP); |
|
| 2622 | + mapOptions.setZoomControlOptions(zoomControlOptions); |
|
| 2623 | + PanControlOptions panControlOptions = PanControlOptions.newInstance(); |
|
| 2624 | + panControlOptions.setPosition(ControlPosition.RIGHT_TOP); |
|
| 2625 | + mapOptions.setPanControlOptions(panControlOptions); |
|
| 2626 | + } |
|
| 2627 | + return mapOptions; |
|
| 2628 | + } |
|
| 2629 | + |
|
| 2630 | + /** |
|
| 2631 | + * @return CSS style name to adjust the indent of left controls (combined wind and true north indicator). |
|
| 2632 | + */ |
|
| 2633 | + protected String getLeftControlsIndentStyle() { |
|
| 2634 | + return null; |
|
| 2635 | + } |
|
| 2636 | +} |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettings.java
| ... | ... | @@ -19,6 +19,10 @@ public class RaceMapSettings extends AbstractSettings { |
| 19 | 19 | private RaceMapZoomSettings zoomSettings;
|
| 20 | 20 | |
| 21 | 21 | private RaceMapHelpLinesSettings helpLinesSettings;
|
| 22 | +
|
|
| 23 | + private boolean transparentHoverlines = true;
|
|
| 24 | +
|
|
| 25 | + private int hoverlineStrokeWeight = 10;
|
|
| 22 | 26 | |
| 23 | 27 | private long tailLengthInMilliseconds = 100000l;
|
| 24 | 28 | |
| ... | ... | @@ -55,6 +59,8 @@ public class RaceMapSettings extends AbstractSettings { |
| 55 | 59 | public RaceMapSettings(RaceMapSettings settings) {
|
| 56 | 60 | this.buoyZoneRadiusInMeters = settings.buoyZoneRadiusInMeters;
|
| 57 | 61 | this.helpLinesSettings = new RaceMapHelpLinesSettings(settings.getHelpLinesSettings().getVisibleHelpLineTypes());
|
| 62 | + this.transparentHoverlines = settings.transparentHoverlines;
|
|
| 63 | + this.hoverlineStrokeWeight = settings.hoverlineStrokeWeight;
|
|
| 58 | 64 | this.maneuverTypesToShow = settings.maneuverTypesToShow;
|
| 59 | 65 | this.showDouglasPeuckerPoints = settings.showDouglasPeuckerPoints;
|
| 60 | 66 | this.showOnlySelectedCompetitors = settings.showOnlySelectedCompetitors;
|
| ... | ... | @@ -153,6 +159,22 @@ public class RaceMapSettings extends AbstractSettings { |
| 153 | 159 | public void setHelpLinesSettings(RaceMapHelpLinesSettings helpLinesSettings) {
|
| 154 | 160 | this.helpLinesSettings = helpLinesSettings;
|
| 155 | 161 | }
|
| 162 | +
|
|
| 163 | + public boolean getTransparentHoverlines() {
|
|
| 164 | + return this.transparentHoverlines;
|
|
| 165 | + }
|
|
| 166 | +
|
|
| 167 | + public void setTransparentHoverlines(boolean transparentHoverlines) {
|
|
| 168 | + this.transparentHoverlines = transparentHoverlines;
|
|
| 169 | + }
|
|
| 170 | +
|
|
| 171 | + public int getHoverlineStrokeWeight() {
|
|
| 172 | + return this.hoverlineStrokeWeight;
|
|
| 173 | + }
|
|
| 174 | +
|
|
| 175 | + public void setHoverlineStrokeWeight(int hoverlineStrokeWeight) {
|
|
| 176 | + this.hoverlineStrokeWeight = hoverlineStrokeWeight;
|
|
| 177 | + }
|
|
| 156 | 178 | |
| 157 | 179 | public boolean isShowSelectedCompetitorsInfo() {
|
| 158 | 180 | return showSelectedCompetitorsInfo;
|
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettingsDialogComponent.java
| ... | ... | @@ -24,11 +24,14 @@ import com.sap.sailing.gwt.ui.client.StringMessages; |
| 24 | 24 | import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapHelpLinesSettings.HelpLineTypes;
|
| 25 | 25 | import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings.ZoomTypes;
|
| 26 | 26 | import com.sap.sse.common.Util;
|
| 27 | +import com.sap.sse.gwt.client.controls.IntegerBox;
|
|
| 27 | 28 | import com.sap.sse.gwt.client.dialog.DataEntryDialog;
|
| 28 | 29 | import com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator;
|
| 29 | 30 | import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent;
|
| 30 | 31 | |
| 31 | 32 | public class RaceMapSettingsDialogComponent implements SettingsDialogComponent<RaceMapSettings> {
|
| 33 | + private static final int MAX_BUOY_ZONE_RADIUS_IN_METERS = 100;
|
|
| 34 | + private static final int MAX_STROKE_WEIGHT = 33;
|
|
| 32 | 35 | //Initializing the lists to prevent a null pointer exception in the first validation call
|
| 33 | 36 | private List<Util.Pair<CheckBox, ManeuverType>> checkboxAndManeuverType = new ArrayList<Util.Pair<CheckBox, ManeuverType>>();
|
| 34 | 37 | private List<Util.Pair<CheckBox, ZoomTypes>> checkboxAndZoomType = new ArrayList<Util.Pair<CheckBox,ZoomTypes>>();
|
| ... | ... | @@ -43,6 +46,8 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent<R |
| 43 | 46 | private CheckBox showSelectedCompetitorsInfoCheckBox;
|
| 44 | 47 | private LongBox tailLengthBox;
|
| 45 | 48 | private DoubleBox buoyZoneRadiusBox;
|
| 49 | + private CheckBox transparentHoverlines;
|
|
| 50 | + private IntegerBox hoverlineStrokeWeight;
|
|
| 46 | 51 | private boolean showViewSimulation;
|
| 47 | 52 | |
| 48 | 53 | private final StringMessages stringMessages;
|
| ... | ... | @@ -211,6 +216,17 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent<R |
| 211 | 216 | vp.add(createHelpLineCheckBox(dialog, HelpLineTypes.STARTLINETOFIRSTMARKTRIANGLE));
|
| 212 | 217 | vp.add(createHelpLineCheckBox(dialog, HelpLineTypes.COURSEGEOMETRY));
|
| 213 | 218 | |
| 219 | + transparentHoverlines = dialog.createCheckbox(stringMessages.transparentBufferLineOnHover());
|
|
| 220 | + transparentHoverlines.setValue(initialSettings.getTransparentHoverlines());
|
|
| 221 | + vp.add(transparentHoverlines);
|
|
| 222 | +
|
|
| 223 | + HorizontalPanel hoverlineStrokeWeightPanel = new HorizontalPanel();
|
|
| 224 | + Label hoverlineStrokeWeightLabel = new Label(stringMessages.bufferLineStrokeWeight() + ":");
|
|
| 225 | + hoverlineStrokeWeightPanel.add(hoverlineStrokeWeightLabel);
|
|
| 226 | + hoverlineStrokeWeight = dialog.createIntegerBox(initialSettings.getHoverlineStrokeWeight(), 3);
|
|
| 227 | + hoverlineStrokeWeightPanel.add(hoverlineStrokeWeight);
|
|
| 228 | + vp.add(hoverlineStrokeWeightPanel);
|
|
| 229 | +
|
|
| 214 | 230 | return vp;
|
| 215 | 231 | }
|
| 216 | 232 | |
| ... | ... | @@ -267,6 +283,8 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent<R |
| 267 | 283 | result.setBuoyZoneRadiusInMeters(value);
|
| 268 | 284 | }
|
| 269 | 285 | }
|
| 286 | + result.setTransparentHoverlines(transparentHoverlines.getValue());
|
|
| 287 | + result.setHoverlineStrokeWeight(hoverlineStrokeWeight.getValue());
|
|
| 270 | 288 | return result;
|
| 271 | 289 | }
|
| 272 | 290 | |
| ... | ... | @@ -304,8 +322,10 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent<R |
| 304 | 322 | if (valueToValidate.getHelpLinesSettings().isVisible(HelpLineTypes.BOATTAILS) && valueToValidate.getTailLengthInMilliseconds() <= 0) {
|
| 305 | 323 | errorMessage = stringMessages.tailLengthMustBePositive();
|
| 306 | 324 | } else if (valueToValidate.getHelpLinesSettings().isVisible(HelpLineTypes.BUOYZONE)
|
| 307 | - && (valueToValidate.getBuoyZoneRadiusInMeters() < 0.0 || valueToValidate.getBuoyZoneRadiusInMeters() > 100.0)) {
|
|
| 308 | - errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.buoyZone(), "0", "100");
|
|
| 325 | + && (valueToValidate.getBuoyZoneRadiusInMeters() < 0 || valueToValidate.getBuoyZoneRadiusInMeters() > MAX_BUOY_ZONE_RADIUS_IN_METERS)) {
|
|
| 326 | + errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.buoyZone(), 0, 100);
|
|
| 327 | + } else if (valueToValidate.getHoverlineStrokeWeight() < 0 || valueToValidate.getHoverlineStrokeWeight() > MAX_STROKE_WEIGHT) {
|
|
| 328 | + errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.bufferLineStrokeWeight(), 0, MAX_STROKE_WEIGHT);
|
|
| 309 | 329 | }
|
| 310 | 330 | return errorMessage;
|
| 311 | 331 | }
|
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/CompetitorFilter.css
| ... | ... | @@ -1,5 +1,6 @@ |
| 1 | 1 | .competitorFilterContainer { |
| 2 | - width: 400px; |
|
| 2 | + max-width: 400px; |
|
| 3 | + width: 100%; |
|
| 3 | 4 | height: 2.14em; |
| 4 | 5 | background-color: #FFFFFF; |
| 5 | 6 | margin-left: 0px; |
| ... | ... | @@ -22,7 +23,7 @@ |
| 22 | 23 | .searchInput { |
| 23 | 24 | font-size: .933333333333333em; |
| 24 | 25 | height: 2.142857142857143em; |
| 25 | - width: 280px; |
|
| 26 | + width: 70%; |
|
| 26 | 27 | border: none; |
| 27 | 28 | background-color: #eaeaea; |
| 28 | 29 | padding: 0; |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java
| ... | ... | @@ -258,7 +258,7 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint { |
| 258 | 258 | result = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, |
| 259 | 259 | /* overallDetails */ overallDetails, null, |
| 260 | 260 | /* autoExpandFirstRace */false, refreshIntervalMillis, numberOfLastRacesToShow, |
| 261 | - raceColumnSelectionStrategy); |
|
| 261 | + raceColumnSelectionStrategy, /* showCompetitorSailIdColumns */ true, /*showCompetitorFullNameColumn*/ true); |
|
| 262 | 262 | } |
| 263 | 263 | return result; |
| 264 | 264 | } |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java
| ... | ... | @@ -348,7 +348,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS |
| 348 | 348 | private Element elementToBlur;
|
| 349 | 349 | private boolean showSelectionCheckbox;
|
| 350 | 350 | |
| 351 | - private List<LeaderboardUpdateListener> leaderboardUpdateListener;
|
|
| 351 | + private final List<LeaderboardUpdateListener> leaderboardUpdateListener; |
|
| 352 | 352 | |
| 353 | 353 | private boolean initialCompetitorFilterHasBeenApplied = false;
|
| 354 | 354 | private final boolean showCompetitorFilterStatus;
|
| ... | ... | @@ -673,6 +673,13 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS |
| 673 | 673 | final String twoLetterIsoCountryCode = competitor.getTwoLetterIsoCountryCode();
|
| 674 | 674 | final String flagImageURL = competitor.getFlagImageURL();
|
| 675 | 675 | |
| 676 | + boolean showBoatColor = !isShowCompetitorFullName() && LeaderboardPanel.this.isEmbedded && preSelectedRace != null; |
|
| 677 | + if (showBoatColor) { |
|
| 678 | + String competitorColor = LeaderboardPanel.this.competitorSelectionProvider.getColor( |
|
| 679 | + competitorFetcher.getCompetitor(object), LeaderboardPanel.this.preSelectedRace).getAsHtml(); |
|
| 680 | + sb.appendHtmlConstant("<div style=\"border-bottom: 2px solid " + competitorColor + ";\">"); |
|
| 681 | + } |
|
| 682 | + |
|
| 676 | 683 | if (flagImageURL != null && !flagImageURL.isEmpty()) {
|
| 677 | 684 | sb.appendHtmlConstant("<img src=\"" + flagImageURL + "\" width=\"18px\" height=\"12px\" title=\"" + competitor.getName() + "\"/>");
|
| 678 | 685 | sb.appendHtmlConstant(" ");
|
| ... | ... | @@ -689,6 +696,9 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS |
| 689 | 696 | }
|
| 690 | 697 | }
|
| 691 | 698 | sb.appendEscaped(competitor.getSailID());
|
| 699 | + if (showBoatColor) { |
|
| 700 | + sb.appendHtmlConstant("</div>"); |
|
| 701 | + } |
|
| 692 | 702 | }
|
| 693 | 703 | |
| 694 | 704 | @Override
|
| ... | ... | @@ -2403,27 +2413,8 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS |
| 2403 | 2413 | scoreCorrectionLastUpdateTimeLabel.setText("");
|
| 2404 | 2414 | }
|
| 2405 | 2415 | |
| 2406 | - List<com.sap.sse.common.Util.Pair<RaceColumnDTO, FleetDTO>> liveRaces = leaderboard.getLiveRaces(timer.getLiveTimePointInMillis());
|
|
| 2407 | - boolean hasLiveRace = !liveRaces.isEmpty();
|
|
| 2416 | + boolean hasLiveRace = !leaderboard.getLiveRaces(timer.getLiveTimePointInMillis()).isEmpty(); |
|
| 2408 | 2417 | liveRaceLabel.setText(hasLiveRace ? getLiveRacesText() : "");
|
| 2409 | - if (hasLiveRace) {
|
|
| 2410 | - String liveRaceText = "";
|
|
| 2411 | - if(liveRaces.size() == 1) {
|
|
| 2412 | - com.sap.sse.common.Util.Pair<RaceColumnDTO, FleetDTO> liveRace = liveRaces.get(0);
|
|
| 2413 | - liveRaceText = stringMessages.raceIsLive("'" + liveRace.getA().getRaceColumnName() + "'");
|
|
| 2414 | - } else {
|
|
| 2415 | - String raceNames = "";
|
|
| 2416 | - for (com.sap.sse.common.Util.Pair<RaceColumnDTO, FleetDTO> liveRace : liveRaces) {
|
|
| 2417 | - raceNames += "'" + liveRace.getA().getRaceColumnName() + "', ";
|
|
| 2418 | - }
|
|
| 2419 | - // remove last ", "
|
|
| 2420 | - raceNames = raceNames.substring(0, raceNames.length() - 2);
|
|
| 2421 | - liveRaceText = stringMessages.racesAreLive(raceNames);
|
|
| 2422 | - }
|
|
| 2423 | - liveRaceLabel.setText(liveRaceText);
|
|
| 2424 | - } else {
|
|
| 2425 | - liveRaceLabel.setText("");
|
|
| 2426 | - }
|
|
| 2427 | 2418 | scoreCorrectionLastUpdateTimeLabel.setVisible(!hasLiveRace);
|
| 2428 | 2419 | liveRaceLabel.setVisible(hasLiveRace);
|
| 2429 | 2420 | }
|
| ... | ... | @@ -3191,7 +3182,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS |
| 3191 | 3182 | if (timer != null) {
|
| 3192 | 3183 | timer.removeTimeListener(this);
|
| 3193 | 3184 | }
|
| 3194 | - if(leaderboardUpdateListener != null) {
|
|
| 3185 | + if (leaderboardUpdateListener != null) { |
|
| 3195 | 3186 | leaderboardUpdateListener.clear();
|
| 3196 | 3187 | }
|
| 3197 | 3188 | }
|
| ... | ... | @@ -3250,18 +3241,20 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS |
| 3250 | 3241 | public String getLiveRacesText() {
|
| 3251 | 3242 | String result = "";
|
| 3252 | 3243 | List<com.sap.sse.common.Util.Pair<RaceColumnDTO, FleetDTO>> liveRaces = leaderboard.getLiveRaces(timer.getLiveTimePointInMillis());
|
| 3244 | + boolean isMeta = leaderboard.type.isMetaLeaderboard(); |
|
| 3253 | 3245 | if (!liveRaces.isEmpty()) {
|
| 3254 | - if(liveRaces.size() == 1) {
|
|
| 3246 | + if (liveRaces.size() == 1) { |
|
| 3255 | 3247 | com.sap.sse.common.Util.Pair<RaceColumnDTO, FleetDTO> liveRace = liveRaces.get(0);
|
| 3256 | - result = stringMessages.raceIsLive("'" + liveRace.getA().getRaceColumnName() + "'");
|
|
| 3248 | + String text = "'" + liveRace.getA().getRaceColumnName() + "'"; |
|
| 3249 | + result = isMeta ? stringMessages.regattaIsLive(text) : stringMessages.raceIsLive(text); |
|
| 3257 | 3250 | } else {
|
| 3258 | - String raceNames = "";
|
|
| 3251 | + String names = ""; |
|
| 3259 | 3252 | for (com.sap.sse.common.Util.Pair<RaceColumnDTO, FleetDTO> liveRace : liveRaces) {
|
| 3260 | - raceNames += "'" + liveRace.getA().getRaceColumnName() + "', ";
|
|
| 3253 | + names += "'" + liveRace.getA().getRaceColumnName() + "', "; |
|
| 3261 | 3254 | }
|
| 3262 | 3255 | // remove last ", "
|
| 3263 | - raceNames = raceNames.substring(0, raceNames.length() - 2);
|
|
| 3264 | - result = stringMessages.racesAreLive(raceNames);
|
|
| 3256 | + names = names.substring(0, names.length() - 2); |
|
| 3257 | + result = isMeta ? stringMessages.regattasAreLive(names) : stringMessages.racesAreLive(names); |
|
| 3265 | 3258 | }
|
| 3266 | 3259 | }
|
| 3267 | 3260 | return result;
|
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsFactory.java
| ... | ... | @@ -78,11 +78,11 @@ public class LeaderboardSettingsFactory { |
| 78 | 78 | /* refresh interval */ null, /* name of race to sort */ nameOfRaceToSort,
|
| 79 | 79 | /* ascending */ true, /* updateUponPlayStateChange */ true, raceColumnSelection.getType(),
|
| 80 | 80 | /*showAddedScores*/ false, /*showOverallRacesCompleted*/ false,
|
| 81 | - /*showCompetitorSailIdColumns*/showCompetitorSailIdColumn, /*showCompetitorFullNameColumn*/showCompetitorNameColumn);
|
|
| 81 | + showCompetitorSailIdColumn, showCompetitorNameColumn);
|
|
| 82 | 82 | break;
|
| 83 | 83 | case Replay:
|
| 84 | 84 | settings = createNewDefaultSettings(namesOfRaceColumnsToShow, namesOfRacesToShow, nameOfRaceToSort, /* autoExpandFirstRace */
|
| 85 | - nameOfRaceColumnToShow != null, showRegattaRank);
|
|
| 85 | + nameOfRaceColumnToShow != null, showRegattaRank, showCompetitorSailIdColumn, showCompetitorNameColumn);
|
|
| 86 | 86 | break;
|
| 87 | 87 | }
|
| 88 | 88 | return settings;
|
| ... | ... | @@ -127,28 +127,32 @@ public class LeaderboardSettingsFactory { |
| 127 | 127 | * such a meta leaderboard exists; if multiple such meta leaderboards exist, they are all shown
|
| 128 | 128 | */
|
| 129 | 129 | public LeaderboardSettings createNewDefaultSettings(List<String> namesOfRaceColumnsToShow,
|
| 130 | - List<String> namesOfRacesToShow, String nameOfRaceToSort, boolean autoExpandPreSelectedRace, Boolean showRegattaRank) {
|
|
| 130 | + List<String> namesOfRacesToShow, String nameOfRaceToSort, boolean autoExpandPreSelectedRace, Boolean showRegattaRank,
|
|
| 131 | + boolean showCompetitorSailIdColumn, boolean showCompetitorFullNameColumn) {
|
|
| 131 | 132 | final List<DetailType> overallDetails = createOverallDetailsForShowRegattaRank(showRegattaRank);
|
| 132 | 133 | return createNewDefaultSettings(namesOfRaceColumnsToShow, namesOfRacesToShow, overallDetails,
|
| 133 | - nameOfRaceToSort, autoExpandPreSelectedRace, /* refreshIntervalMillis */ null);
|
|
| 134 | + nameOfRaceToSort, autoExpandPreSelectedRace, /* refreshIntervalMillis */ null,
|
|
| 135 | + showCompetitorSailIdColumn, showCompetitorFullNameColumn);
|
|
| 134 | 136 | }
|
| 135 | 137 | |
| 136 | 138 | /**
|
| 137 | - * Like {@link #createNewDefaultSettings(List, List, String, boolean, boolean)}, only that an additional refresh interval for auto-refresh
|
|
| 139 | + * Like {@link #createNewDefaultSettings(List, List, String, boolean, boolean, boolean, boolean)}, only that an additional refresh interval for auto-refresh
|
|
| 138 | 140 | * may be specified; if <code>null</code>, no auto-refresh shall be performed
|
| 139 | 141 | */
|
| 140 | - public LeaderboardSettings createNewDefaultSettings(List<String> namesOfRaceColumnsToShow,
|
|
| 142 | + private LeaderboardSettings createNewDefaultSettings(List<String> namesOfRaceColumnsToShow,
|
|
| 141 | 143 | List<String> namesOfRacesToShow, List<DetailType> overallDetailsToShow, String nameOfRaceToSort,
|
| 142 | - boolean autoExpandPreSelectedRace, Long refreshIntervalMillis) {
|
|
| 144 | + boolean autoExpandPreSelectedRace, Long refreshIntervalMillis, boolean showCompetitorSailIdColumns, boolean showCompetitorFullNameColumn) {
|
|
| 143 | 145 | return createNewDefaultSettings(namesOfRaceColumnsToShow, namesOfRacesToShow, overallDetailsToShow,
|
| 144 | 146 | nameOfRaceToSort, autoExpandPreSelectedRace, refreshIntervalMillis,
|
| 145 | - /* numberOfLastRacesToShow */null, /* raceColumnSelectionStrategy */ RaceColumnSelectionStrategies.EXPLICIT);
|
|
| 147 | + /* numberOfLastRacesToShow */null, /* raceColumnSelectionStrategy */ RaceColumnSelectionStrategies.EXPLICIT,
|
|
| 148 | + showCompetitorSailIdColumns, showCompetitorFullNameColumn);
|
|
| 146 | 149 | }
|
| 147 | 150 | |
| 148 | 151 | public LeaderboardSettings createNewDefaultSettings(List<String> namesOfRaceColumnsToShow,
|
| 149 | 152 | List<String> namesOfRacesToShow, List<DetailType> overallDetailsToShow, String nameOfRaceToSort,
|
| 150 | 153 | boolean autoExpandPreSelectedRace, Long refreshIntervalMillis, Integer numberOfLastRacesToShow,
|
| 151 | - RaceColumnSelectionStrategies raceColumnSelectionStrategy) {
|
|
| 154 | + RaceColumnSelectionStrategies raceColumnSelectionStrategy, boolean showCompetitorSailIdColumns,
|
|
| 155 | + boolean showCompetitorFullNameColumn) {
|
|
| 152 | 156 | if (namesOfRaceColumnsToShow != null && namesOfRacesToShow != null) {
|
| 153 | 157 | throw new IllegalArgumentException("Can specify race columns either by column or by race name, not both");
|
| 154 | 158 | }
|
| ... | ... | @@ -168,7 +172,7 @@ public class LeaderboardSettingsFactory { |
| 168 | 172 | autoExpandPreSelectedRace, refreshIntervalMillis, /* sort by column */ nameOfRaceToSort,
|
| 169 | 173 | /* ascending */ true, /* updateUponPlayStateChange */ true, raceColumnSelectionStrategy,
|
| 170 | 174 | /*showAddedScores*/ false, /*showOverallRacesCompleted*/ false,
|
| 171 | - /*showCompetitorSailIdColumns*/true, /*showCompetitorFullNameColumn*/true);
|
|
| 175 | + showCompetitorSailIdColumns, showCompetitorFullNameColumn);
|
|
| 172 | 176 | }
|
| 173 | 177 | |
| 174 | 178 | public LeaderboardSettings mergeLeaderboardSettings(LeaderboardSettings settingsWithRaceSelection, LeaderboardSettings settingsWithDetails) {
|
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlConfigurationDialog.java
| ... | ... | @@ -106,7 +106,7 @@ public class LeaderboardUrlConfigurationDialog extends SettingsDialog<Leaderboar |
| 106 | 106 | }
|
| 107 | 107 | LeaderboardSettings settings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(
|
| 108 | 108 | namesOfRaceColumnsToShow, /* namesOfRacesToShow */null, /* nameOfRaceToSort */null, /* autoExpandPreSelectedRace */
|
| 109 | - false, /* showRegattaRank */ true);
|
|
| 109 | + false, /* showRegattaRank */ true, /* showCompetitorSailIdColumn */ true, /* showCompetitorFullNameColumn */ true);
|
|
| 110 | 110 | leaderboardSettingsDialogComponent = new LeaderboardSettingsDialogComponent(settings.getManeuverDetailsToShow(),
|
| 111 | 111 | settings.getLegDetailsToShow(), settings.getRaceDetailsToShow(), settings.getOverallDetailsToShow(), raceList,
|
| 112 | 112 | /* select all races by default */ raceList, new ExplicitRaceColumnSelection(),
|
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MultiLeaderboardPanel.java
| ... | ... | @@ -91,7 +91,7 @@ public class MultiLeaderboardPanel extends AbstractLazyComponent<LeaderboardSett |
| 91 | 91 | private LeaderboardSettings getOrCreateLeaderboardSettings(String leaderboardName, LeaderboardSettings currentLeaderboardSettings) { |
| 92 | 92 | LeaderboardSettings newLeaderboardSettings = leaderboardNamesAndSettings.get(leaderboardName); |
| 93 | 93 | if(newLeaderboardSettings == null) { |
| 94 | - newLeaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, false, /* showRegattaRank */ true); |
|
| 94 | + newLeaderboardSettings = LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(null, null, null, false, /* showRegattaRank */ true, /* showCompetitorSailIdColumn */ true, /* showCompetitorFullNameColumn */ true); |
|
| 95 | 95 | } |
| 96 | 96 | if(currentLeaderboardSettings != null) { |
| 97 | 97 | newLeaderboardSettings = LeaderboardSettingsFactory.getInstance().mergeLeaderboardSettings(newLeaderboardSettings, currentLeaderboardSettings); |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditableLeaderboardPanel.java
| ... | ... | @@ -584,7 +584,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel { |
| 584 | 584 | String leaderboardName, String leaderboardGroupName, final ErrorReporter errorReporter,
|
| 585 | 585 | final StringMessages stringMessages, UserAgentDetails userAgent) {
|
| 586 | 586 | super(sailingService, asyncActionsExecutor, LeaderboardSettingsFactory.getInstance().createNewDefaultSettings(
|
| 587 | - /* racesToShow */ null, /* namesOfRacesToShow */ null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true),
|
|
| 587 | + /* racesToShow */ null, /* namesOfRacesToShow */ null, null, /* autoExpandFirstRace */ false, /* showRegattaRank */ true, /* showCompetitorSailIdColumn */ true, /* showCompetitorFullNameColumn */ true),
|
|
| 588 | 588 | new CompetitorSelectionModel(/* hasMultiSelection */true),
|
| 589 | 589 | leaderboardName, errorReporter, stringMessages, userAgent, /* showRaceDetails */ true);
|
| 590 | 590 | suppressedCompetitorsShown = new ListDataProvider<CompetitorDTO>(new ArrayList<CompetitorDTO>());
|
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/EmbeddedMapAndWindChartEntryPoint.java
| ... | ... | @@ -151,7 +151,8 @@ public class EmbeddedMapAndWindChartEntryPoint extends AbstractSailingEntryPoint |
| 151 | 151 | // Use a TimePanel to manage wind chart zoom, although the TimePanel itself is not being displayed;
|
| 152 | 152 | // let the time panel always return to "live" mode.
|
| 153 | 153 | final TimePanel<TimePanelSettings> timePanel = new TimePanel<TimePanelSettings>(
|
| 154 | - timer, timeRangeWithZoomProvider, getStringMessages(), /* canReplayWhileLive */ false) {
|
|
| 154 | + timer, timeRangeWithZoomProvider, getStringMessages(), /* canReplayWhileLive */ false,
|
|
| 155 | + /* isScreenLargeEnoughToOfferChartSupport set to true iff wind chart will be displayed */ showWindChart) {
|
|
| 155 | 156 | protected boolean isLiveModeToBeMadePossible() {
|
| 156 | 157 | return true;
|
| 157 | 158 | }
|
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java
| ... | ... | @@ -1,197 +1,205 @@ |
| 1 | -
|
|
| 2 | -package com.sap.sailing.gwt.ui.raceboard;
|
|
| 3 | -
|
|
| 4 | -import java.util.Collections;
|
|
| 5 | -import java.util.List;
|
|
| 6 | -import java.util.UUID;
|
|
| 7 | -
|
|
| 8 | -import com.google.gwt.core.client.GWT;
|
|
| 9 | -import com.google.gwt.dom.client.Style.Unit;
|
|
| 10 | -import com.google.gwt.event.dom.client.ClickEvent;
|
|
| 11 | -import com.google.gwt.event.dom.client.ClickHandler;
|
|
| 12 | -import com.google.gwt.user.client.Window;
|
|
| 13 | -import com.google.gwt.user.client.rpc.AsyncCallback;
|
|
| 14 | -import com.google.gwt.user.client.rpc.ServiceDefTarget;
|
|
| 15 | -import com.google.gwt.user.client.ui.Button;
|
|
| 16 | -import com.google.gwt.user.client.ui.DockLayoutPanel;
|
|
| 17 | -import com.google.gwt.user.client.ui.FlowPanel;
|
|
| 18 | -import com.google.gwt.user.client.ui.Label;
|
|
| 19 | -import com.google.gwt.user.client.ui.RootLayoutPanel;
|
|
| 20 | -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
|
| 21 | -import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint;
|
|
| 22 | -import com.sap.sailing.gwt.ui.client.LogoAndTitlePanel;
|
|
| 23 | -import com.sap.sailing.gwt.ui.client.MediaService;
|
|
| 24 | -import com.sap.sailing.gwt.ui.client.MediaServiceAsync;
|
|
| 25 | -import com.sap.sailing.gwt.ui.client.RaceSelectionModel;
|
|
| 26 | -import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider;
|
|
| 27 | -import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants;
|
|
| 28 | -import com.sap.sailing.gwt.ui.shared.RaceWithCompetitorsDTO;
|
|
| 29 | -import com.sap.sailing.gwt.ui.shared.RaceboardDataDTO;
|
|
| 30 | -import com.sap.sse.gwt.client.EntryPointHelper;
|
|
| 31 | -import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
|
|
| 32 | -import com.sap.sse.gwt.client.player.Timer;
|
|
| 33 | -import com.sap.sse.gwt.client.player.Timer.PlayModes;
|
|
| 34 | -import com.sap.sse.gwt.shared.GwtHttpRequestUtils;
|
|
| 35 | -
|
|
| 36 | -public class RaceBoardEntryPoint extends AbstractSailingEntryPoint {
|
|
| 37 | - private RaceWithCompetitorsDTO selectedRace;
|
|
| 38 | -
|
|
| 39 | - private static final String PARAM_REGATTA_NAME = "regattaName";
|
|
| 40 | - private static final String PARAM_RACE_NAME = "raceName";
|
|
| 41 | - private static final String PARAM_LEADERBOARD_NAME = "leaderboardName";
|
|
| 42 | - private static final String PARAM_LEADERBOARD_GROUP_NAME = "leaderboardGroupName";
|
|
| 43 | - private static final String PARAM_EVENT_ID = "eventId";
|
|
| 44 | -
|
|
| 45 | - private String regattaName;
|
|
| 46 | - private String raceName;
|
|
| 47 | - private String leaderboardName;
|
|
| 48 | - private String leaderboardGroupName;
|
|
| 49 | - private UUID eventId;
|
|
| 50 | - private RaceBoardViewConfiguration raceboardViewConfig;
|
|
| 51 | -
|
|
| 52 | - private final MediaServiceAsync mediaService = GWT.create(MediaService.class);
|
|
| 53 | -
|
|
| 54 | - @Override
|
|
| 55 | - protected void doOnModuleLoad() {
|
|
| 56 | - super.doOnModuleLoad();
|
|
| 57 | - EntryPointHelper.registerASyncService((ServiceDefTarget) mediaService, RemoteServiceMappingConstants.mediaServiceRemotePath);
|
|
| 58 | - // read mandatory parameters
|
|
| 59 | - regattaName = Window.Location.getParameter(PARAM_REGATTA_NAME);
|
|
| 60 | - raceName = Window.Location.getParameter(PARAM_RACE_NAME);
|
|
| 61 | - String leaderboardNameParamValue = Window.Location.getParameter(PARAM_LEADERBOARD_NAME);
|
|
| 62 | - String leaderboardGroupNameParamValue = Window.Location.getParameter(PARAM_LEADERBOARD_GROUP_NAME);
|
|
| 63 | - if (leaderboardNameParamValue != null && !leaderboardNameParamValue.isEmpty()) {
|
|
| 64 | - leaderboardName = leaderboardNameParamValue;
|
|
| 65 | - }
|
|
| 66 | - if (leaderboardGroupNameParamValue != null && !leaderboardGroupNameParamValue.isEmpty()) {
|
|
| 67 | - leaderboardGroupName = leaderboardGroupNameParamValue;
|
|
| 68 | - }
|
|
| 69 | - String eventIdParamValue = Window.Location.getParameter(PARAM_EVENT_ID);
|
|
| 70 | - if (eventIdParamValue != null && !eventIdParamValue.isEmpty()) {
|
|
| 71 | - eventId = UUID.fromString(eventIdParamValue);
|
|
| 72 | - }
|
|
| 73 | - if (leaderboardGroupNameParamValue != null && !leaderboardGroupNameParamValue.isEmpty()) {
|
|
| 74 | - leaderboardGroupName = leaderboardGroupNameParamValue;
|
|
| 75 | - }
|
|
| 76 | - if (regattaName == null || regattaName.isEmpty() || raceName == null || raceName.isEmpty() ||
|
|
| 77 | - leaderboardName == null || leaderboardName.isEmpty()) {
|
|
| 78 | - createErrorPage("This page requires a valid regatta name, race name and leaderboard name.");
|
|
| 79 | - return;
|
|
| 80 | - }
|
|
| 81 | -
|
|
| 82 | - // read optional parameters
|
|
| 83 | - boolean showLeaderboard = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_LEADERBOARD, true /* default*/);
|
|
| 84 | - boolean showWindChart = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_WINDCHART, false /* default*/);
|
|
| 85 | - boolean showCompetitorsChart = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_COMPETITORSCHART, false /* default*/);
|
|
| 86 | - boolean showViewStreamlets = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_STREAMLETS, false /* default*/);
|
|
| 87 | - boolean showViewStreamletColors = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_STREAMLET_COLORS, false /* default*/);
|
|
| 88 | - boolean showViewSimulation = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_SIMULATION, true /* default*/);
|
|
| 89 | - String activeCompetitorsFilterSetName = GwtHttpRequestUtils.getStringParameter(RaceBoardViewConfiguration.PARAM_VIEW_COMPETITOR_FILTER, null /* default*/);
|
|
| 90 | - final boolean canReplayWhileLiveIsPossible = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_CAN_REPLAY_DURING_LIVE_RACES, false);
|
|
| 91 | - final boolean autoSelectMedia = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_AUTOSELECT_MEDIA, true);
|
|
| 92 | - final String defaultMedia = GwtHttpRequestUtils.getStringParameter(RaceBoardViewConfiguration.PARAM_DEFAULT_MEDIA, null /* default*/);
|
|
| 93 | - final boolean showMapControls = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_MAPCONTROLS, true /* default*/);
|
|
| 94 | - raceboardViewConfig = new RaceBoardViewConfiguration(activeCompetitorsFilterSetName, showLeaderboard,
|
|
| 95 | - showWindChart, showCompetitorsChart, showViewStreamlets, showViewStreamletColors, showViewSimulation, canReplayWhileLiveIsPossible, autoSelectMedia, defaultMedia);
|
|
| 96 | -
|
|
| 97 | - sailingService.getRaceboardData(regattaName, raceName, leaderboardName, leaderboardGroupName, eventId, new AsyncCallback<RaceboardDataDTO>() {
|
|
| 98 | - @Override
|
|
| 99 | - public void onSuccess(RaceboardDataDTO result) {
|
|
| 100 | - checkUrlParameters(result, canReplayWhileLiveIsPossible, showMapControls);
|
|
| 101 | - }
|
|
| 102 | -
|
|
| 103 | - @Override
|
|
| 104 | - public void onFailure(Throwable caught) {
|
|
| 105 | - reportError("Error trying to create the raceboard: " + caught.getMessage());
|
|
| 106 | - }
|
|
| 107 | - });
|
|
| 108 | - }
|
|
| 109 | -
|
|
| 110 | - private void createErrorPage(String message) {
|
|
| 111 | - final DockLayoutPanel vp = new DockLayoutPanel(Unit.PX);
|
|
| 112 | - LogoAndTitlePanel logoAndTitlePanel = new LogoAndTitlePanel(getStringMessages(), this, getUserService());
|
|
| 113 | - logoAndTitlePanel.addStyleName("LogoAndTitlePanel");
|
|
| 114 | - RootLayoutPanel.get().add(vp);
|
|
| 115 | - vp.addNorth(logoAndTitlePanel, 100);
|
|
| 116 | - vp.add(new Label(message));
|
|
| 117 | - }
|
|
| 118 | -
|
|
| 119 | - private void checkUrlParameters(RaceboardDataDTO raceboardData, boolean canReplayWhileLiveIsPossible, boolean showMapControls) {
|
|
| 120 | - if (!raceboardData.isValidLeaderboard()) {
|
|
| 121 | - createErrorPage(getStringMessages().noSuchLeaderboard());
|
|
| 122 | - return;
|
|
| 123 | - }
|
|
| 124 | - if (eventId != null && !raceboardData.isValidEvent()) {
|
|
| 125 | - createErrorPage(getStringMessages().noSuchEvent());
|
|
| 126 | - }
|
|
| 127 | - if (leaderboardGroupName != null) {
|
|
| 128 | - if(!raceboardData.isValidLeaderboardGroup()) {
|
|
| 129 | - createErrorPage(getStringMessages().leaderboardNotContainedInLeaderboardGroup(leaderboardName, leaderboardGroupName));
|
|
| 130 | - return;
|
|
| 131 | - }
|
|
| 132 | - if (eventId != null && raceboardData.isValidLeaderboardGroup() && !raceboardData.isValidEvent()) {
|
|
| 133 | - createErrorPage(getStringMessages().leaderboardGroupNotContainedInEvent(leaderboardGroupName, eventId.toString()));
|
|
| 134 | - return;
|
|
| 135 | - }
|
|
| 136 | - }
|
|
| 137 | - if (raceboardData.getRace() == null) {
|
|
| 138 | - createErrorPage("Could not obtain a race with name " + raceName + " for a regatta with name " + regattaName);
|
|
| 139 | - return;
|
|
| 140 | - }
|
|
| 141 | - selectedRace = raceboardData.getRace();
|
|
| 142 | - Window.setTitle(selectedRace.getName());
|
|
| 143 | - RaceSelectionModel raceSelectionModel = new RaceSelectionModel();
|
|
| 144 | - List<RegattaAndRaceIdentifier> singletonList = Collections.singletonList(selectedRace.getRaceIdentifier());
|
|
| 145 | - raceSelectionModel.setSelection(singletonList);
|
|
| 146 | - Timer timer = new Timer(PlayModes.Replay, 1000l);
|
|
| 147 | - AsyncActionsExecutor asyncActionsExecutor = new AsyncActionsExecutor();
|
|
| 148 | - RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, asyncActionsExecutor, this, singletonList, 5000l /* requestInterval*/);
|
|
| 149 | - RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, mediaService, getUserService(), asyncActionsExecutor,
|
|
| 150 | - raceboardData.getCompetitorAndTheirBoats(), timer, raceSelectionModel, leaderboardName, leaderboardGroupName, eventId,
|
|
| 151 | - raceboardViewConfig, RaceBoardEntryPoint.this, getStringMessages(), userAgent, raceTimesInfoProvider, showMapControls);
|
|
| 152 | -
|
|
| 153 | - createRaceBoardInOneScreenMode(raceBoardPanel, raceboardViewConfig);
|
|
| 154 | - }
|
|
| 155 | -
|
|
| 156 | - private FlowPanel createTimePanel(RaceBoardPanel raceBoardPanel) {
|
|
| 157 | - FlowPanel timeLineInnerBgPanel = new FlowPanel();
|
|
| 158 | - timeLineInnerBgPanel.addStyleName("timeLineInnerBgPanel");
|
|
| 159 | - timeLineInnerBgPanel.add(raceBoardPanel.getTimePanel());
|
|
| 160 | -
|
|
| 161 | - FlowPanel timeLineInnerPanel = new FlowPanel();
|
|
| 162 | - timeLineInnerPanel.add(timeLineInnerBgPanel);
|
|
| 163 | - timeLineInnerPanel.addStyleName("timeLineInnerPanel");
|
|
| 164 | -
|
|
| 165 | - FlowPanel timelinePanel = new FlowPanel();
|
|
| 166 | - timelinePanel.add(timeLineInnerPanel);
|
|
| 167 | - timelinePanel.addStyleName("timeLinePanel");
|
|
| 168 | -
|
|
| 169 | - return timelinePanel;
|
|
| 170 | - }
|
|
| 171 | -
|
|
| 172 | - private void createRaceBoardInOneScreenMode(final RaceBoardPanel raceBoardPanel,
|
|
| 173 | - RaceBoardViewConfiguration raceboardViewConfiguration) {
|
|
| 174 | - final DockLayoutPanel p = new DockLayoutPanel(Unit.PX);
|
|
| 175 | - RootLayoutPanel.get().add(p);
|
|
| 176 | - final FlowPanel timePanel = createTimePanel(raceBoardPanel);
|
|
| 177 | - final Button toggleButton = raceBoardPanel.getTimePanel().getAdvancedToggleButton();
|
|
| 178 | - toggleButton.addClickHandler(new ClickHandler() {
|
|
| 179 | - @Override
|
|
| 180 | - public void onClick(ClickEvent event) {
|
|
| 181 | - boolean advancedModeShown = raceBoardPanel.getTimePanel().toggleAdvancedMode();
|
|
| 182 | - if (advancedModeShown) {
|
|
| 183 | - p.setWidgetSize(timePanel, 96);
|
|
| 184 | - toggleButton.removeStyleDependentName("Closed");
|
|
| 185 | - toggleButton.addStyleDependentName("Open");
|
|
| 186 | - } else {
|
|
| 187 | - p.setWidgetSize(timePanel, 67);
|
|
| 188 | - toggleButton.addStyleDependentName("Closed");
|
|
| 189 | - toggleButton.removeStyleDependentName("Open");
|
|
| 190 | - }
|
|
| 191 | - }
|
|
| 192 | - });
|
|
| 193 | - p.addSouth(timePanel, 67);
|
|
| 194 | - p.add(raceBoardPanel);
|
|
| 195 | - p.addStyleName("dockLayoutPanel");
|
|
| 196 | - }
|
|
| 197 | -}
|
|
| 1 | + |
|
| 2 | +package com.sap.sailing.gwt.ui.raceboard; |
|
| 3 | + |
|
| 4 | +import java.util.Collections; |
|
| 5 | +import java.util.List; |
|
| 6 | +import java.util.UUID; |
|
| 7 | + |
|
| 8 | +import com.google.gwt.core.client.GWT; |
|
| 9 | +import com.google.gwt.dom.client.Document; |
|
| 10 | +import com.google.gwt.dom.client.Style.Unit; |
|
| 11 | +import com.google.gwt.event.dom.client.ClickEvent; |
|
| 12 | +import com.google.gwt.event.dom.client.ClickHandler; |
|
| 13 | +import com.google.gwt.user.client.Window; |
|
| 14 | +import com.google.gwt.user.client.rpc.AsyncCallback; |
|
| 15 | +import com.google.gwt.user.client.rpc.ServiceDefTarget; |
|
| 16 | +import com.google.gwt.user.client.ui.Button; |
|
| 17 | +import com.google.gwt.user.client.ui.DockLayoutPanel; |
|
| 18 | +import com.google.gwt.user.client.ui.FlowPanel; |
|
| 19 | +import com.google.gwt.user.client.ui.Label; |
|
| 20 | +import com.google.gwt.user.client.ui.RootLayoutPanel; |
|
| 21 | +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; |
|
| 22 | +import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint; |
|
| 23 | +import com.sap.sailing.gwt.ui.client.LogoAndTitlePanel; |
|
| 24 | +import com.sap.sailing.gwt.ui.client.MediaService; |
|
| 25 | +import com.sap.sailing.gwt.ui.client.MediaServiceAsync; |
|
| 26 | +import com.sap.sailing.gwt.ui.client.RaceSelectionModel; |
|
| 27 | +import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider; |
|
| 28 | +import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants; |
|
| 29 | +import com.sap.sailing.gwt.ui.shared.RaceWithCompetitorsDTO; |
|
| 30 | +import com.sap.sailing.gwt.ui.shared.RaceboardDataDTO; |
|
| 31 | +import com.sap.sse.gwt.client.EntryPointHelper; |
|
| 32 | +import com.sap.sse.gwt.client.async.AsyncActionsExecutor; |
|
| 33 | +import com.sap.sse.gwt.client.player.Timer; |
|
| 34 | +import com.sap.sse.gwt.client.player.Timer.PlayModes; |
|
| 35 | +import com.sap.sse.gwt.shared.GwtHttpRequestUtils; |
|
| 36 | + |
|
| 37 | +public class RaceBoardEntryPoint extends AbstractSailingEntryPoint { |
|
| 38 | + private RaceWithCompetitorsDTO selectedRace; |
|
| 39 | + |
|
| 40 | + private static final String PARAM_REGATTA_NAME = "regattaName"; |
|
| 41 | + private static final String PARAM_RACE_NAME = "raceName"; |
|
| 42 | + private static final String PARAM_LEADERBOARD_NAME = "leaderboardName"; |
|
| 43 | + private static final String PARAM_LEADERBOARD_GROUP_NAME = "leaderboardGroupName"; |
|
| 44 | + private static final String PARAM_EVENT_ID = "eventId"; |
|
| 45 | + |
|
| 46 | + private String regattaName; |
|
| 47 | + private String raceName; |
|
| 48 | + private String leaderboardName; |
|
| 49 | + private String leaderboardGroupName; |
|
| 50 | + private UUID eventId; |
|
| 51 | + private RaceBoardViewConfiguration raceboardViewConfig; |
|
| 52 | + |
|
| 53 | + private final MediaServiceAsync mediaService = GWT.create(MediaService.class); |
|
| 54 | + |
|
| 55 | + @Override |
|
| 56 | + protected void doOnModuleLoad() { |
|
| 57 | + super.doOnModuleLoad(); |
|
| 58 | + EntryPointHelper.registerASyncService((ServiceDefTarget) mediaService, RemoteServiceMappingConstants.mediaServiceRemotePath); |
|
| 59 | + // read mandatory parameters |
|
| 60 | + regattaName = Window.Location.getParameter(PARAM_REGATTA_NAME); |
|
| 61 | + raceName = Window.Location.getParameter(PARAM_RACE_NAME); |
|
| 62 | + String leaderboardNameParamValue = Window.Location.getParameter(PARAM_LEADERBOARD_NAME); |
|
| 63 | + String leaderboardGroupNameParamValue = Window.Location.getParameter(PARAM_LEADERBOARD_GROUP_NAME); |
|
| 64 | + if (leaderboardNameParamValue != null && !leaderboardNameParamValue.isEmpty()) { |
|
| 65 | + leaderboardName = leaderboardNameParamValue; |
|
| 66 | + } |
|
| 67 | + if (leaderboardGroupNameParamValue != null && !leaderboardGroupNameParamValue.isEmpty()) { |
|
| 68 | + leaderboardGroupName = leaderboardGroupNameParamValue; |
|
| 69 | + } |
|
| 70 | + String eventIdParamValue = Window.Location.getParameter(PARAM_EVENT_ID); |
|
| 71 | + if (eventIdParamValue != null && !eventIdParamValue.isEmpty()) { |
|
| 72 | + eventId = UUID.fromString(eventIdParamValue); |
|
| 73 | + } |
|
| 74 | + if (leaderboardGroupNameParamValue != null && !leaderboardGroupNameParamValue.isEmpty()) { |
|
| 75 | + leaderboardGroupName = leaderboardGroupNameParamValue; |
|
| 76 | + } |
|
| 77 | + if (regattaName == null || regattaName.isEmpty() || raceName == null || raceName.isEmpty() || |
|
| 78 | + leaderboardName == null || leaderboardName.isEmpty()) { |
|
| 79 | + createErrorPage("This page requires a valid regatta name, race name and leaderboard name."); |
|
| 80 | + return; |
|
| 81 | + } |
|
| 82 | + |
|
| 83 | + // read optional parameters |
|
| 84 | + boolean showLeaderboard = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_LEADERBOARD, true /* default*/); |
|
| 85 | + boolean showWindChart = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_WINDCHART, false /* default*/); |
|
| 86 | + boolean showCompetitorsChart = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_COMPETITORSCHART, false /* default*/); |
|
| 87 | + boolean showViewStreamlets = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_STREAMLETS, false /* default*/); |
|
| 88 | + boolean showViewStreamletColors = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_STREAMLET_COLORS, false /* default*/); |
|
| 89 | + boolean showViewSimulation = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_SIMULATION, true /* default*/); |
|
| 90 | + String activeCompetitorsFilterSetName = GwtHttpRequestUtils.getStringParameter(RaceBoardViewConfiguration.PARAM_VIEW_COMPETITOR_FILTER, null /* default*/); |
|
| 91 | + final boolean canReplayWhileLiveIsPossible = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_CAN_REPLAY_DURING_LIVE_RACES, false); |
|
| 92 | + final boolean autoSelectMedia = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_AUTOSELECT_MEDIA, true); |
|
| 93 | + final String defaultMedia = GwtHttpRequestUtils.getStringParameter(RaceBoardViewConfiguration.PARAM_DEFAULT_MEDIA, null /* default*/); |
|
| 94 | + final boolean showMapControls = GwtHttpRequestUtils.getBooleanParameter(RaceBoardViewConfiguration.PARAM_VIEW_SHOW_MAPCONTROLS, true /* default*/); |
|
| 95 | + raceboardViewConfig = new RaceBoardViewConfiguration(activeCompetitorsFilterSetName, showLeaderboard, |
|
| 96 | + showWindChart, showCompetitorsChart, showViewStreamlets, showViewStreamletColors, showViewSimulation, canReplayWhileLiveIsPossible, autoSelectMedia, defaultMedia); |
|
| 97 | + |
|
| 98 | + sailingService.getRaceboardData(regattaName, raceName, leaderboardName, leaderboardGroupName, eventId, new AsyncCallback<RaceboardDataDTO>() { |
|
| 99 | + @Override |
|
| 100 | + public void onSuccess(RaceboardDataDTO result) { |
|
| 101 | + // Determine if the screen is large enough to display charts such as the competitor chart or the wind chart. |
|
| 102 | + // This decision is made once based on the initial screen height. Resizing the window afterwards will have |
|
| 103 | + // no impact on the chart support, i.e. they are available/unavailable based on the initial decision. |
|
| 104 | + boolean isScreenLargeEnoughToOfferChartSupport = Document.get().getClientHeight() >= 600; |
|
| 105 | + checkUrlParameters(result, canReplayWhileLiveIsPossible, showMapControls, |
|
| 106 | + isScreenLargeEnoughToOfferChartSupport); |
|
| 107 | + } |
|
| 108 | + |
|
| 109 | + @Override |
|
| 110 | + public void onFailure(Throwable caught) { |
|
| 111 | + reportError("Error trying to create the raceboard: " + caught.getMessage()); |
|
| 112 | + } |
|
| 113 | + }); |
|
| 114 | + } |
|
| 115 | + |
|
| 116 | + private void createErrorPage(String message) { |
|
| 117 | + final DockLayoutPanel vp = new DockLayoutPanel(Unit.PX); |
|
| 118 | + LogoAndTitlePanel logoAndTitlePanel = new LogoAndTitlePanel(getStringMessages(), this, getUserService()); |
|
| 119 | + logoAndTitlePanel.addStyleName("LogoAndTitlePanel"); |
|
| 120 | + RootLayoutPanel.get().add(vp); |
|
| 121 | + vp.addNorth(logoAndTitlePanel, 100); |
|
| 122 | + vp.add(new Label(message)); |
|
| 123 | + } |
|
| 124 | + |
|
| 125 | + private void checkUrlParameters(RaceboardDataDTO raceboardData, boolean canReplayWhileLiveIsPossible, boolean showMapControls, |
|
| 126 | + boolean isScreenLargeEnoughToOfferChartSupport) { |
|
| 127 | + if (!raceboardData.isValidLeaderboard()) { |
|
| 128 | + createErrorPage(getStringMessages().noSuchLeaderboard()); |
|
| 129 | + return; |
|
| 130 | + } |
|
| 131 | + if (eventId != null && !raceboardData.isValidEvent()) { |
|
| 132 | + createErrorPage(getStringMessages().noSuchEvent()); |
|
| 133 | + } |
|
| 134 | + if (leaderboardGroupName != null) { |
|
| 135 | + if(!raceboardData.isValidLeaderboardGroup()) { |
|
| 136 | + createErrorPage(getStringMessages().leaderboardNotContainedInLeaderboardGroup(leaderboardName, leaderboardGroupName)); |
|
| 137 | + return; |
|
| 138 | + } |
|
| 139 | + if (eventId != null && raceboardData.isValidLeaderboardGroup() && !raceboardData.isValidEvent()) { |
|
| 140 | + createErrorPage(getStringMessages().leaderboardGroupNotContainedInEvent(leaderboardGroupName, eventId.toString())); |
|
| 141 | + return; |
|
| 142 | + } |
|
| 143 | + } |
|
| 144 | + if (raceboardData.getRace() == null) { |
|
| 145 | + createErrorPage("Could not obtain a race with name " + raceName + " for a regatta with name " + regattaName); |
|
| 146 | + return; |
|
| 147 | + } |
|
| 148 | + selectedRace = raceboardData.getRace(); |
|
| 149 | + Window.setTitle(selectedRace.getName()); |
|
| 150 | + RaceSelectionModel raceSelectionModel = new RaceSelectionModel(); |
|
| 151 | + List<RegattaAndRaceIdentifier> singletonList = Collections.singletonList(selectedRace.getRaceIdentifier()); |
|
| 152 | + raceSelectionModel.setSelection(singletonList); |
|
| 153 | + Timer timer = new Timer(PlayModes.Replay, 1000l); |
|
| 154 | + AsyncActionsExecutor asyncActionsExecutor = new AsyncActionsExecutor(); |
|
| 155 | + RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, asyncActionsExecutor, this, singletonList, 5000l /* requestInterval*/); |
|
| 156 | + RaceBoardPanel raceBoardPanel = new RaceBoardPanel(sailingService, mediaService, getUserService(), asyncActionsExecutor, |
|
| 157 | + raceboardData.getCompetitorAndTheirBoats(), timer, raceSelectionModel, leaderboardName, leaderboardGroupName, eventId, |
|
| 158 | + raceboardViewConfig, RaceBoardEntryPoint.this, getStringMessages(), userAgent, raceTimesInfoProvider, showMapControls, |
|
| 159 | + isScreenLargeEnoughToOfferChartSupport); |
|
| 160 | + |
|
| 161 | + createRaceBoardInOneScreenMode(raceBoardPanel, raceboardViewConfig); |
|
| 162 | + } |
|
| 163 | + |
|
| 164 | + private FlowPanel createTimePanel(RaceBoardPanel raceBoardPanel) { |
|
| 165 | + FlowPanel timeLineInnerBgPanel = new ResizableFlowPanel(); |
|
| 166 | + timeLineInnerBgPanel.addStyleName("timeLineInnerBgPanel"); |
|
| 167 | + timeLineInnerBgPanel.add(raceBoardPanel.getTimePanel()); |
|
| 168 | + |
|
| 169 | + FlowPanel timeLineInnerPanel = new ResizableFlowPanel(); |
|
| 170 | + timeLineInnerPanel.add(timeLineInnerBgPanel); |
|
| 171 | + timeLineInnerPanel.addStyleName("timeLineInnerPanel"); |
|
| 172 | + |
|
| 173 | + FlowPanel timelinePanel = new ResizableFlowPanel(); |
|
| 174 | + timelinePanel.add(timeLineInnerPanel); |
|
| 175 | + timelinePanel.addStyleName("timeLinePanel"); |
|
| 176 | + |
|
| 177 | + return timelinePanel; |
|
| 178 | + } |
|
| 179 | + |
|
| 180 | + private void createRaceBoardInOneScreenMode(final RaceBoardPanel raceBoardPanel, |
|
| 181 | + RaceBoardViewConfiguration raceboardViewConfiguration) { |
|
| 182 | + final DockLayoutPanel p = new DockLayoutPanel(Unit.PX); |
|
| 183 | + RootLayoutPanel.get().add(p); |
|
| 184 | + final FlowPanel timePanel = createTimePanel(raceBoardPanel); |
|
| 185 | + final Button toggleButton = raceBoardPanel.getTimePanel().getAdvancedToggleButton(); |
|
| 186 | + toggleButton.addClickHandler(new ClickHandler() { |
|
| 187 | + @Override |
|
| 188 | + public void onClick(ClickEvent event) { |
|
| 189 | + boolean advancedModeShown = raceBoardPanel.getTimePanel().toggleAdvancedMode(); |
|
| 190 | + if (advancedModeShown) { |
|
| 191 | + p.setWidgetSize(timePanel, 96); |
|
| 192 | + toggleButton.removeStyleDependentName("Closed"); |
|
| 193 | + toggleButton.addStyleDependentName("Open"); |
|
| 194 | + } else { |
|
| 195 | + p.setWidgetSize(timePanel, 67); |
|
| 196 | + toggleButton.addStyleDependentName("Closed"); |
|
| 197 | + toggleButton.removeStyleDependentName("Open"); |
|
| 198 | + } |
|
| 199 | + } |
|
| 200 | + }); |
|
| 201 | + p.addSouth(timePanel, 67); |
|
| 202 | + p.add(raceBoardPanel); |
|
| 203 | + p.addStyleName("dockLayoutPanel"); |
|
| 204 | + } |
|
| 205 | +} |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java
| ... | ... | @@ -1,437 +1,483 @@ |
| 1 | -package com.sap.sailing.gwt.ui.raceboard;
|
|
| 2 | -
|
|
| 3 | -import java.util.ArrayList;
|
|
| 4 | -import java.util.Collections;
|
|
| 5 | -import java.util.HashMap;
|
|
| 6 | -import java.util.List;
|
|
| 7 | -import java.util.Map;
|
|
| 8 | -import java.util.UUID;
|
|
| 9 | -
|
|
| 10 | -import com.google.gwt.core.shared.GWT;
|
|
| 11 | -import com.google.gwt.dom.client.Document;
|
|
| 12 | -import com.google.gwt.dom.client.Style.Unit;
|
|
| 13 | -import com.google.gwt.i18n.client.DateTimeFormat;
|
|
| 14 | -import com.google.gwt.user.client.Command;
|
|
| 15 | -import com.google.gwt.user.client.Window;
|
|
| 16 | -import com.google.gwt.user.client.ui.Anchor;
|
|
| 17 | -import com.google.gwt.user.client.ui.FlowPanel;
|
|
| 18 | -import com.google.gwt.user.client.ui.Label;
|
|
| 19 | -import com.google.gwt.user.client.ui.MenuBar;
|
|
| 20 | -import com.google.gwt.user.client.ui.SimplePanel;
|
|
| 21 | -import com.google.gwt.user.client.ui.UIObject;
|
|
| 22 | -import com.sap.sailing.domain.common.LeaderboardNameConstants;
|
|
| 23 | -import com.sap.sailing.domain.common.RaceIdentifier;
|
|
| 24 | -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
|
| 25 | -import com.sap.sailing.domain.common.dto.BoatDTO;
|
|
| 26 | -import com.sap.sailing.domain.common.dto.CompetitorDTO;
|
|
| 27 | -import com.sap.sailing.domain.common.dto.FleetDTO;
|
|
| 28 | -import com.sap.sailing.domain.common.dto.LeaderboardDTO;
|
|
| 29 | -import com.sap.sailing.domain.common.dto.RaceColumnDTO;
|
|
| 30 | -import com.sap.sailing.domain.common.dto.RaceDTO;
|
|
| 31 | -import com.sap.sailing.gwt.ui.client.CompetitorColorProvider;
|
|
| 32 | -import com.sap.sailing.gwt.ui.client.CompetitorColorProviderImpl;
|
|
| 33 | -import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel;
|
|
| 34 | -import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider;
|
|
| 35 | -import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory;
|
|
| 36 | -import com.sap.sailing.gwt.ui.client.GlobalNavigationPanel;
|
|
| 37 | -import com.sap.sailing.gwt.ui.client.LeaderboardUpdateListener;
|
|
| 38 | -import com.sap.sailing.gwt.ui.client.MediaServiceAsync;
|
|
| 39 | -import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener;
|
|
| 40 | -import com.sap.sailing.gwt.ui.client.RaceSelectionProvider;
|
|
| 41 | -import com.sap.sailing.gwt.ui.client.RaceTimePanel;
|
|
| 42 | -import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider;
|
|
| 43 | -import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
|
| 44 | -import com.sap.sailing.gwt.ui.client.StringMessages;
|
|
| 45 | -import com.sap.sailing.gwt.ui.client.media.MediaPlayerManagerComponent;
|
|
| 46 | -import com.sap.sailing.gwt.ui.client.media.PopupPositionProvider;
|
|
| 47 | -import com.sap.sailing.gwt.ui.client.shared.charts.EditMarkPassingsPanel;
|
|
| 48 | -import com.sap.sailing.gwt.ui.client.shared.charts.MultiCompetitorRaceChart;
|
|
| 49 | -import com.sap.sailing.gwt.ui.client.shared.charts.WindChart;
|
|
| 50 | -import com.sap.sailing.gwt.ui.client.shared.charts.WindChartSettings;
|
|
| 51 | -import com.sap.sailing.gwt.ui.client.shared.filter.FilterWithUI;
|
|
| 52 | -import com.sap.sailing.gwt.ui.client.shared.filter.LeaderboardFetcher;
|
|
| 53 | -import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMap;
|
|
| 54 | -import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapResources;
|
|
| 55 | -import com.sap.sailing.gwt.ui.leaderboard.CompetitorFilterPanel;
|
|
| 56 | -import com.sap.sailing.gwt.ui.leaderboard.ExplicitRaceColumnSelectionWithPreselectedRace;
|
|
| 57 | -import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel;
|
|
| 58 | -import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings;
|
|
| 59 | -import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettingsFactory;
|
|
| 60 | -import com.sap.sse.common.filter.FilterSet;
|
|
| 61 | -import com.sap.sse.common.settings.AbstractSettings;
|
|
| 62 | -import com.sap.sse.gwt.client.ErrorReporter;
|
|
| 63 | -import com.sap.sse.gwt.client.URLEncoder;
|
|
| 64 | -import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
|
|
| 65 | -import com.sap.sse.gwt.client.player.TimeRangeWithZoomModel;
|
|
| 66 | -import com.sap.sse.gwt.client.player.Timer;
|
|
| 67 | -import com.sap.sse.gwt.client.shared.components.Component;
|
|
| 68 | -import com.sap.sse.gwt.client.shared.components.ComponentViewer;
|
|
| 69 | -import com.sap.sse.gwt.client.shared.components.SettingsDialog;
|
|
| 70 | -import com.sap.sse.gwt.client.useragent.UserAgentDetails;
|
|
| 71 | -import com.sap.sse.security.ui.client.UserService;
|
|
| 72 | -
|
|
| 73 | -/**
|
|
| 74 | - * A view showing a list of components visualizing a race from the regattas announced by calls to {@link #fillRegattas(List)}.
|
|
| 75 | - * The race selection is provided by a {@link RaceSelectionProvider} for which this is a {@link RaceSelectionChangeListener listener}.
|
|
| 76 | - * {@link RaceIdentifier}-based race selection changes are converted to {@link RaceDTO} objects using the {@link #racesByIdentifier}
|
|
| 77 | - * map maintained during {@link #fillRegattas(List)}. The race selection provider is expected to be single selection only.
|
|
| 78 | - *
|
|
| 79 | - * @author Frank Mittag, Axel Uhl (d043530)
|
|
| 80 | - *
|
|
| 81 | - */
|
|
| 82 | -public class RaceBoardPanel extends SimplePanel implements RaceSelectionChangeListener, LeaderboardUpdateListener, PopupPositionProvider {
|
|
| 83 | - private final SailingServiceAsync sailingService;
|
|
| 84 | - private final MediaServiceAsync mediaService;
|
|
| 85 | - private final UUID eventId;
|
|
| 86 | - private final StringMessages stringMessages;
|
|
| 87 | - private final ErrorReporter errorReporter;
|
|
| 88 | - private final RaceBoardViewConfiguration raceboardViewConfiguration;
|
|
| 89 | - private String raceBoardName;
|
|
| 90 | -
|
|
| 91 | - private final List<ComponentViewer> componentViewers;
|
|
| 92 | - private RaceTimePanel timePanel;
|
|
| 93 | - private final Timer timer;
|
|
| 94 | - private final RaceSelectionProvider raceSelectionProvider;
|
|
| 95 | - private final UserAgentDetails userAgent;
|
|
| 96 | - private final CompetitorSelectionProvider competitorSelectionProvider;
|
|
| 97 | - private final TimeRangeWithZoomModel timeRangeWithZoomModel;
|
|
| 98 | - private final RegattaAndRaceIdentifier selectedRaceIdentifier;
|
|
| 99 | -
|
|
| 100 | - private final String leaderboardName;
|
|
| 101 | - private final LeaderboardPanel leaderboardPanel;
|
|
| 102 | - private WindChart windChart;
|
|
| 103 | - private MultiCompetitorRaceChart competitorChart;
|
|
| 104 | - private EditMarkPassingsPanel editMarkPassingPanel;
|
|
| 105 | -
|
|
| 106 | - /**
|
|
| 107 | - * The component viewer in <code>ONESCREEN</code> view mode. <code>null</code> if in <code>CASCADE</code> view mode
|
|
| 108 | - */
|
|
| 109 | - private SideBySideComponentViewer leaderboardAndMapViewer;
|
|
| 110 | -
|
|
| 111 | - private final AsyncActionsExecutor asyncActionsExecutor;
|
|
| 112 | -
|
|
| 113 | - private final RaceTimesInfoProvider raceTimesInfoProvider;
|
|
| 114 | - private RaceMap raceMap;
|
|
| 115 | -
|
|
| 116 | - private final FlowPanel raceInformationHeader;
|
|
| 117 | - private final FlowPanel regattaAndRaceTimeInformationHeader;
|
|
| 118 | - private boolean currentRaceHasBeenSelectedOnce;
|
|
| 119 | -
|
|
| 120 | - private static final RaceMapResources raceMapResources = GWT.create(RaceMapResources.class);
|
|
| 121 | -
|
|
| 122 | - /**
|
|
| 123 | - * @param eventId
|
|
| 124 | - * an optional event that can be used for "back"-navigation in case the race board shows a race in the
|
|
| 125 | - * context of an event; may be <code>null</code>.
|
|
| 126 | - */
|
|
| 127 | - public RaceBoardPanel(SailingServiceAsync sailingService, MediaServiceAsync mediaService,
|
|
| 128 | - UserService userService, AsyncActionsExecutor asyncActionsExecutor, Map<CompetitorDTO, BoatDTO> competitorsAndTheirBoats,
|
|
| 129 | - Timer timer, RaceSelectionProvider theRaceSelectionProvider, String leaderboardName,
|
|
| 130 | - String leaderboardGroupName, UUID eventId, RaceBoardViewConfiguration raceboardViewConfiguration,
|
|
| 131 | - ErrorReporter errorReporter, final StringMessages stringMessages,
|
|
| 132 | - UserAgentDetails userAgent, RaceTimesInfoProvider raceTimesInfoProvider, boolean showMapControls) {
|
|
| 133 | - this.sailingService = sailingService;
|
|
| 134 | - this.mediaService = mediaService;
|
|
| 135 | - this.stringMessages = stringMessages;
|
|
| 136 | - this.raceboardViewConfiguration = raceboardViewConfiguration;
|
|
| 137 | - this.raceSelectionProvider = theRaceSelectionProvider;
|
|
| 138 | - this.raceTimesInfoProvider = raceTimesInfoProvider;
|
|
| 139 | - this.errorReporter = errorReporter;
|
|
| 140 | - this.userAgent = userAgent;
|
|
| 141 | - this.timer = timer;
|
|
| 142 | - this.eventId = eventId;
|
|
| 143 | - this.currentRaceHasBeenSelectedOnce = false;
|
|
| 144 | - this.leaderboardName = leaderboardName;
|
|
| 145 | - raceSelectionProvider.addRaceSelectionChangeListener(this);
|
|
| 146 | - selectedRaceIdentifier = raceSelectionProvider.getSelectedRaces().iterator().next();
|
|
| 147 | - this.setRaceBoardName(selectedRaceIdentifier.getRaceName());
|
|
| 148 | - this.asyncActionsExecutor = asyncActionsExecutor;
|
|
| 149 | - FlowPanel mainPanel = new FlowPanel();
|
|
| 150 | - mainPanel.setSize("100%", "100%");
|
|
| 151 | - setWidget(mainPanel);
|
|
| 152 | - raceInformationHeader = new FlowPanel();
|
|
| 153 | - raceInformationHeader.setStyleName("RegattaRaceInformation-Header");
|
|
| 154 | - regattaAndRaceTimeInformationHeader = new FlowPanel();
|
|
| 155 | - regattaAndRaceTimeInformationHeader.setStyleName("RegattaAndRaceTime-Header");
|
|
| 156 | - timeRangeWithZoomModel = new TimeRangeWithZoomModel();
|
|
| 157 | - componentViewers = new ArrayList<ComponentViewer>();
|
|
| 158 | - final CompetitorColorProvider colorProvider = new CompetitorColorProviderImpl(selectedRaceIdentifier, competitorsAndTheirBoats);
|
|
| 159 | - competitorSelectionProvider = new CompetitorSelectionModel(/* hasMultiSelection */ true, colorProvider);
|
|
| 160 | -
|
|
| 161 | - raceMapResources.combinedWindPanelStyle().ensureInjected();
|
|
| 162 | - raceMap = new RaceMap(sailingService, asyncActionsExecutor, errorReporter, timer,
|
|
| 163 | - competitorSelectionProvider, stringMessages, showMapControls, getConfiguration().isShowViewStreamlets(), getConfiguration().isShowViewStreamletColors(), getConfiguration().isShowViewSimulation(),
|
|
| 164 | - selectedRaceIdentifier, raceMapResources.combinedWindPanelStyle(), /* showHeaderPanel */ true);
|
|
| 165 | - CompetitorFilterPanel competitorSearchTextBox = new CompetitorFilterPanel(competitorSelectionProvider, stringMessages, raceMap,
|
|
| 166 | - new LeaderboardFetcher() {
|
|
| 167 | - @Override
|
|
| 168 | - public LeaderboardDTO getLeaderboard() {
|
|
| 169 | - return leaderboardPanel.getLeaderboard();
|
|
| 170 | - }
|
|
| 171 | - }, selectedRaceIdentifier);
|
|
| 172 | - raceMap.getLeftHeaderPanel().add(raceInformationHeader);
|
|
| 173 | - raceMap.getRightHeaderPanel().add(regattaAndRaceTimeInformationHeader);
|
|
| 174 | -
|
|
| 175 | - leaderboardPanel = createLeaderboardPanel(leaderboardName, leaderboardGroupName, competitorSearchTextBox);
|
|
| 176 | - leaderboardPanel.setTitle(stringMessages.leaderboard());
|
|
| 177 | - leaderboardPanel.getElement().getStyle().setMarginLeft(6, Unit.PX);
|
|
| 178 | - leaderboardPanel.getElement().getStyle().setMarginTop(10, Unit.PX);
|
|
| 179 | - createOneScreenView(leaderboardName, leaderboardGroupName, eventId, mainPanel, showMapControls, raceMap, userService); // initializes the raceMap field
|
|
| 180 | - leaderboardPanel.addLeaderboardUpdateListener(this);
|
|
| 181 | - // in case the URL configuration contains the name of a competitors filter set we try to activate it
|
|
| 182 | - // FIXME the competitorsFilterSets has now moved to CompetitorSearchTextBox (which should probably be renamed); pass on the parameters to the LeaderboardPanel and see what it does with it
|
|
| 183 | - if (raceboardViewConfiguration.getActiveCompetitorsFilterSetName() != null) {
|
|
| 184 | - for (FilterSet<CompetitorDTO, FilterWithUI<CompetitorDTO>> filterSet : competitorSearchTextBox.getCompetitorsFilterSets()
|
|
| 185 | - .getFilterSets()) {
|
|
| 186 | - if (filterSet.getName().equals(raceboardViewConfiguration.getActiveCompetitorsFilterSetName())) {
|
|
| 187 | - competitorSearchTextBox.getCompetitorsFilterSets().setActiveFilterSet(filterSet);
|
|
| 188 | - break;
|
|
| 189 | - }
|
|
| 190 | - }
|
|
| 191 | - }
|
|
| 192 | - timePanel = new RaceTimePanel(timer, timeRangeWithZoomModel, stringMessages, raceTimesInfoProvider,
|
|
| 193 | - raceboardViewConfiguration.isCanReplayDuringLiveRaces());
|
|
| 194 | - timeRangeWithZoomModel.addTimeZoomChangeListener(timePanel);
|
|
| 195 | - raceTimesInfoProvider.addRaceTimesInfoProviderListener(timePanel);
|
|
| 196 | - raceSelectionProvider.addRaceSelectionChangeListener(timePanel);
|
|
| 197 | - timePanel.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces());
|
|
| 198 | - }
|
|
| 199 | -
|
|
| 200 | - /**
|
|
| 201 | - * @param event an optional event; may be <code>null</code> or else can be used to show some context information in
|
|
| 202 | - * the {@link GlobalNavigationPanel}.
|
|
| 203 | - */
|
|
| 204 | - private void createOneScreenView(String leaderboardName, String leaderboardGroupName, UUID event, FlowPanel mainPanel,
|
|
| 205 | - boolean showMapControls, RaceMap raceMap, UserService userService) {
|
|
| 206 | - // create the default leaderboard and select the right race
|
|
| 207 | - raceTimesInfoProvider.addRaceTimesInfoProviderListener(raceMap);
|
|
| 208 | - raceMap.onRaceSelectionChange(Collections.singletonList(selectedRaceIdentifier));
|
|
| 209 | - List<Component<?>> components = new ArrayList<Component<?>>();
|
|
| 210 | - competitorChart = new MultiCompetitorRaceChart(sailingService, asyncActionsExecutor, competitorSelectionProvider, raceSelectionProvider,
|
|
| 211 | - timer, timeRangeWithZoomModel, stringMessages, errorReporter, true, true, leaderboardGroupName, leaderboardName);
|
|
| 212 | - competitorChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces());
|
|
| 213 | - competitorChart.getEntryWidget().setTitle(stringMessages.competitorCharts());
|
|
| 214 | - competitorChart.setVisible(false);
|
|
| 215 | - components.add(competitorChart);
|
|
| 216 | - windChart = new WindChart(sailingService, raceSelectionProvider, timer, timeRangeWithZoomModel, new WindChartSettings(),
|
|
| 217 | - stringMessages, asyncActionsExecutor, errorReporter, /* compactChart */ true);
|
|
| 218 | - windChart.setVisible(false);
|
|
| 219 | - windChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces());
|
|
| 220 | - windChart.getEntryWidget().setTitle(stringMessages.windChart());
|
|
| 221 | - components.add(windChart);
|
|
| 222 | - editMarkPassingPanel = new EditMarkPassingsPanel(sailingService, selectedRaceIdentifier,
|
|
| 223 | - stringMessages, competitorSelectionProvider, errorReporter, timer);
|
|
| 224 | - editMarkPassingPanel.setLeaderboard(leaderboardPanel.getLeaderboard());
|
|
| 225 | - editMarkPassingPanel.getEntryWidget().setTitle(stringMessages.editMarkPassings());
|
|
| 226 | - components.add(editMarkPassingPanel);
|
|
| 227 | - boolean autoSelectMedia = getConfiguration().isAutoSelectMedia();
|
|
| 228 | - MediaPlayerManagerComponent mediaPlayerManagerComponent = new MediaPlayerManagerComponent(
|
|
| 229 | - selectedRaceIdentifier, raceTimesInfoProvider, timer, mediaService, userService, stringMessages,
|
|
| 230 | - errorReporter, userAgent, this, autoSelectMedia);
|
|
| 231 | - leaderboardAndMapViewer = new SideBySideComponentViewer(leaderboardPanel, raceMap, mediaPlayerManagerComponent, components, stringMessages, userService, editMarkPassingPanel);
|
|
| 232 | - componentViewers.add(leaderboardAndMapViewer);
|
|
| 233 | - for (ComponentViewer componentViewer : componentViewers) {
|
|
| 234 | - mainPanel.add(componentViewer.getViewerWidget());
|
|
| 235 | - }
|
|
| 236 | - boolean showLeaderboard = getConfiguration().isShowLeaderboard();
|
|
| 237 | - if (Document.get().getClientWidth() <= 1024) {
|
|
| 238 | - showLeaderboard = false;
|
|
| 239 | - }
|
|
| 240 | - setLeaderboardVisible(showLeaderboard);
|
|
| 241 | - setWindChartVisible(getConfiguration().isShowWindChart());
|
|
| 242 | - setCompetitorChartVisible(getConfiguration().isShowCompetitorsChart());
|
|
| 243 | - // make sure to load leaderboard data for filtering to work
|
|
| 244 | - if (!showLeaderboard) {
|
|
| 245 | - leaderboardPanel.setVisible(true);
|
|
| 246 | - leaderboardPanel.setVisible(false);
|
|
| 247 | - }
|
|
| 248 | - }
|
|
| 249 | -
|
|
| 250 | - @SuppressWarnings("unused")
|
|
| 251 | - private <SettingsType extends AbstractSettings> void addSettingsMenuItem(MenuBar settingsMenu, final Component<SettingsType> component) {
|
|
| 252 | - if (component.hasSettings()) {
|
|
| 253 | - settingsMenu.addItem(component.getLocalizedShortName(), new Command() {
|
|
| 254 | - public void execute() {
|
|
| 255 | - new SettingsDialog<SettingsType>(component, stringMessages).show();
|
|
| 256 | - }
|
|
| 257 | - });
|
|
| 258 | - }
|
|
| 259 | - }
|
|
| 260 | -
|
|
| 261 | - private LeaderboardPanel createLeaderboardPanel(String leaderboardName, String leaderboardGroupName, CompetitorFilterPanel competitorSearchTextBox) {
|
|
| 262 | - LeaderboardSettings leaderBoardSettings = LeaderboardSettingsFactory.getInstance()
|
|
| 263 | - .createNewSettingsForPlayMode(timer.getPlayMode(),
|
|
| 264 | - /* nameOfRaceToSort */ selectedRaceIdentifier.getRaceName(),
|
|
| 265 | - /* nameOfRaceColumnToShow */ null, /* nameOfRaceToShow */ selectedRaceIdentifier.getRaceName(),
|
|
| 266 | - new ExplicitRaceColumnSelectionWithPreselectedRace(selectedRaceIdentifier), /* showRegattaRank */ false,
|
|
| 267 | - /*showCompetitorSailIdColumn*/true, /*showCompetitorFullNameColumn*/true);
|
|
| 268 | - return new LeaderboardPanel(sailingService, asyncActionsExecutor, leaderBoardSettings, selectedRaceIdentifier != null, selectedRaceIdentifier,
|
|
| 269 | - competitorSelectionProvider, timer, leaderboardGroupName, leaderboardName, errorReporter, stringMessages,
|
|
| 270 | - userAgent, /* showRaceDetails */ true, competitorSearchTextBox,
|
|
| 271 | - /* showSelectionCheckbox */ true, raceTimesInfoProvider, /* autoExpandLastRaceColumn */ false,
|
|
| 272 | - /* don't adjust the timer's delay from the leaderboard; control it solely from the RaceTimesInfoProvider */ false,
|
|
| 273 | - /*autoApplyTopNFilter*/ false, /*showCompetitorFilterStatus*/ false);
|
|
| 274 | - }
|
|
| 275 | -
|
|
| 276 | - private void setComponentVisible(ComponentViewer componentViewer, Component<?> component, boolean visible) {
|
|
| 277 | - component.setVisible(visible);
|
|
| 278 | - componentViewer.forceLayout();
|
|
| 279 | - }
|
|
| 280 | -
|
|
| 281 | - /**
|
|
| 282 | - * Sets the collapsable panel for the leaderboard open or close, if in <code>CASCADE</code> view mode.<br />
|
|
| 283 | - * Displays or hides the leaderboard, if in <code>ONESCREEN</code> view mode.<br /><br />
|
|
| 284 | - *
|
|
| 285 | - * The race board should be completely rendered before this method is called, or a few exceptions could be thrown.
|
|
| 286 | - *
|
|
| 287 | - * @param visible <code>true</code> if the leaderboard shall be open/visible
|
|
| 288 | - */
|
|
| 289 | - public void setLeaderboardVisible(boolean visible) {
|
|
| 290 | - setComponentVisible(leaderboardAndMapViewer, leaderboardPanel, visible);
|
|
| 291 | - }
|
|
| 292 | -
|
|
| 293 | - /**
|
|
| 294 | - * Sets the collapsable panel for the wind chart open or close, if in <code>CASCADE</code> view mode.<br />
|
|
| 295 | - * Displays or hides the wind chart, if in <code>ONESCREEN</code> view mode.<br /><br />
|
|
| 296 | - *
|
|
| 297 | - * The race board should be completely rendered before this method is called, or a few exceptions could be thrown.
|
|
| 298 | - *
|
|
| 299 | - * @param visible <code>true</code> if the wind chart shall be open/visible
|
|
| 300 | - */
|
|
| 301 | - public void setWindChartVisible(boolean visible) {
|
|
| 302 | - setComponentVisible(leaderboardAndMapViewer, windChart, visible);
|
|
| 303 | - }
|
|
| 304 | -
|
|
| 305 | - /**
|
|
| 306 | - * Sets the collapsable panel for the competitor chart open or close, if in <code>CASCADE</code> view mode.<br />
|
|
| 307 | - * Displays or hides the competitor chart, if in <code>ONESCREEN</code> view mode.<br /><br />
|
|
| 308 | - *
|
|
| 309 | - * The race board should be completely rendered before this method is called, or a few exceptions could be thrown.
|
|
| 310 | - *
|
|
| 311 | - * @param visible <code>true</code> if the competitor chart shall be open/visible
|
|
| 312 | - */
|
|
| 313 | - public void setCompetitorChartVisible(boolean visible) {
|
|
| 314 | - setComponentVisible(leaderboardAndMapViewer, competitorChart, visible);
|
|
| 315 | - }
|
|
| 316 | -
|
|
| 317 | - public RaceTimePanel getTimePanel() {
|
|
| 318 | - return timePanel;
|
|
| 319 | - }
|
|
| 320 | -
|
|
| 321 | - protected SailingServiceAsync getSailingService() {
|
|
| 322 | - return sailingService;
|
|
| 323 | - }
|
|
| 324 | -
|
|
| 325 | - protected String getRaceBoardName() {
|
|
| 326 | - return raceBoardName;
|
|
| 327 | - }
|
|
| 328 | -
|
|
| 329 | - protected void setRaceBoardName(String raceBoardName) {
|
|
| 330 | - this.raceBoardName = raceBoardName;
|
|
| 331 | - }
|
|
| 332 | -
|
|
| 333 | - protected ErrorReporter getErrorReporter() {
|
|
| 334 | - return errorReporter;
|
|
| 335 | - }
|
|
| 336 | -
|
|
| 337 | - @Override
|
|
| 338 | - public void onRaceSelectionChange(List<RegattaAndRaceIdentifier> selectedRaces) {
|
|
| 339 | - }
|
|
| 340 | -
|
|
| 341 | - public RaceBoardViewConfiguration getConfiguration() {
|
|
| 342 | - return raceboardViewConfiguration;
|
|
| 343 | - }
|
|
| 344 | -
|
|
| 345 | - @Override
|
|
| 346 | - public void updatedLeaderboard(LeaderboardDTO leaderboard) {
|
|
| 347 | - leaderboardAndMapViewer.setLeftComponentWidth(leaderboardPanel.getContentPanel().getOffsetWidth());
|
|
| 348 | - editMarkPassingPanel.setLeaderboard(leaderboard);
|
|
| 349 | - }
|
|
| 350 | -
|
|
| 351 | - @Override
|
|
| 352 | - public void currentRaceSelected(RaceIdentifier raceIdentifier, RaceColumnDTO raceColumn) {
|
|
| 353 | - if (!currentRaceHasBeenSelectedOnce) {
|
|
| 354 | - FleetDTO fleet = raceColumn.getFleet(raceIdentifier);
|
|
| 355 | - String seriesName = raceColumn.getSeriesName();
|
|
| 356 | - if (LeaderboardNameConstants.DEFAULT_SERIES_NAME.equals(seriesName)) {
|
|
| 357 | - seriesName = "";
|
|
| 358 | - }
|
|
| 359 | - String fleetForRaceName = fleet==null?"":fleet.getName();
|
|
| 360 | - if (fleetForRaceName.equals(LeaderboardNameConstants.DEFAULT_FLEET_NAME)) {
|
|
| 361 | - fleetForRaceName = "";
|
|
| 362 | - } else {
|
|
| 363 | - fleetForRaceName = " - "+fleetForRaceName;
|
|
| 364 | - }
|
|
| 365 | - final Label raceNameLabel = new Label(stringMessages.race() + " " + raceColumn.getRaceColumnName());
|
|
| 366 | - raceNameLabel.setStyleName("RaceName-Label");
|
|
| 367 | - final Label raceAdditionalInformationLabel = new Label(seriesName + fleetForRaceName);
|
|
| 368 | - raceAdditionalInformationLabel.setStyleName("RaceSeriesAndFleet-Label");
|
|
| 369 | - raceInformationHeader.clear();
|
|
| 370 | - raceInformationHeader.add(raceNameLabel);
|
|
| 371 | - raceInformationHeader.add(raceAdditionalInformationLabel);
|
|
| 372 | - final Anchor regattaNameAnchor = new Anchor(raceIdentifier.getRegattaName());
|
|
| 373 | - if (eventId != null) {
|
|
| 374 | - // we don't use the EntryPointLinkFactory here, because of lacking support for Places
|
|
| 375 | - String debugParam = Window.Location.getParameter("gwt.codesvr");
|
|
| 376 | - String link = "/gwt/Home.html";
|
|
| 377 | - if (debugParam != null && !debugParam.isEmpty()) {
|
|
| 378 | - link += "?gwt.codesvr=" + debugParam;
|
|
| 379 | - }
|
|
| 380 | - link += "#EventPlace:eventId="+eventId.toString();
|
|
| 381 | - link += "&navigationTab=Regatta&leaderboardName=" + URLEncoder.encode(leaderboardName);
|
|
| 382 | - regattaNameAnchor.setHref(link);
|
|
| 383 | - } else {
|
|
| 384 | - String leaderboardGroupNameParam = Window.Location.getParameter("leaderboardGroupName");
|
|
| 385 | - if(leaderboardGroupNameParam != null) {
|
|
| 386 | - Map<String, String> leaderboardGroupLinkParameters = new HashMap<String, String>();
|
|
| 387 | - leaderboardGroupLinkParameters.put("showRaceDetails", "true");
|
|
| 388 | - leaderboardGroupLinkParameters.put("leaderboardGroupName", leaderboardGroupNameParam);
|
|
| 389 | - String leaderBoardGroupLink = EntryPointLinkFactory.createLeaderboardGroupLink(leaderboardGroupLinkParameters);
|
|
| 390 | - regattaNameAnchor.setHref(leaderBoardGroupLink);
|
|
| 391 | - } else {
|
|
| 392 | - // fallback
|
|
| 393 | - regattaNameAnchor.setHref("javascript:window.history.back();");
|
|
| 394 | - }
|
|
| 395 | - }
|
|
| 396 | - regattaNameAnchor.setStyleName("RegattaName-Anchor");
|
|
| 397 | -
|
|
| 398 | - // TODO: Strange behavior... check
|
|
| 399 | -// Window.addResizeHandler(new ResizeHandler() {
|
|
| 400 | -// @Override
|
|
| 401 | -// public void onResize(ResizeEvent event) {
|
|
| 402 | -// int headerPanelWidth = raceMap.getRightHeaderPanel().getOffsetWidth() - 150; // 150px is the width of the sapLogo
|
|
| 403 | -// int raceNameAndFleetLabelWidth = raceInformationHeader.getOffsetWidth();
|
|
| 404 | -// int regattaAnchorWidth = regattaNameAnchor.getOffsetWidth();
|
|
| 405 | -// boolean overlap = raceNameAndFleetLabelWidth + regattaAnchorWidth > headerPanelWidth;
|
|
| 406 | -// raceInformationHeader.setVisible(!overlap);
|
|
| 407 | -// }
|
|
| 408 | -// });
|
|
| 409 | -
|
|
| 410 | - Label raceTimeLabel = computeRaceInformation(raceColumn, fleet);
|
|
| 411 | - raceTimeLabel.setStyleName("RaceTime-Label");
|
|
| 412 | - regattaAndRaceTimeInformationHeader.clear();
|
|
| 413 | - regattaAndRaceTimeInformationHeader.add(regattaNameAnchor);
|
|
| 414 | - regattaAndRaceTimeInformationHeader.add(raceTimeLabel);
|
|
| 415 | - currentRaceHasBeenSelectedOnce = true;
|
|
| 416 | - }
|
|
| 417 | - }
|
|
| 418 | -
|
|
| 419 | - @Override
|
|
| 420 | - public UIObject getXPositionUiObject() {
|
|
| 421 | - return timePanel;
|
|
| 422 | - }
|
|
| 423 | -
|
|
| 424 | - @Override
|
|
| 425 | - public UIObject getYPositionUiObject() {
|
|
| 426 | - return timePanel;
|
|
| 427 | - }
|
|
| 428 | -
|
|
| 429 | - private Label computeRaceInformation(RaceColumnDTO raceColumn, FleetDTO fleet) {
|
|
| 430 | - Label raceInformationLabel = new Label();
|
|
| 431 | - raceInformationLabel.setStyleName("Race-Time-Label");
|
|
| 432 | - DateTimeFormat formatter = DateTimeFormat.getFormat("E d/M/y");
|
|
| 433 | - raceInformationLabel.setText(formatter.format(raceColumn.getStartDate(fleet)));
|
|
| 434 | - return raceInformationLabel;
|
|
| 435 | - }
|
|
| 436 | -}
|
|
| 437 | -
|
|
| 1 | +package com.sap.sailing.gwt.ui.raceboard; |
|
| 2 | + |
|
| 3 | +import java.util.ArrayList; |
|
| 4 | +import java.util.Collections; |
|
| 5 | +import java.util.HashMap; |
|
| 6 | +import java.util.List; |
|
| 7 | +import java.util.Map; |
|
| 8 | +import java.util.UUID; |
|
| 9 | + |
|
| 10 | +import com.google.gwt.core.client.Scheduler; |
|
| 11 | +import com.google.gwt.core.client.Scheduler.ScheduledCommand; |
|
| 12 | +import com.google.gwt.core.shared.GWT; |
|
| 13 | +import com.google.gwt.dom.client.Document; |
|
| 14 | +import com.google.gwt.dom.client.Style.Unit; |
|
| 15 | +import com.google.gwt.i18n.client.DateTimeFormat; |
|
| 16 | +import com.google.gwt.user.client.Command; |
|
| 17 | +import com.google.gwt.user.client.Window; |
|
| 18 | +import com.google.gwt.user.client.ui.Anchor; |
|
| 19 | +import com.google.gwt.user.client.ui.FlowPanel; |
|
| 20 | +import com.google.gwt.user.client.ui.Label; |
|
| 21 | +import com.google.gwt.user.client.ui.MenuBar; |
|
| 22 | +import com.google.gwt.user.client.ui.RequiresResize; |
|
| 23 | +import com.google.gwt.user.client.ui.SimplePanel; |
|
| 24 | +import com.google.gwt.user.client.ui.UIObject; |
|
| 25 | +import com.google.gwt.user.client.ui.Widget; |
|
| 26 | +import com.sap.sailing.domain.common.LeaderboardNameConstants; |
|
| 27 | +import com.sap.sailing.domain.common.RaceIdentifier; |
|
| 28 | +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; |
|
| 29 | +import com.sap.sailing.domain.common.dto.BoatDTO; |
|
| 30 | +import com.sap.sailing.domain.common.dto.CompetitorDTO; |
|
| 31 | +import com.sap.sailing.domain.common.dto.FleetDTO; |
|
| 32 | +import com.sap.sailing.domain.common.dto.LeaderboardDTO; |
|
| 33 | +import com.sap.sailing.domain.common.dto.RaceColumnDTO; |
|
| 34 | +import com.sap.sailing.domain.common.dto.RaceDTO; |
|
| 35 | +import com.sap.sailing.gwt.ui.client.CompetitorColorProvider; |
|
| 36 | +import com.sap.sailing.gwt.ui.client.CompetitorColorProviderImpl; |
|
| 37 | +import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; |
|
| 38 | +import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider; |
|
| 39 | +import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory; |
|
| 40 | +import com.sap.sailing.gwt.ui.client.GlobalNavigationPanel; |
|
| 41 | +import com.sap.sailing.gwt.ui.client.LeaderboardUpdateListener; |
|
| 42 | +import com.sap.sailing.gwt.ui.client.MediaServiceAsync; |
|
| 43 | +import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; |
|
| 44 | +import com.sap.sailing.gwt.ui.client.RaceSelectionProvider; |
|
| 45 | +import com.sap.sailing.gwt.ui.client.RaceTimePanel; |
|
| 46 | +import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider; |
|
| 47 | +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; |
|
| 48 | +import com.sap.sailing.gwt.ui.client.StringMessages; |
|
| 49 | +import com.sap.sailing.gwt.ui.client.media.MediaPlayerManagerComponent; |
|
| 50 | +import com.sap.sailing.gwt.ui.client.media.PopupPositionProvider; |
|
| 51 | +import com.sap.sailing.gwt.ui.client.shared.charts.EditMarkPassingsPanel; |
|
| 52 | +import com.sap.sailing.gwt.ui.client.shared.charts.MultiCompetitorRaceChart; |
|
| 53 | +import com.sap.sailing.gwt.ui.client.shared.charts.WindChart; |
|
| 54 | +import com.sap.sailing.gwt.ui.client.shared.charts.WindChartSettings; |
|
| 55 | +import com.sap.sailing.gwt.ui.client.shared.filter.FilterWithUI; |
|
| 56 | +import com.sap.sailing.gwt.ui.client.shared.filter.LeaderboardFetcher; |
|
| 57 | +import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMap; |
|
| 58 | +import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapResources; |
|
| 59 | +import com.sap.sailing.gwt.ui.leaderboard.CompetitorFilterPanel; |
|
| 60 | +import com.sap.sailing.gwt.ui.leaderboard.ExplicitRaceColumnSelectionWithPreselectedRace; |
|
| 61 | +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; |
|
| 62 | +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings; |
|
| 63 | +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettingsFactory; |
|
| 64 | +import com.sap.sse.common.filter.FilterSet; |
|
| 65 | +import com.sap.sse.common.settings.AbstractSettings; |
|
| 66 | +import com.sap.sse.gwt.client.ErrorReporter; |
|
| 67 | +import com.sap.sse.gwt.client.URLEncoder; |
|
| 68 | +import com.sap.sse.gwt.client.async.AsyncActionsExecutor; |
|
| 69 | +import com.sap.sse.gwt.client.player.TimeRangeWithZoomModel; |
|
| 70 | +import com.sap.sse.gwt.client.player.Timer; |
|
| 71 | +import com.sap.sse.gwt.client.shared.components.Component; |
|
| 72 | +import com.sap.sse.gwt.client.shared.components.ComponentViewer; |
|
| 73 | +import com.sap.sse.gwt.client.shared.components.SettingsDialog; |
|
| 74 | +import com.sap.sse.gwt.client.useragent.UserAgentDetails; |
|
| 75 | +import com.sap.sse.security.ui.client.UserService; |
|
| 76 | + |
|
| 77 | +/** |
|
| 78 | + * A view showing a list of components visualizing a race from the regattas announced by calls to {@link #fillRegattas(List)}. |
|
| 79 | + * The race selection is provided by a {@link RaceSelectionProvider} for which this is a {@link RaceSelectionChangeListener listener}. |
|
| 80 | + * {@link RaceIdentifier}-based race selection changes are converted to {@link RaceDTO} objects using the {@link #racesByIdentifier} |
|
| 81 | + * map maintained during {@link #fillRegattas(List)}. The race selection provider is expected to be single selection only. |
|
| 82 | + * |
|
| 83 | + * @author Frank Mittag, Axel Uhl (d043530) |
|
| 84 | + * |
|
| 85 | + */ |
|
| 86 | +public class RaceBoardPanel extends SimplePanel implements RaceSelectionChangeListener, |
|
| 87 | + LeaderboardUpdateListener, PopupPositionProvider, RequiresResize { |
|
| 88 | + private final SailingServiceAsync sailingService; |
|
| 89 | + private final MediaServiceAsync mediaService; |
|
| 90 | + private final UUID eventId; |
|
| 91 | + private final StringMessages stringMessages; |
|
| 92 | + private final ErrorReporter errorReporter; |
|
| 93 | + private final RaceBoardViewConfiguration raceboardViewConfiguration; |
|
| 94 | + private String raceBoardName; |
|
| 95 | + |
|
| 96 | + private final List<ComponentViewer> componentViewers; |
|
| 97 | + private RaceTimePanel timePanel; |
|
| 98 | + private final Timer timer; |
|
| 99 | + private final RaceSelectionProvider raceSelectionProvider; |
|
| 100 | + private final UserAgentDetails userAgent; |
|
| 101 | + private final CompetitorSelectionProvider competitorSelectionProvider; |
|
| 102 | + private final TimeRangeWithZoomModel timeRangeWithZoomModel; |
|
| 103 | + private final RegattaAndRaceIdentifier selectedRaceIdentifier; |
|
| 104 | + |
|
| 105 | + private final String leaderboardName; |
|
| 106 | + private final LeaderboardPanel leaderboardPanel; |
|
| 107 | + private WindChart windChart; |
|
| 108 | + private MultiCompetitorRaceChart competitorChart; |
|
| 109 | + private EditMarkPassingsPanel editMarkPassingPanel; |
|
| 110 | + |
|
| 111 | + /** |
|
| 112 | + * The component viewer in <code>ONESCREEN</code> view mode. <code>null</code> if in <code>CASCADE</code> view mode |
|
| 113 | + */ |
|
| 114 | + private SideBySideComponentViewer leaderboardAndMapViewer; |
|
| 115 | + |
|
| 116 | + private final AsyncActionsExecutor asyncActionsExecutor; |
|
| 117 | + |
|
| 118 | + private final RaceTimesInfoProvider raceTimesInfoProvider; |
|
| 119 | + private RaceMap raceMap; |
|
| 120 | + |
|
| 121 | + private final FlowPanel raceInformationHeader; |
|
| 122 | + private final FlowPanel regattaAndRaceTimeInformationHeader; |
|
| 123 | + private boolean currentRaceHasBeenSelectedOnce; |
|
| 124 | + |
|
| 125 | + private static final RaceMapResources raceMapResources = GWT.create(RaceMapResources.class); |
|
| 126 | + |
|
| 127 | + /** |
|
| 128 | + * @param eventId |
|
| 129 | + * an optional event that can be used for "back"-navigation in case the race board shows a race in the |
|
| 130 | + * context of an event; may be <code>null</code>. |
|
| 131 | + * @param isScreenLargeEnoughToOfferChartSupport |
|
| 132 | + * if the screen is large enough to display charts such as the competitor chart or the wind chart, a |
|
| 133 | + * padding is provided for the RaceTimePanel that aligns its right border with that of the charts, and |
|
| 134 | + * the charts are created. This decision is made once on startup in the {@link RaceBoardEntryPoint} class. |
|
| 135 | + */ |
|
| 136 | + public RaceBoardPanel(SailingServiceAsync sailingService, MediaServiceAsync mediaService, |
|
| 137 | + UserService userService, AsyncActionsExecutor asyncActionsExecutor, Map<CompetitorDTO, BoatDTO> competitorsAndTheirBoats, |
|
| 138 | + Timer timer, RaceSelectionProvider theRaceSelectionProvider, String leaderboardName, |
|
| 139 | + String leaderboardGroupName, UUID eventId, RaceBoardViewConfiguration raceboardViewConfiguration, |
|
| 140 | + ErrorReporter errorReporter, final StringMessages stringMessages, |
|
| 141 | + UserAgentDetails userAgent, RaceTimesInfoProvider raceTimesInfoProvider, boolean showMapControls, boolean isScreenLargeEnoughToOfferChartSupport) { |
|
| 142 | + this.sailingService = sailingService; |
|
| 143 | + this.mediaService = mediaService; |
|
| 144 | + this.stringMessages = stringMessages; |
|
| 145 | + this.raceboardViewConfiguration = raceboardViewConfiguration; |
|
| 146 | + this.raceSelectionProvider = theRaceSelectionProvider; |
|
| 147 | + this.raceTimesInfoProvider = raceTimesInfoProvider; |
|
| 148 | + this.errorReporter = errorReporter; |
|
| 149 | + this.userAgent = userAgent; |
|
| 150 | + this.timer = timer; |
|
| 151 | + this.eventId = eventId; |
|
| 152 | + this.currentRaceHasBeenSelectedOnce = false; |
|
| 153 | + this.leaderboardName = leaderboardName; |
|
| 154 | + raceSelectionProvider.addRaceSelectionChangeListener(this); |
|
| 155 | + selectedRaceIdentifier = raceSelectionProvider.getSelectedRaces().iterator().next(); |
|
| 156 | + this.setRaceBoardName(selectedRaceIdentifier.getRaceName()); |
|
| 157 | + this.asyncActionsExecutor = asyncActionsExecutor; |
|
| 158 | + FlowPanel mainPanel = new ResizableFlowPanel(); |
|
| 159 | + mainPanel.setSize("100%", "100%"); |
|
| 160 | + setWidget(mainPanel); |
|
| 161 | + raceInformationHeader = new FlowPanel(); |
|
| 162 | + raceInformationHeader.setStyleName("RegattaRaceInformation-Header"); |
|
| 163 | + regattaAndRaceTimeInformationHeader = new FlowPanel(); |
|
| 164 | + regattaAndRaceTimeInformationHeader.setStyleName("RegattaAndRaceTime-Header"); |
|
| 165 | + timeRangeWithZoomModel = new TimeRangeWithZoomModel(); |
|
| 166 | + componentViewers = new ArrayList<ComponentViewer>(); |
|
| 167 | + final CompetitorColorProvider colorProvider = new CompetitorColorProviderImpl(selectedRaceIdentifier, competitorsAndTheirBoats); |
|
| 168 | + competitorSelectionProvider = new CompetitorSelectionModel(/* hasMultiSelection */ true, colorProvider); |
|
| 169 | + |
|
| 170 | + raceMapResources.combinedWindPanelStyle().ensureInjected(); |
|
| 171 | + raceMap = new RaceMap(sailingService, asyncActionsExecutor, errorReporter, timer, |
|
| 172 | + competitorSelectionProvider, stringMessages, showMapControls, getConfiguration().isShowViewStreamlets(), getConfiguration().isShowViewStreamletColors(), getConfiguration().isShowViewSimulation(), |
|
| 173 | + selectedRaceIdentifier, raceMapResources.combinedWindPanelStyle(), /* showHeaderPanel */ true) { |
|
| 174 | + private static final String INDENT_SMALL_CONTROL_STYLE = "indentsmall"; |
|
| 175 | + private static final String INDENT_BIG_CONTROL_STYLE = "indentbig"; |
|
| 176 | + @Override |
|
| 177 | + public void onResize() { |
|
| 178 | + super.onResize(); |
|
| 179 | + Scheduler.get().scheduleDeferred(new ScheduledCommand() { |
|
| 180 | + @Override |
|
| 181 | + public void execute() { |
|
| 182 | + // Show/hide the leaderboard panels toggle button text based on the race map height |
|
| 183 | + leaderboardAndMapViewer.setLeftComponentToggleButtonTextVisibilityAndDraggerPosition(raceMap.getOffsetHeight() > 400); |
|
| 184 | + } |
|
| 185 | + }); |
|
| 186 | + } |
|
| 187 | + |
|
| 188 | + @Override |
|
| 189 | + protected String getLeftControlsIndentStyle() { |
|
| 190 | + // Calculate style name for left control indent based on race map height an leaderboard panel visibility |
|
| 191 | + if (raceMap.getOffsetHeight() <= 300) { |
|
| 192 | + return INDENT_BIG_CONTROL_STYLE; |
|
| 193 | + } |
|
| 194 | + if (leaderboardPanel.isVisible() && raceMap.getOffsetHeight() <= 500) { |
|
| 195 | + return INDENT_SMALL_CONTROL_STYLE; |
|
| 196 | + } |
|
| 197 | + return super.getLeftControlsIndentStyle(); |
|
| 198 | + } |
|
| 199 | + }; |
|
| 200 | + CompetitorFilterPanel competitorSearchTextBox = new CompetitorFilterPanel(competitorSelectionProvider, stringMessages, raceMap, |
|
| 201 | + new LeaderboardFetcher() { |
|
| 202 | + @Override |
|
| 203 | + public LeaderboardDTO getLeaderboard() { |
|
| 204 | + return leaderboardPanel.getLeaderboard(); |
|
| 205 | + } |
|
| 206 | + }, selectedRaceIdentifier); |
|
| 207 | + raceMap.getLeftHeaderPanel().add(raceInformationHeader); |
|
| 208 | + raceMap.getRightHeaderPanel().add(regattaAndRaceTimeInformationHeader); |
|
| 209 | + |
|
| 210 | + // Determine if the screen is large enough to initially display the leaderboard panel on the left side of the |
|
| 211 | + // map based on the initial screen width. Afterwards, the leaderboard panel visibility can be toggled as usual. |
|
| 212 | + boolean isScreenLargeEnoughToInitiallyDisplayLeaderboard = Document.get().getClientWidth() >= 1024; |
|
| 213 | + leaderboardPanel = createLeaderboardPanel(leaderboardName, leaderboardGroupName, competitorSearchTextBox, isScreenLargeEnoughToInitiallyDisplayLeaderboard); |
|
| 214 | + leaderboardPanel.setTitle(stringMessages.leaderboard()); |
|
| 215 | + leaderboardPanel.getElement().getStyle().setMarginLeft(6, Unit.PX); |
|
| 216 | + leaderboardPanel.getElement().getStyle().setMarginTop(10, Unit.PX); |
|
| 217 | + createOneScreenView(leaderboardName, leaderboardGroupName, eventId, mainPanel, showMapControls, isScreenLargeEnoughToOfferChartSupport, isScreenLargeEnoughToInitiallyDisplayLeaderboard, raceMap, userService); // initializes the raceMap field |
|
| 218 | + leaderboardPanel.addLeaderboardUpdateListener(this); |
|
| 219 | + // in case the URL configuration contains the name of a competitors filter set we try to activate it |
|
| 220 | + // FIXME the competitorsFilterSets has now moved to CompetitorSearchTextBox (which should probably be renamed); pass on the parameters to the LeaderboardPanel and see what it does with it |
|
| 221 | + if (raceboardViewConfiguration.getActiveCompetitorsFilterSetName() != null) { |
|
| 222 | + for (FilterSet<CompetitorDTO, FilterWithUI<CompetitorDTO>> filterSet : competitorSearchTextBox.getCompetitorsFilterSets() |
|
| 223 | + .getFilterSets()) { |
|
| 224 | + if (filterSet.getName().equals(raceboardViewConfiguration.getActiveCompetitorsFilterSetName())) { |
|
| 225 | + competitorSearchTextBox.getCompetitorsFilterSets().setActiveFilterSet(filterSet); |
|
| 226 | + break; |
|
| 227 | + } |
|
| 228 | + } |
|
| 229 | + } |
|
| 230 | + timePanel = new RaceTimePanel(timer, timeRangeWithZoomModel, stringMessages, raceTimesInfoProvider, |
|
| 231 | + raceboardViewConfiguration.isCanReplayDuringLiveRaces(), isScreenLargeEnoughToOfferChartSupport); |
|
| 232 | + timeRangeWithZoomModel.addTimeZoomChangeListener(timePanel); |
|
| 233 | + raceTimesInfoProvider.addRaceTimesInfoProviderListener(timePanel); |
|
| 234 | + raceSelectionProvider.addRaceSelectionChangeListener(timePanel); |
|
| 235 | + timePanel.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces()); |
|
| 236 | + } |
|
| 237 | + |
|
| 238 | + /** |
|
| 239 | + * @param event |
|
| 240 | + * an optional event; may be <code>null</code> or else can be used to show some context information in |
|
| 241 | + * the {@link GlobalNavigationPanel}. |
|
| 242 | + * @param isScreenLargeEnoughToOfferChartSupport |
|
| 243 | + * if the screen is large enough to display charts such as the competitor chart or the wind chart, a |
|
| 244 | + * padding is provided for the RaceTimePanel that aligns its right border with that of the charts, and |
|
| 245 | + * the charts are created. |
|
| 246 | + * @param isScreenLargeEnoughToInitiallyDisplayLeaderboard TODO |
|
| 247 | + */ |
|
| 248 | + private void createOneScreenView(String leaderboardName, String leaderboardGroupName, UUID event, FlowPanel mainPanel, |
|
| 249 | + boolean showMapControls, boolean isScreenLargeEnoughToOfferChartSupport, boolean isScreenLargeEnoughToInitiallyDisplayLeaderboard, RaceMap raceMap, UserService userService) { |
|
| 250 | + // create the default leaderboard and select the right race |
|
| 251 | + raceTimesInfoProvider.addRaceTimesInfoProviderListener(raceMap); |
|
| 252 | + raceMap.onRaceSelectionChange(Collections.singletonList(selectedRaceIdentifier)); |
|
| 253 | + List<Component<?>> components = new ArrayList<Component<?>>(); |
|
| 254 | + if (isScreenLargeEnoughToOfferChartSupport) { |
|
| 255 | + competitorChart = new MultiCompetitorRaceChart(sailingService, asyncActionsExecutor, competitorSelectionProvider, raceSelectionProvider, |
|
| 256 | + timer, timeRangeWithZoomModel, stringMessages, errorReporter, true, true, leaderboardGroupName, leaderboardName); |
|
| 257 | + competitorChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces()); |
|
| 258 | + competitorChart.getEntryWidget().setTitle(stringMessages.competitorCharts()); |
|
| 259 | + competitorChart.setVisible(false); |
|
| 260 | + components.add(competitorChart); |
|
| 261 | + windChart = new WindChart(sailingService, raceSelectionProvider, timer, timeRangeWithZoomModel, new WindChartSettings(), |
|
| 262 | + stringMessages, asyncActionsExecutor, errorReporter, /* compactChart */ true); |
|
| 263 | + windChart.setVisible(false); |
|
| 264 | + windChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces()); |
|
| 265 | + windChart.getEntryWidget().setTitle(stringMessages.windChart()); |
|
| 266 | + components.add(windChart); |
|
| 267 | + editMarkPassingPanel = new EditMarkPassingsPanel(sailingService, selectedRaceIdentifier, |
|
| 268 | + stringMessages, competitorSelectionProvider, errorReporter, timer); |
|
| 269 | + editMarkPassingPanel.setLeaderboard(leaderboardPanel.getLeaderboard()); |
|
| 270 | + editMarkPassingPanel.getEntryWidget().setTitle(stringMessages.editMarkPassings()); |
|
| 271 | + components.add(editMarkPassingPanel); |
|
| 272 | + } |
|
| 273 | + boolean autoSelectMedia = getConfiguration().isAutoSelectMedia(); |
|
| 274 | + MediaPlayerManagerComponent mediaPlayerManagerComponent = new MediaPlayerManagerComponent( |
|
| 275 | + selectedRaceIdentifier, raceTimesInfoProvider, timer, mediaService, userService, stringMessages, |
|
| 276 | + errorReporter, userAgent, this, autoSelectMedia); |
|
| 277 | + leaderboardAndMapViewer = new SideBySideComponentViewer(leaderboardPanel, raceMap, mediaPlayerManagerComponent, components, stringMessages, userService, editMarkPassingPanel); |
|
| 278 | + componentViewers.add(leaderboardAndMapViewer); |
|
| 279 | + for (ComponentViewer componentViewer : componentViewers) { |
|
| 280 | + mainPanel.add(componentViewer.getViewerWidget()); |
|
| 281 | + } |
|
| 282 | + boolean showLeaderboard = getConfiguration().isShowLeaderboard() && isScreenLargeEnoughToInitiallyDisplayLeaderboard; |
|
| 283 | + setLeaderboardVisible(showLeaderboard); |
|
| 284 | + if (isScreenLargeEnoughToOfferChartSupport) { |
|
| 285 | + setWindChartVisible(getConfiguration().isShowWindChart()); |
|
| 286 | + setCompetitorChartVisible(getConfiguration().isShowCompetitorsChart()); |
|
| 287 | + } |
|
| 288 | + // make sure to load leaderboard data for filtering to work |
|
| 289 | + if (!showLeaderboard) { |
|
| 290 | + leaderboardPanel.setVisible(true); |
|
| 291 | + leaderboardPanel.setVisible(false); |
|
| 292 | + } |
|
| 293 | + } |
|
| 294 | + |
|
| 295 | + @SuppressWarnings("unused") |
|
| 296 | + private <SettingsType extends AbstractSettings> void addSettingsMenuItem(MenuBar settingsMenu, final Component<SettingsType> component) { |
|
| 297 | + if (component.hasSettings()) { |
|
| 298 | + settingsMenu.addItem(component.getLocalizedShortName(), new Command() { |
|
| 299 | + public void execute() { |
|
| 300 | + new SettingsDialog<SettingsType>(component, stringMessages).show(); |
|
| 301 | + } |
|
| 302 | + }); |
|
| 303 | + } |
|
| 304 | + } |
|
| 305 | + |
|
| 306 | + private LeaderboardPanel createLeaderboardPanel(String leaderboardName, String leaderboardGroupName, |
|
| 307 | + CompetitorFilterPanel competitorSearchTextBox, boolean isScreenLargeEnoughToInitiallyDisplayLeaderboard) { |
|
| 308 | + LeaderboardSettings leaderBoardSettings = LeaderboardSettingsFactory.getInstance() |
|
| 309 | + .createNewSettingsForPlayMode(timer.getPlayMode(), |
|
| 310 | + /* nameOfRaceToSort */ selectedRaceIdentifier.getRaceName(), |
|
| 311 | + /* nameOfRaceColumnToShow */ null, /* nameOfRaceToShow */ selectedRaceIdentifier.getRaceName(), |
|
| 312 | + new ExplicitRaceColumnSelectionWithPreselectedRace(selectedRaceIdentifier), /* showRegattaRank */ false, |
|
| 313 | + /*showCompetitorSailIdColumn*/true, |
|
| 314 | + /* don't showCompetitorFullNameColumn in case screen is so small that we don't |
|
| 315 | + * even display the leaderboard initially */ isScreenLargeEnoughToInitiallyDisplayLeaderboard); |
|
| 316 | + return new LeaderboardPanel(sailingService, asyncActionsExecutor, leaderBoardSettings, selectedRaceIdentifier != null, selectedRaceIdentifier, |
|
| 317 | + competitorSelectionProvider, timer, leaderboardGroupName, leaderboardName, errorReporter, stringMessages, |
|
| 318 | + userAgent, /* showRaceDetails */ true, competitorSearchTextBox, |
|
| 319 | + /* showSelectionCheckbox */ true, raceTimesInfoProvider, /* autoExpandLastRaceColumn */ false, |
|
| 320 | + /* don't adjust the timer's delay from the leaderboard; control it solely from the RaceTimesInfoProvider */ false, |
|
| 321 | + /*autoApplyTopNFilter*/ false, /*showCompetitorFilterStatus*/ false); |
|
| 322 | + } |
|
| 323 | + |
|
| 324 | + private void setComponentVisible(ComponentViewer componentViewer, Component<?> component, boolean visible) { |
|
| 325 | + component.setVisible(visible); |
|
| 326 | + componentViewer.forceLayout(); |
|
| 327 | + } |
|
| 328 | + |
|
| 329 | + /** |
|
| 330 | + * Sets the collapsable panel for the leaderboard open or close, if in <code>CASCADE</code> view mode.<br /> |
|
| 331 | + * Displays or hides the leaderboard, if in <code>ONESCREEN</code> view mode.<br /><br /> |
|
| 332 | + * |
|
| 333 | + * The race board should be completely rendered before this method is called, or a few exceptions could be thrown. |
|
| 334 | + * |
|
| 335 | + * @param visible <code>true</code> if the leaderboard shall be open/visible |
|
| 336 | + */ |
|
| 337 | + public void setLeaderboardVisible(boolean visible) { |
|
| 338 | + setComponentVisible(leaderboardAndMapViewer, leaderboardPanel, visible); |
|
| 339 | + } |
|
| 340 | + |
|
| 341 | + /** |
|
| 342 | + * Sets the collapsable panel for the wind chart open or close, if in <code>CASCADE</code> view mode.<br /> |
|
| 343 | + * Displays or hides the wind chart, if in <code>ONESCREEN</code> view mode.<br /><br /> |
|
| 344 | + * |
|
| 345 | + * The race board should be completely rendered before this method is called, or a few exceptions could be thrown. |
|
| 346 | + * |
|
| 347 | + * @param visible <code>true</code> if the wind chart shall be open/visible |
|
| 348 | + */ |
|
| 349 | + public void setWindChartVisible(boolean visible) { |
|
| 350 | + setComponentVisible(leaderboardAndMapViewer, windChart, visible); |
|
| 351 | + } |
|
| 352 | + |
|
| 353 | + /** |
|
| 354 | + * Sets the collapsable panel for the competitor chart open or close, if in <code>CASCADE</code> view mode.<br /> |
|
| 355 | + * Displays or hides the competitor chart, if in <code>ONESCREEN</code> view mode.<br /><br /> |
|
| 356 | + * |
|
| 357 | + * The race board should be completely rendered before this method is called, or a few exceptions could be thrown. |
|
| 358 | + * |
|
| 359 | + * @param visible <code>true</code> if the competitor chart shall be open/visible |
|
| 360 | + */ |
|
| 361 | + public void setCompetitorChartVisible(boolean visible) { |
|
| 362 | + setComponentVisible(leaderboardAndMapViewer, competitorChart, visible); |
|
| 363 | + } |
|
| 364 | + |
|
| 365 | + public RaceTimePanel getTimePanel() { |
|
| 366 | + return timePanel; |
|
| 367 | + } |
|
| 368 | + |
|
| 369 | + protected SailingServiceAsync getSailingService() { |
|
| 370 | + return sailingService; |
|
| 371 | + } |
|
| 372 | + |
|
| 373 | + protected String getRaceBoardName() { |
|
| 374 | + return raceBoardName; |
|
| 375 | + } |
|
| 376 | + |
|
| 377 | + protected void setRaceBoardName(String raceBoardName) { |
|
| 378 | + this.raceBoardName = raceBoardName; |
|
| 379 | + } |
|
| 380 | + |
|
| 381 | + protected ErrorReporter getErrorReporter() { |
|
| 382 | + return errorReporter; |
|
| 383 | + } |
|
| 384 | + |
|
| 385 | + @Override |
|
| 386 | + public void onRaceSelectionChange(List<RegattaAndRaceIdentifier> selectedRaces) { |
|
| 387 | + } |
|
| 388 | + |
|
| 389 | + public RaceBoardViewConfiguration getConfiguration() { |
|
| 390 | + return raceboardViewConfiguration; |
|
| 391 | + } |
|
| 392 | + |
|
| 393 | + @Override |
|
| 394 | + public void updatedLeaderboard(LeaderboardDTO leaderboard) { |
|
| 395 | + leaderboardAndMapViewer.setLeftComponentWidth(leaderboardPanel.getContentPanel().getOffsetWidth()); |
|
| 396 | + if (editMarkPassingPanel != null) { |
|
| 397 | + editMarkPassingPanel.setLeaderboard(leaderboard); |
|
| 398 | + } |
|
| 399 | + } |
|
| 400 | + |
|
| 401 | + @Override |
|
| 402 | + public void currentRaceSelected(RaceIdentifier raceIdentifier, RaceColumnDTO raceColumn) { |
|
| 403 | + if (!currentRaceHasBeenSelectedOnce) { |
|
| 404 | + FleetDTO fleet = raceColumn.getFleet(raceIdentifier); |
|
| 405 | + String seriesName = raceColumn.getSeriesName(); |
|
| 406 | + if (LeaderboardNameConstants.DEFAULT_SERIES_NAME.equals(seriesName)) { |
|
| 407 | + seriesName = ""; |
|
| 408 | + } |
|
| 409 | + String fleetForRaceName = fleet==null?"":fleet.getName(); |
|
| 410 | + if (fleetForRaceName.equals(LeaderboardNameConstants.DEFAULT_FLEET_NAME)) { |
|
| 411 | + fleetForRaceName = ""; |
|
| 412 | + } else { |
|
| 413 | + fleetForRaceName = (seriesName.isEmpty() ? "" : " - ") + fleetForRaceName; |
|
| 414 | + } |
|
| 415 | + final Label raceNameLabel = new Label(stringMessages.race() + " " + raceColumn.getRaceColumnName()); |
|
| 416 | + raceNameLabel.setStyleName("RaceName-Label"); |
|
| 417 | + final Label raceAdditionalInformationLabel = new Label(seriesName + fleetForRaceName); |
|
| 418 | + raceAdditionalInformationLabel.setStyleName("RaceSeriesAndFleet-Label"); |
|
| 419 | + raceInformationHeader.clear(); |
|
| 420 | + raceInformationHeader.add(raceNameLabel); |
|
| 421 | + raceInformationHeader.add(raceAdditionalInformationLabel); |
|
| 422 | + final Anchor regattaNameAnchor = new Anchor(raceIdentifier.getRegattaName()); |
|
| 423 | + regattaNameAnchor.setTitle(raceIdentifier.getRegattaName()); |
|
| 424 | + if (eventId != null) { |
|
| 425 | + // we don't use the EntryPointLinkFactory here, because of lacking support for Places |
|
| 426 | + String debugParam = Window.Location.getParameter("gwt.codesvr"); |
|
| 427 | + String link = "/gwt/Home.html"; |
|
| 428 | + if (debugParam != null && !debugParam.isEmpty()) { |
|
| 429 | + link += "?gwt.codesvr=" + debugParam; |
|
| 430 | + } |
|
| 431 | + link += "#EventPlace:eventId="+eventId.toString(); |
|
| 432 | + link += "&navigationTab=Regatta&leaderboardName=" + URLEncoder.encode(leaderboardName); |
|
| 433 | + regattaNameAnchor.setHref(link); |
|
| 434 | + } else { |
|
| 435 | + String leaderboardGroupNameParam = Window.Location.getParameter("leaderboardGroupName"); |
|
| 436 | + if(leaderboardGroupNameParam != null) { |
|
| 437 | + Map<String, String> leaderboardGroupLinkParameters = new HashMap<String, String>(); |
|
| 438 | + leaderboardGroupLinkParameters.put("showRaceDetails", "true"); |
|
| 439 | + leaderboardGroupLinkParameters.put("leaderboardGroupName", leaderboardGroupNameParam); |
|
| 440 | + String leaderBoardGroupLink = EntryPointLinkFactory.createLeaderboardGroupLink(leaderboardGroupLinkParameters); |
|
| 441 | + regattaNameAnchor.setHref(leaderBoardGroupLink); |
|
| 442 | + } else { |
|
| 443 | + // fallback |
|
| 444 | + regattaNameAnchor.setHref("javascript:window.history.back();"); |
|
| 445 | + } |
|
| 446 | + } |
|
| 447 | + regattaNameAnchor.setStyleName("RegattaName-Anchor"); |
|
| 448 | + Label raceTimeLabel = computeRaceInformation(raceColumn, fleet); |
|
| 449 | + raceTimeLabel.setStyleName("RaceTime-Label"); |
|
| 450 | + regattaAndRaceTimeInformationHeader.clear(); |
|
| 451 | + regattaAndRaceTimeInformationHeader.add(regattaNameAnchor); |
|
| 452 | + regattaAndRaceTimeInformationHeader.add(raceTimeLabel); |
|
| 453 | + currentRaceHasBeenSelectedOnce = true; |
|
| 454 | + } |
|
| 455 | + } |
|
| 456 | + |
|
| 457 | + @Override |
|
| 458 | + public UIObject getXPositionUiObject() { |
|
| 459 | + return timePanel; |
|
| 460 | + } |
|
| 461 | + |
|
| 462 | + @Override |
|
| 463 | + public UIObject getYPositionUiObject() { |
|
| 464 | + return timePanel; |
|
| 465 | + } |
|
| 466 | + |
|
| 467 | + private Label computeRaceInformation(RaceColumnDTO raceColumn, FleetDTO fleet) { |
|
| 468 | + Label raceInformationLabel = new Label(); |
|
| 469 | + raceInformationLabel.setStyleName("Race-Time-Label"); |
|
| 470 | + DateTimeFormat formatter = DateTimeFormat.getFormat("E d/M/y"); |
|
| 471 | + raceInformationLabel.setText(formatter.format(raceColumn.getStartDate(fleet))); |
|
| 472 | + return raceInformationLabel; |
|
| 473 | + } |
|
| 474 | + |
|
| 475 | + @Override |
|
| 476 | + public void onResize() { |
|
| 477 | + Widget child = getWidget(); |
|
| 478 | + if (child != null && child instanceof RequiresResize) { |
|
| 479 | + ((RequiresResize) child).onResize(); |
|
| 480 | + } |
|
| 481 | + } |
|
| 482 | +} |
|
| 483 | + |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/ResizableFlowPanel.java
| ... | ... | @@ -0,0 +1,23 @@ |
| 1 | +package com.sap.sailing.gwt.ui.raceboard; |
|
| 2 | + |
|
| 3 | +import com.google.gwt.user.client.ui.FlowPanel; |
|
| 4 | +import com.google.gwt.user.client.ui.RequiresResize; |
|
| 5 | +import com.google.gwt.user.client.ui.Widget; |
|
| 6 | +import com.google.gwt.user.client.ui.WidgetCollection; |
|
| 7 | + |
|
| 8 | +/** |
|
| 9 | + * Utility class extending {@link FlowPanel} and implementing {@link RequiresResize} interface. All |
|
| 10 | + * {@link #getChildren() children} also implementing {@link RequiresResize} are informed by calling their |
|
| 11 | + * {@link RequiresResize#onResize()} method. |
|
| 12 | + */ |
|
| 13 | +class ResizableFlowPanel extends FlowPanel implements RequiresResize { |
|
| 14 | + @Override |
|
| 15 | + public void onResize() { |
|
| 16 | + WidgetCollection children = getChildren(); |
|
| 17 | + for (Widget widget : children) { |
|
| 18 | + if (widget instanceof RequiresResize) { |
|
| 19 | + ((RequiresResize) widget).onResize(); |
|
| 20 | + } |
|
| 21 | + } |
|
| 22 | + } |
|
| 23 | +} |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SideBySideComponentViewer.java
| ... | ... | @@ -1,305 +1,334 @@ |
| 1 | -package com.sap.sailing.gwt.ui.raceboard;
|
|
| 2 | -
|
|
| 3 | -import java.util.ArrayList;
|
|
| 4 | -import java.util.List;
|
|
| 5 | -
|
|
| 6 | -import com.google.gwt.dom.client.Style.Unit;
|
|
| 7 | -import com.google.gwt.event.dom.client.ClickEvent;
|
|
| 8 | -import com.google.gwt.event.dom.client.ClickHandler;
|
|
| 9 | -import com.google.gwt.user.client.ui.AbsolutePanel;
|
|
| 10 | -import com.google.gwt.user.client.ui.Button;
|
|
| 11 | -import com.google.gwt.user.client.ui.DockLayoutPanel.Direction;
|
|
| 12 | -import com.google.gwt.user.client.ui.LayoutPanel;
|
|
| 13 | -import com.google.gwt.user.client.ui.Panel;
|
|
| 14 | -import com.google.gwt.user.client.ui.RequiresResize;
|
|
| 15 | -import com.google.gwt.user.client.ui.ScrollPanel;
|
|
| 16 | -import com.google.gwt.user.client.ui.Widget;
|
|
| 17 | -import com.google.gwt.user.client.ui.WidgetCollection;
|
|
| 18 | -import com.sap.sailing.domain.common.security.Roles;
|
|
| 19 | -import com.sap.sailing.gwt.ui.client.StringMessages;
|
|
| 20 | -import com.sap.sailing.gwt.ui.client.media.MediaManagementControl;
|
|
| 21 | -import com.sap.sailing.gwt.ui.client.media.MediaPlayerManager;
|
|
| 22 | -import com.sap.sailing.gwt.ui.client.media.MediaPlayerManager.PlayerChangeListener;
|
|
| 23 | -import com.sap.sailing.gwt.ui.client.media.MediaPlayerManagerComponent;
|
|
| 24 | -import com.sap.sailing.gwt.ui.client.media.MediaSingleSelectionControl;
|
|
| 25 | -import com.sap.sailing.gwt.ui.client.shared.charts.EditMarkPassingsPanel;
|
|
| 26 | -import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel;
|
|
| 27 | -import com.sap.sse.common.Util.Pair;
|
|
| 28 | -import com.sap.sse.common.settings.AbstractSettings;
|
|
| 29 | -import com.sap.sse.gwt.client.shared.components.Component;
|
|
| 30 | -import com.sap.sse.gwt.client.shared.components.ComponentViewer;
|
|
| 31 | -import com.sap.sse.gwt.client.shared.components.SettingsDialog;
|
|
| 32 | -import com.sap.sse.security.shared.DefaultRoles;
|
|
| 33 | -import com.sap.sse.security.ui.client.UserService;
|
|
| 34 | -import com.sap.sse.security.ui.client.UserStatusEventHandler;
|
|
| 35 | -import com.sap.sse.security.ui.shared.UserDTO;
|
|
| 36 | -
|
|
| 37 | -/**
|
|
| 38 | - * Component Viewer that uses a {@link TouchSplitLayoutPanel} to display its components.
|
|
| 39 | - *
|
|
| 40 | - * TODO: Refactor to make sure it is really only performing operations that are related to view. Currently it is digging
|
|
| 41 | - * too deep into components and setting titles or even creating video buttons.
|
|
| 42 | - */
|
|
| 43 | -public class SideBySideComponentViewer implements ComponentViewer, UserStatusEventHandler {
|
|
| 44 | -
|
|
| 45 | - private static final int DEFAULT_SOUTH_SPLIT_PANEL_HEIGHT = 200;
|
|
| 46 | - private final int MIN_LEADERBOARD_WIDTH = 432; // works well for 505 and ESS
|
|
| 47 | -
|
|
| 48 | - /**
|
|
| 49 | - * Absolute Panel that informs its children about a resize
|
|
| 50 | - */
|
|
| 51 | - class ResizableAbsolutePanel extends AbsolutePanel implements RequiresResize {
|
|
| 52 | - public void onResize() {
|
|
| 53 | - WidgetCollection children = getChildren();
|
|
| 54 | - for (Widget widget : children) {
|
|
| 55 | - if (widget instanceof RequiresResize) {
|
|
| 56 | - ((RequiresResize) widget).onResize();
|
|
| 57 | - }
|
|
| 58 | - }
|
|
| 59 | - }
|
|
| 60 | - }
|
|
| 61 | -
|
|
| 62 | - private final LeaderboardPanel leftComponent;
|
|
| 63 | - private final Component<?> rightComponent;
|
|
| 64 | - private final List<Component<?>> components;
|
|
| 65 | - private final ScrollPanel leftScrollPanel;
|
|
| 66 | - private final StringMessages stringMessages;
|
|
| 67 | - private final Button mediaSelectionButton;
|
|
| 68 | - private final Button mediaManagementButton;
|
|
| 69 | - private final EditMarkPassingsPanel markPassingsPanel;
|
|
| 70 | -
|
|
| 71 | - private LayoutPanel mainPanel;
|
|
| 72 | -
|
|
| 73 | - private TouchSplitLayoutPanel splitLayoutPanel;
|
|
| 74 | - private int savedSplitPosition = -1;
|
|
| 75 | - private boolean layoutForLeftComponentForcedOnce = false;
|
|
| 76 | -
|
|
| 77 | - public SideBySideComponentViewer(final LeaderboardPanel leftComponentP, final Component<?> rightComponentP,
|
|
| 78 | - final MediaPlayerManagerComponent mediaPlayerManagerComponent, List<Component<?>> components,
|
|
| 79 | - final StringMessages stringMessages, UserService userService, EditMarkPassingsPanel markPassingsPanel) {
|
|
| 80 | - this.stringMessages = stringMessages;
|
|
| 81 | - this.leftComponent = leftComponentP;
|
|
| 82 | - this.rightComponent = rightComponentP;
|
|
| 83 | - this.components = components;
|
|
| 84 | - this.mediaSelectionButton = createMediaSelectionButton(mediaPlayerManagerComponent);
|
|
| 85 | - this.mediaManagementButton = createMediaManagementButton(mediaPlayerManagerComponent);
|
|
| 86 | - this.markPassingsPanel = markPassingsPanel;
|
|
| 87 | -
|
|
| 88 | - userService.addUserStatusEventHandler(this);
|
|
| 89 | -
|
|
| 90 | - mediaPlayerManagerComponent.setPlayerChangeListener(new PlayerChangeListener() {
|
|
| 91 | -
|
|
| 92 | - public void notifyStateChange(){
|
|
| 93 | - String caption;
|
|
| 94 | - String tooltip;
|
|
| 95 | - switch (mediaPlayerManagerComponent.getAssignedMediaTracks().size()) {
|
|
| 96 | - case 0:
|
|
| 97 | - caption = stringMessages.mediaNoVideosCaption();
|
|
| 98 | - tooltip = caption;
|
|
| 99 | - mediaSelectionButton.setVisible(false);
|
|
| 100 | - break;
|
|
| 101 | - case 1:
|
|
| 102 | - mediaSelectionButton.setVisible(true);
|
|
| 103 | - if(mediaPlayerManagerComponent.isPlaying()){
|
|
| 104 | - caption = stringMessages.mediaHideVideoCaption();
|
|
| 105 | - tooltip = stringMessages.mediaHideVideoTooltip();
|
|
| 106 | - }
|
|
| 107 | - else{
|
|
| 108 | - caption = stringMessages.mediaShowVideoCaption();
|
|
| 109 | - tooltip = stringMessages.mediaShowVideoTooltip(mediaPlayerManagerComponent.getAssignedMediaTracks().iterator().next().title);
|
|
| 110 | - }
|
|
| 111 | - break;
|
|
| 112 | - default:
|
|
| 113 | - mediaSelectionButton.setVisible(true);
|
|
| 114 | - caption = stringMessages.mediaSelectVideoCaption(mediaPlayerManagerComponent.getAssignedMediaTracks().size());
|
|
| 115 | - tooltip = stringMessages.mediaSelectVideoTooltip();
|
|
| 116 | - break;
|
|
| 117 | - }
|
|
| 118 | - if (mediaPlayerManagerComponent.isPlaying()) {
|
|
| 119 | - mediaSelectionButton.addStyleDependentName("mediaplaying");
|
|
| 120 | - } else {
|
|
| 121 | - mediaSelectionButton.removeStyleDependentName("mediaplaying");
|
|
| 122 | - }
|
|
| 123 | - mediaSelectionButton.setText(caption);
|
|
| 124 | - mediaSelectionButton.setTitle(tooltip);
|
|
| 125 | - mediaManagementButton.setVisible(mediaPlayerManagerComponent.allowsEditing());
|
|
| 126 | - }
|
|
| 127 | -
|
|
| 128 | - });
|
|
| 129 | - this.leftScrollPanel = new ScrollPanel();
|
|
| 130 | - this.leftScrollPanel.add(leftComponentP.getEntryWidget());
|
|
| 131 | - this.leftScrollPanel.setTitle(leftComponentP.getEntryWidget().getTitle());
|
|
| 132 | - this.mainPanel = new LayoutPanel();
|
|
| 133 | - this.mainPanel.setSize("100%", "100%");
|
|
| 134 | - this.mainPanel.getElement().getStyle().setMarginTop(-12, Unit.PX);
|
|
| 135 | - this.mainPanel.setStyleName("SideBySideComponentViewer-MainPanel");
|
|
| 136 | - this.splitLayoutPanel = new TouchSplitLayoutPanel(/* horizontal splitter width */3, /* vertical splitter height */
|
|
| 137 | - 25);
|
|
| 138 | - this.mainPanel.add(splitLayoutPanel);
|
|
| 139 | -
|
|
| 140 | - // initialize components - they need to be added before other widgets to get the right width
|
|
| 141 | - initializeComponents();
|
|
| 142 | -
|
|
| 143 | - // initialize the leaderboard component
|
|
| 144 | - savedSplitPosition = MIN_LEADERBOARD_WIDTH;
|
|
| 145 | - splitLayoutPanel.insert(leftScrollPanel, leftComponent, Direction.WEST, savedSplitPosition);
|
|
| 146 | -
|
|
| 147 | - // create a panel that will contain the horizontal toggle buttons
|
|
| 148 | - ResizableAbsolutePanel panelForMapAndHorizontalToggleButtons = new ResizableAbsolutePanel();
|
|
| 149 | - panelForMapAndHorizontalToggleButtons.add(rightComponent.getEntryWidget());
|
|
| 150 | - splitLayoutPanel.insert(panelForMapAndHorizontalToggleButtons, rightComponent, Direction.CENTER, 0);
|
|
| 151 | -
|
|
| 152 | - // add additional toggle buttons panel that currently only contains the video button
|
|
| 153 | - List<Pair<Button, String>> additionalVerticalButtons = new ArrayList<Pair<Button, String>>();
|
|
| 154 | - additionalVerticalButtons.add(new Pair<Button, String>(mediaSelectionButton,
|
|
| 155 | - mediaPlayerManagerComponent.getDependentCssClassName()));
|
|
| 156 | - if (/* TODO check for correct role; was: user != null */ true) {
|
|
| 157 | - additionalVerticalButtons.add(new Pair<Button, String>(mediaManagementButton,
|
|
| 158 | - "managemedia"));
|
|
| 159 | - }
|
|
| 160 | - onUserStatusChange(userService.getCurrentUser());
|
|
| 161 | -
|
|
| 162 | - // ensure that toggle buttons are positioned right
|
|
| 163 | - splitLayoutPanel.lastComponentHasBeenAdded(this, panelForMapAndHorizontalToggleButtons,
|
|
| 164 | - additionalVerticalButtons);
|
|
| 165 | - }
|
|
| 166 | -
|
|
| 167 | - /**
|
|
| 168 | - * Create the video control button that shows or hides the video popup
|
|
| 169 | - *
|
|
| 170 | - * @param userAgent
|
|
| 171 | - */
|
|
| 172 | - private Button createMediaSelectionButton(final MediaPlayerManager mediaPlayerManager) {
|
|
| 173 | - final Button result = new Button();
|
|
| 174 | - final MediaSingleSelectionControl multiSelectionControl = new MediaSingleSelectionControl(mediaPlayerManager,
|
|
| 175 | - result, stringMessages);
|
|
| 176 | - result.addClickHandler(new ClickHandler() {
|
|
| 177 | -
|
|
| 178 | - @Override
|
|
| 179 | - public void onClick(ClickEvent event) {
|
|
| 180 | - if (mediaPlayerManager.getAssignedMediaTracks().size() == 1) {
|
|
| 181 | - if (mediaPlayerManager.isPlaying()) {
|
|
| 182 | - mediaPlayerManager.stopAll();
|
|
| 183 | - } else {
|
|
| 184 | - mediaPlayerManager.playDefault();
|
|
| 185 | -
|
|
| 186 | - }
|
|
| 187 | - } else {
|
|
| 188 | - multiSelectionControl.show();
|
|
| 189 | - }
|
|
| 190 | - }
|
|
| 191 | - });
|
|
| 192 | -
|
|
| 193 | - // hide button initially as we defer showing the button to the asynchroneous
|
|
| 194 | - // task that gets launched by the media service to get video tracks
|
|
| 195 | - result.setVisible(false);
|
|
| 196 | - return result;
|
|
| 197 | - }
|
|
| 198 | -
|
|
| 199 | - /**
|
|
| 200 | - * Create the video control button that shows or hides the video popup
|
|
| 201 | - *
|
|
| 202 | - * @param userAgent
|
|
| 203 | - */
|
|
| 204 | - private Button createMediaManagementButton(final MediaPlayerManager mediaPlayerManager) {
|
|
| 205 | - final Button result = new Button(stringMessages.mediaManageMediaCaption());
|
|
| 206 | - result.setTitle(stringMessages.mediaManageMediaTooltip());
|
|
| 207 | - // onClick
|
|
| 208 | - final MediaManagementControl multiSelectionControl = new MediaManagementControl(mediaPlayerManager,
|
|
| 209 | - result, stringMessages);
|
|
| 210 | - result.addClickHandler(new ClickHandler() {
|
|
| 211 | -
|
|
| 212 | - @Override
|
|
| 213 | - public void onClick(ClickEvent event) {
|
|
| 214 | - multiSelectionControl.show();
|
|
| 215 | - }
|
|
| 216 | - });
|
|
| 217 | -
|
|
| 218 | - // hide button initially as we defer showing the button to the asynchroneous
|
|
| 219 | - // task that gets launched by the media service to get video tracks
|
|
| 220 | - result.setVisible(false);
|
|
| 221 | - return result;
|
|
| 222 | - }
|
|
| 223 | -
|
|
| 224 | - private void initializeComponents() {
|
|
| 225 | - for (final Component<?> component : components) {
|
|
| 226 | - splitLayoutPanel.insert(component.getEntryWidget(), component, Direction.SOUTH, 200);
|
|
| 227 | - }
|
|
| 228 | - }
|
|
| 229 | -
|
|
| 230 | - public <SettingsType extends AbstractSettings> void showSettingsDialog(Component<SettingsType> component) {
|
|
| 231 | - if (component.hasSettings()) {
|
|
| 232 | - new SettingsDialog<SettingsType>(component, stringMessages).show();
|
|
| 233 | - }
|
|
| 234 | - }
|
|
| 235 | -
|
|
| 236 | - /**
|
|
| 237 | - * Called whenever the layout of {@link TouchSplitLayoutPanel} or other components change. Controls the visibility
|
|
| 238 | - * based on the {@link Component}s visibility. Each {@link Component} is in charge to not display any data or update
|
|
| 239 | - * itself when it is not visible.
|
|
| 240 | - */
|
|
| 241 | - public void forceLayout() {
|
|
| 242 | - if (!leftComponent.isVisible() && rightComponent.isVisible()) {
|
|
| 243 | - // the leaderboard is not visible, but the map is
|
|
| 244 | - if (isWidgetInSplitPanel(leftScrollPanel)) {
|
|
| 245 | - if (leftScrollPanel.getOffsetWidth() > 0) {
|
|
| 246 | - savedSplitPosition = leftScrollPanel.getOffsetWidth();
|
|
| 247 | - }
|
|
| 248 | - splitLayoutPanel.setWidgetVisibility(leftScrollPanel, leftComponent, /* hidden */true,
|
|
| 249 | - savedSplitPosition);
|
|
| 250 | - }
|
|
| 251 | - } else if (leftComponent.isVisible() && rightComponent.isVisible()) {
|
|
| 252 | - // the leaderboard and the map are visible
|
|
| 253 | - splitLayoutPanel.setWidgetVisibility(leftScrollPanel, leftComponent, /* hidden */false, savedSplitPosition);
|
|
| 254 | - } else if (!leftComponent.isVisible() && !rightComponent.isVisible()) {
|
|
| 255 | - }
|
|
| 256 | -
|
|
| 257 | - for (Component<?> component : components) {
|
|
| 258 | - final boolean isComponentVisible = component.isVisible();
|
|
| 259 | - splitLayoutPanel.setWidgetVisibility(component.getEntryWidget(), component, !isComponentVisible,
|
|
| 260 | - DEFAULT_SOUTH_SPLIT_PANEL_HEIGHT);
|
|
| 261 | - }
|
|
| 262 | - splitLayoutPanel.forceLayout();
|
|
| 263 | - }
|
|
| 264 | -
|
|
| 265 | - private boolean isWidgetInSplitPanel(Widget widget) {
|
|
| 266 | - int widgetIndex = splitLayoutPanel.getWidgetIndex(widget);
|
|
| 267 | - return widgetIndex >= 0;
|
|
| 268 | - }
|
|
| 269 | -
|
|
| 270 | - public Panel getViewerWidget() {
|
|
| 271 | - return mainPanel;
|
|
| 272 | - }
|
|
| 273 | -
|
|
| 274 | - public Component<?> getRootComponent() {
|
|
| 275 | - return null;
|
|
| 276 | - }
|
|
| 277 | -
|
|
| 278 | - public String getViewerName() {
|
|
| 279 | - return "";
|
|
| 280 | - }
|
|
| 281 | -
|
|
| 282 | - public void setLeftComponentWidth(int width) {
|
|
| 283 | - // TODO: The information provided by width is wrong
|
|
| 284 | - // need to find a way to get the correct information
|
|
| 285 | - if (!layoutForLeftComponentForcedOnce) {
|
|
| 286 | - savedSplitPosition = MIN_LEADERBOARD_WIDTH;
|
|
| 287 | - forceLayout();
|
|
| 288 | - }
|
|
| 289 | - layoutForLeftComponentForcedOnce = true;
|
|
| 290 | - }
|
|
| 291 | -
|
|
| 292 | - @Override
|
|
| 293 | - public void onUserStatusChange(UserDTO user) {
|
|
| 294 | - Button toggleButton = splitLayoutPanel.getAssociatedSplitter(markPassingsPanel).getToggleButton();
|
|
| 295 | - if (user != null
|
|
| 296 | - && (user.hasRole(DefaultRoles.ADMIN.getRolename()) || user.hasRole(Roles.eventmanager.getRolename()))) {
|
|
| 297 | - toggleButton.setVisible(true);
|
|
| 298 | - forceLayout();
|
|
| 299 | - } else {
|
|
| 300 | - markPassingsPanel.setVisible(false);
|
|
| 301 | - toggleButton.setVisible(false);
|
|
| 302 | - forceLayout();
|
|
| 303 | - }
|
|
| 304 | - }
|
|
| 305 | -}
|
|
| 1 | +package com.sap.sailing.gwt.ui.raceboard; |
|
| 2 | + |
|
| 3 | +import java.util.ArrayList; |
|
| 4 | +import java.util.List; |
|
| 5 | + |
|
| 6 | +import com.google.gwt.dom.client.Style; |
|
| 7 | +import com.google.gwt.dom.client.Style.Unit; |
|
| 8 | +import com.google.gwt.event.dom.client.ClickEvent; |
|
| 9 | +import com.google.gwt.event.dom.client.ClickHandler; |
|
| 10 | +import com.google.gwt.user.client.Window; |
|
| 11 | +import com.google.gwt.user.client.ui.AbsolutePanel; |
|
| 12 | +import com.google.gwt.user.client.ui.Button; |
|
| 13 | +import com.google.gwt.user.client.ui.DockLayoutPanel.Direction; |
|
| 14 | +import com.google.gwt.user.client.ui.LayoutPanel; |
|
| 15 | +import com.google.gwt.user.client.ui.Panel; |
|
| 16 | +import com.google.gwt.user.client.ui.RequiresResize; |
|
| 17 | +import com.google.gwt.user.client.ui.ScrollPanel; |
|
| 18 | +import com.google.gwt.user.client.ui.Widget; |
|
| 19 | +import com.google.gwt.user.client.ui.WidgetCollection; |
|
| 20 | +import com.sap.sailing.domain.common.security.Roles; |
|
| 21 | +import com.sap.sailing.gwt.ui.client.StringMessages; |
|
| 22 | +import com.sap.sailing.gwt.ui.client.media.MediaManagementControl; |
|
| 23 | +import com.sap.sailing.gwt.ui.client.media.MediaPlayerManager; |
|
| 24 | +import com.sap.sailing.gwt.ui.client.media.MediaPlayerManager.PlayerChangeListener; |
|
| 25 | +import com.sap.sailing.gwt.ui.client.media.MediaPlayerManagerComponent; |
|
| 26 | +import com.sap.sailing.gwt.ui.client.media.MediaSingleSelectionControl; |
|
| 27 | +import com.sap.sailing.gwt.ui.client.shared.charts.EditMarkPassingsPanel; |
|
| 28 | +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; |
|
| 29 | +import com.sap.sailing.gwt.ui.raceboard.TouchSplitLayoutPanel.Splitter; |
|
| 30 | +import com.sap.sse.common.Util.Pair; |
|
| 31 | +import com.sap.sse.common.settings.AbstractSettings; |
|
| 32 | +import com.sap.sse.gwt.client.shared.components.Component; |
|
| 33 | +import com.sap.sse.gwt.client.shared.components.ComponentViewer; |
|
| 34 | +import com.sap.sse.gwt.client.shared.components.SettingsDialog; |
|
| 35 | +import com.sap.sse.security.shared.DefaultRoles; |
|
| 36 | +import com.sap.sse.security.ui.client.UserService; |
|
| 37 | +import com.sap.sse.security.ui.client.UserStatusEventHandler; |
|
| 38 | +import com.sap.sse.security.ui.shared.UserDTO; |
|
| 39 | + |
|
| 40 | +/** |
|
| 41 | + * Component Viewer that uses a {@link TouchSplitLayoutPanel} to display its components. |
|
| 42 | + * |
|
| 43 | + * TODO: Refactor to make sure it is really only performing operations that are related to view. Currently it is digging |
|
| 44 | + * too deep into components and setting titles or even creating video buttons. |
|
| 45 | + */ |
|
| 46 | +public class SideBySideComponentViewer implements ComponentViewer, UserStatusEventHandler { |
|
| 47 | + |
|
| 48 | + private static final int DEFAULT_SOUTH_SPLIT_PANEL_HEIGHT = 200; |
|
| 49 | + private final int MIN_LEADERBOARD_WIDTH = Math.min(432, Window.getClientWidth() - 40); // fallback value "432" works well for 505 and ESS |
|
| 50 | + |
|
| 51 | + /** |
|
| 52 | + * Absolute Panel that informs its children about a resize |
|
| 53 | + */ |
|
| 54 | + class ResizableAbsolutePanel extends AbsolutePanel implements RequiresResize { |
|
| 55 | + public void onResize() { |
|
| 56 | + WidgetCollection children = getChildren(); |
|
| 57 | + for (Widget widget : children) { |
|
| 58 | + if (widget instanceof RequiresResize) { |
|
| 59 | + ((RequiresResize) widget).onResize(); |
|
| 60 | + } |
|
| 61 | + } |
|
| 62 | + } |
|
| 63 | + } |
|
| 64 | + |
|
| 65 | + private final LeaderboardPanel leftComponent; |
|
| 66 | + private final Component<?> rightComponent; |
|
| 67 | + private final List<Component<?>> components; |
|
| 68 | + private final ScrollPanel leftScrollPanel; |
|
| 69 | + private final StringMessages stringMessages; |
|
| 70 | + private final Button mediaSelectionButton; |
|
| 71 | + private final Button mediaManagementButton; |
|
| 72 | + private final EditMarkPassingsPanel markPassingsPanel; |
|
| 73 | + |
|
| 74 | + private LayoutPanel mainPanel; |
|
| 75 | + |
|
| 76 | + private TouchSplitLayoutPanel splitLayoutPanel; |
|
| 77 | + private int savedSplitPosition = -1; |
|
| 78 | + private boolean layoutForLeftComponentForcedOnce = false; |
|
| 79 | + |
|
| 80 | + public SideBySideComponentViewer(final LeaderboardPanel leftComponentP, final Component<?> rightComponentP, |
|
| 81 | + final MediaPlayerManagerComponent mediaPlayerManagerComponent, List<Component<?>> components, |
|
| 82 | + final StringMessages stringMessages, UserService userService, EditMarkPassingsPanel markPassingsPanel) { |
|
| 83 | + this.stringMessages = stringMessages; |
|
| 84 | + this.leftComponent = leftComponentP; |
|
| 85 | + this.rightComponent = rightComponentP; |
|
| 86 | + this.components = components; |
|
| 87 | + this.mediaSelectionButton = createMediaSelectionButton(mediaPlayerManagerComponent); |
|
| 88 | + this.mediaManagementButton = createMediaManagementButton(mediaPlayerManagerComponent); |
|
| 89 | + this.markPassingsPanel = markPassingsPanel; |
|
| 90 | + userService.addUserStatusEventHandler(this); |
|
| 91 | + mediaPlayerManagerComponent.setPlayerChangeListener(new PlayerChangeListener() { |
|
| 92 | + public void notifyStateChange() { |
|
| 93 | + String caption; |
|
| 94 | + String tooltip; |
|
| 95 | + switch (mediaPlayerManagerComponent.getAssignedMediaTracks().size()) { |
|
| 96 | + case 0: |
|
| 97 | + caption = stringMessages.mediaNoVideosCaption(); |
|
| 98 | + tooltip = caption; |
|
| 99 | + mediaSelectionButton.setVisible(false); |
|
| 100 | + break; |
|
| 101 | + case 1: |
|
| 102 | + mediaSelectionButton.setVisible(true); |
|
| 103 | + if(mediaPlayerManagerComponent.isPlaying()){ |
|
| 104 | + caption = stringMessages.mediaHideVideoCaption(); |
|
| 105 | + tooltip = stringMessages.mediaHideVideoTooltip(); |
|
| 106 | + } |
|
| 107 | + else{ |
|
| 108 | + caption = stringMessages.mediaShowVideoCaption(); |
|
| 109 | + tooltip = stringMessages.mediaShowVideoTooltip(mediaPlayerManagerComponent.getAssignedMediaTracks().iterator().next().title); |
|
| 110 | + } |
|
| 111 | + break; |
|
| 112 | + default: |
|
| 113 | + mediaSelectionButton.setVisible(true); |
|
| 114 | + caption = stringMessages.mediaSelectVideoCaption(mediaPlayerManagerComponent.getAssignedMediaTracks().size()); |
|
| 115 | + tooltip = stringMessages.mediaSelectVideoTooltip(); |
|
| 116 | + break; |
|
| 117 | + } |
|
| 118 | + if (mediaPlayerManagerComponent.isPlaying()) { |
|
| 119 | + mediaSelectionButton.addStyleDependentName("mediaplaying"); |
|
| 120 | + } else { |
|
| 121 | + mediaSelectionButton.removeStyleDependentName("mediaplaying"); |
|
| 122 | + } |
|
| 123 | + mediaSelectionButton.setText(caption); |
|
| 124 | + mediaSelectionButton.setTitle(tooltip); |
|
| 125 | + mediaManagementButton.setVisible(mediaPlayerManagerComponent.allowsEditing()); |
|
| 126 | + } |
|
| 127 | + }); |
|
| 128 | + this.leftScrollPanel = new ScrollPanel(); |
|
| 129 | + this.leftScrollPanel.add(leftComponentP.getEntryWidget()); |
|
| 130 | + this.leftScrollPanel.setTitle(leftComponentP.getEntryWidget().getTitle()); |
|
| 131 | + this.mainPanel = new LayoutPanel() { |
|
| 132 | + @Override |
|
| 133 | + public void onResize() { |
|
| 134 | + int leftWidth = leftScrollPanel.getOffsetWidth(); |
|
| 135 | + // The left scroll panel is potentially resized to ensure it is not too wide when the screen gets narrower, |
|
| 136 | + // e.g. when resizing browser window or changing mobile device orientation. An offset of 40px is used, so |
|
| 137 | + // the panels size slider and its toggle button is always accessable if it is open. |
|
| 138 | + savedSplitPosition = Math.min(leftWidth > 0 ? leftWidth : savedSplitPosition, Window.getClientWidth() - 40); |
|
| 139 | + splitLayoutPanel.setWidgetSize(leftScrollPanel, savedSplitPosition); |
|
| 140 | + super.onResize(); |
|
| 141 | + } |
|
| 142 | + }; |
|
| 143 | + this.mainPanel.setSize("100%", "100%"); |
|
| 144 | + this.mainPanel.getElement().getStyle().setMarginTop(-12, Unit.PX); |
|
| 145 | + this.mainPanel.setStyleName("SideBySideComponentViewer-MainPanel"); |
|
| 146 | + this.splitLayoutPanel = new TouchSplitLayoutPanel(/* horizontal splitter width */3, /* vertical splitter height */ 25); |
|
| 147 | + this.mainPanel.add(splitLayoutPanel); |
|
| 148 | + |
|
| 149 | + // initialize components - they need to be added before other widgets to get the right width |
|
| 150 | + initializeComponents(); |
|
| 151 | + |
|
| 152 | + // initialize the leaderboard component |
|
| 153 | + savedSplitPosition = MIN_LEADERBOARD_WIDTH; |
|
| 154 | + splitLayoutPanel.insert(leftScrollPanel, leftComponent, Direction.WEST, savedSplitPosition); |
|
| 155 | + |
|
| 156 | + // create a panel that will contain the horizontal toggle buttons |
|
| 157 | + ResizableAbsolutePanel panelForMapAndHorizontalToggleButtons = new ResizableAbsolutePanel(); |
|
| 158 | + panelForMapAndHorizontalToggleButtons.add(rightComponent.getEntryWidget()); |
|
| 159 | + splitLayoutPanel.insert(panelForMapAndHorizontalToggleButtons, rightComponent, Direction.CENTER, 0); |
|
| 160 | + |
|
| 161 | + // add additional toggle buttons panel that currently only contains the video button |
|
| 162 | + List<Pair<Button, String>> additionalVerticalButtons = new ArrayList<Pair<Button, String>>(); |
|
| 163 | + additionalVerticalButtons.add(new Pair<Button, String>(mediaSelectionButton, |
|
| 164 | + mediaPlayerManagerComponent.getDependentCssClassName())); |
|
| 165 | + if (/* TODO check for correct role; was: user != null */ true) { |
|
| 166 | + additionalVerticalButtons.add(new Pair<Button, String>(mediaManagementButton, |
|
| 167 | + "managemedia")); |
|
| 168 | + } |
|
| 169 | + onUserStatusChange(userService.getCurrentUser()); |
|
| 170 | + // ensure that toggle buttons are positioned right |
|
| 171 | + splitLayoutPanel.lastComponentHasBeenAdded(this, panelForMapAndHorizontalToggleButtons, |
|
| 172 | + additionalVerticalButtons); |
|
| 173 | + } |
|
| 174 | + |
|
| 175 | + /** |
|
| 176 | + * Create the video control button that shows or hides the video popup |
|
| 177 | + * |
|
| 178 | + * @param userAgent |
|
| 179 | + */ |
|
| 180 | + private Button createMediaSelectionButton(final MediaPlayerManager mediaPlayerManager) { |
|
| 181 | + final Button result = new Button(); |
|
| 182 | + final MediaSingleSelectionControl multiSelectionControl = new MediaSingleSelectionControl(mediaPlayerManager, |
|
| 183 | + result, stringMessages); |
|
| 184 | + result.addClickHandler(new ClickHandler() { |
|
| 185 | + |
|
| 186 | + @Override |
|
| 187 | + public void onClick(ClickEvent event) { |
|
| 188 | + if (mediaPlayerManager.getAssignedMediaTracks().size() == 1) { |
|
| 189 | + if (mediaPlayerManager.isPlaying()) { |
|
| 190 | + mediaPlayerManager.stopAll(); |
|
| 191 | + } else { |
|
| 192 | + mediaPlayerManager.playDefault(); |
|
| 193 | + |
|
| 194 | + } |
|
| 195 | + } else { |
|
| 196 | + multiSelectionControl.show(); |
|
| 197 | + } |
|
| 198 | + } |
|
| 199 | + }); |
|
| 200 | + |
|
| 201 | + // hide button initially as we defer showing the button to the asynchroneous |
|
| 202 | + // task that gets launched by the media service to get video tracks |
|
| 203 | + result.setVisible(false); |
|
| 204 | + return result; |
|
| 205 | + } |
|
| 206 | + |
|
| 207 | + /** |
|
| 208 | + * Create the video control button that shows or hides the video popup |
|
| 209 | + * |
|
| 210 | + * @param userAgent |
|
| 211 | + */ |
|
| 212 | + private Button createMediaManagementButton(final MediaPlayerManager mediaPlayerManager) { |
|
| 213 | + final Button result = new Button(stringMessages.mediaManageMediaCaption()); |
|
| 214 | + result.setTitle(stringMessages.mediaManageMediaTooltip()); |
|
| 215 | + // onClick |
|
| 216 | + final MediaManagementControl multiSelectionControl = new MediaManagementControl(mediaPlayerManager, |
|
| 217 | + result, stringMessages); |
|
| 218 | + result.addClickHandler(new ClickHandler() { |
|
| 219 | + @Override |
|
| 220 | + public void onClick(ClickEvent event) { |
|
| 221 | + multiSelectionControl.show(); |
|
| 222 | + } |
|
| 223 | + }); |
|
| 224 | + // hide button initially as we defer showing the button to the asynchronous |
|
| 225 | + // task that gets launched by the media service to get video tracks |
|
| 226 | + result.setVisible(false); |
|
| 227 | + return result; |
|
| 228 | + } |
|
| 229 | + |
|
| 230 | + private void initializeComponents() { |
|
| 231 | + for (final Component<?> component : components) { |
|
| 232 | + splitLayoutPanel.insert(component.getEntryWidget(), component, Direction.SOUTH, 200); |
|
| 233 | + } |
|
| 234 | + } |
|
| 235 | + |
|
| 236 | + public <SettingsType extends AbstractSettings> void showSettingsDialog(Component<SettingsType> component) { |
|
| 237 | + if (component.hasSettings()) { |
|
| 238 | + new SettingsDialog<SettingsType>(component, stringMessages).show(); |
|
| 239 | + } |
|
| 240 | + } |
|
| 241 | + |
|
| 242 | + /** |
|
| 243 | + * Called whenever the layout of {@link TouchSplitLayoutPanel} or other components change. Controls the visibility |
|
| 244 | + * based on the {@link Component}s visibility. Each {@link Component} is in charge to not display any data or update |
|
| 245 | + * itself when it is not visible. |
|
| 246 | + */ |
|
| 247 | + public void forceLayout() { |
|
| 248 | + if (!leftComponent.isVisible() && rightComponent.isVisible()) { |
|
| 249 | + // the leaderboard is not visible, but the map is |
|
| 250 | + if (isWidgetInSplitPanel(leftScrollPanel)) { |
|
| 251 | + if (leftScrollPanel.getOffsetWidth() > 0) { |
|
| 252 | + savedSplitPosition = Math.min(savedSplitPosition, leftScrollPanel.getOffsetWidth()); |
|
| 253 | + } |
|
| 254 | + splitLayoutPanel.setWidgetVisibility(leftScrollPanel, leftComponent, /* hidden */true, |
|
| 255 | + savedSplitPosition); |
|
| 256 | + } |
|
| 257 | + } else if (leftComponent.isVisible() && rightComponent.isVisible()) { |
|
| 258 | + // the leaderboard and the map are visible |
|
| 259 | + splitLayoutPanel.setWidgetVisibility(leftScrollPanel, leftComponent, /* hidden */false, savedSplitPosition); |
|
| 260 | + } else if (!leftComponent.isVisible() && !rightComponent.isVisible()) { |
|
| 261 | + } |
|
| 262 | + |
|
| 263 | + for (Component<?> component : components) { |
|
| 264 | + final boolean isComponentVisible = component.isVisible(); |
|
| 265 | + splitLayoutPanel.setWidgetVisibility(component.getEntryWidget(), component, !isComponentVisible, |
|
| 266 | + DEFAULT_SOUTH_SPLIT_PANEL_HEIGHT); |
|
| 267 | + } |
|
| 268 | + splitLayoutPanel.forceLayout(); |
|
| 269 | + } |
|
| 270 | + |
|
| 271 | + private boolean isWidgetInSplitPanel(Widget widget) { |
|
| 272 | + int widgetIndex = splitLayoutPanel.getWidgetIndex(widget); |
|
| 273 | + return widgetIndex >= 0; |
|
| 274 | + } |
|
| 275 | + |
|
| 276 | + public Panel getViewerWidget() { |
|
| 277 | + return mainPanel; |
|
| 278 | + } |
|
| 279 | + |
|
| 280 | + public Component<?> getRootComponent() { |
|
| 281 | + return null; |
|
| 282 | + } |
|
| 283 | + |
|
| 284 | + public String getViewerName() { |
|
| 285 | + return ""; |
|
| 286 | + } |
|
| 287 | + |
|
| 288 | + public void setLeftComponentWidth(int width) { |
|
| 289 | + // TODO: The information provided by width is wrong |
|
| 290 | + // need to find a way to get the correct information |
|
| 291 | + if (!layoutForLeftComponentForcedOnce) { |
|
| 292 | + savedSplitPosition = MIN_LEADERBOARD_WIDTH; |
|
| 293 | + forceLayout(); |
|
| 294 | + } |
|
| 295 | + layoutForLeftComponentForcedOnce = true; |
|
| 296 | + } |
|
| 297 | + |
|
| 298 | + @Override |
|
| 299 | + public void onUserStatusChange(UserDTO user) { |
|
| 300 | + final Splitter associatedSplitter = splitLayoutPanel.getAssociatedSplitter(markPassingsPanel); |
|
| 301 | + if (associatedSplitter != null) { // if the panel is not present, the splitter may not be found |
|
| 302 | + final Button toggleButton = associatedSplitter.getToggleButton(); |
|
| 303 | + if (user != null |
|
| 304 | + && (user.hasRole(DefaultRoles.ADMIN.getRolename()) || |
|
| 305 | + user.hasRole(Roles.eventmanager.getRolename()))) { |
|
| 306 | + toggleButton.setVisible(true); |
|
| 307 | + forceLayout(); |
|
| 308 | + } else { |
|
| 309 | + markPassingsPanel.setVisible(false); |
|
| 310 | + toggleButton.setVisible(false); |
|
| 311 | + forceLayout(); |
|
| 312 | + } |
|
| 313 | + } |
|
| 314 | + } |
|
| 315 | + |
|
| 316 | + /** |
|
| 317 | + * Shows/hides the text on left components toggle button by modifying CSS <code>font-size</code> property and adjust |
|
| 318 | + * the dragger position by modifying CSS <code>margin-top</code> property. |
|
| 319 | + * |
|
| 320 | + * @param visible |
|
| 321 | + * <code>true</code> to show the button text, <code>false</code> to hide it |
|
| 322 | + */ |
|
| 323 | + void setLeftComponentToggleButtonTextVisibilityAndDraggerPosition(final boolean visible) { |
|
| 324 | + Splitter leftScrollPanelSplitter = splitLayoutPanel.getAssociatedSplitter(leftScrollPanel); |
|
| 325 | + if (leftScrollPanelSplitter != null) { |
|
| 326 | + Style toggleButtonStyle = leftScrollPanelSplitter.getToggleButton().getElement().getStyle(); |
|
| 327 | + if (visible) toggleButtonStyle.clearFontSize(); |
|
| 328 | + else toggleButtonStyle.setFontSize(0, Unit.PX); |
|
| 329 | + Style drapperStyle = leftScrollPanelSplitter.getDragger().getElement().getStyle(); |
|
| 330 | + if (visible) drapperStyle.clearMarginTop(); |
|
| 331 | + else drapperStyle.setMarginTop(-25, Unit.PX); |
|
| 332 | + } |
|
| 333 | + } |
|
| 334 | +} |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/TouchSplitLayoutPanel.java
| ... | ... | @@ -613,7 +613,7 @@ public class TouchSplitLayoutPanel extends DockLayoutPanel { |
| 613 | 613 | if (lastVerticalSplitter != null) { |
| 614 | 614 | Panel panel = createToggleButtonPanel(allVerticalSplitters, |
| 615 | 615 | "gwt-SplitLayoutPanel-NorthSouthToggleButton-Panel", "gwt-SplitLayoutPanel-NorthSouthToggleButton", |
| 616 | - cm, additionalVerticalButtonsAndStyles); |
|
| 616 | + cm, additionalVerticalButtonsAndStyles); // TODO additional buttons will only be displayed if at least one vertical splitter is... on purpose? |
|
| 617 | 617 | lastVerticalSplitter.addToogleButtons(panel); |
| 618 | 618 | lastVerticalSplitter.setVisible(true); |
| 619 | 619 | lastVerticalSplitter.setDraggerVisible(false); |
| ... | ... | @@ -621,7 +621,7 @@ public class TouchSplitLayoutPanel extends DockLayoutPanel { |
| 621 | 621 | ensureVerticalToggleButtonPosition(); |
| 622 | 622 | if (lastHorizontalSplitter != null) { |
| 623 | 623 | Panel horizontalButtonsPanel = createToggleButtonPanel(allHorizontalSplitters, |
| 624 | - "gwt-SplitLayoutPanel-EastToggleButton-Panel", "gwt-SplitLayoutPanel-EastToggleButton", cm, null); |
|
| 624 | + "gwt-SplitLayoutPanel-EastToggleButton-Panel", "gwt-SplitLayoutPanel-EastToggleButton", cm, /* additional buttons and styles */ null); |
|
| 625 | 625 | panelForHorizontalButtons.add(horizontalButtonsPanel); |
| 626 | 626 | lastHorizontalSplitter.setVisible(false); |
| 627 | 627 | lastHorizontalSplitter.setDraggerVisible(false); |
| ... | ... | @@ -842,6 +842,24 @@ public class TouchSplitLayoutPanel extends DockLayoutPanel { |
| 842 | 842 | } |
| 843 | 843 | } |
| 844 | 844 | } |
| 845 | + |
|
| 846 | + /** |
|
| 847 | + * Sets the size of the given {@link Widget} if it is {@link Widget#isVisible() visible}. Otherwise, the given size |
|
| 848 | + * will be persisted as the widgets {@link LayoutData#oldSize old size}. |
|
| 849 | + * |
|
| 850 | + * @param widget the {@link Widget} to resize |
|
| 851 | + * @param size the new size |
|
| 852 | + */ |
|
| 853 | + public void setWidgetSize(Widget widget, int size) { |
|
| 854 | + final Splitter splitter = getAssociatedSplitter(widget); |
|
| 855 | + if (splitter != null) { |
|
| 856 | + if (widget.isVisible()) { |
|
| 857 | + splitter.setAssociatedWidgetSize(size, /* defer */false); |
|
| 858 | + } else { |
|
| 859 | + ((LayoutData) widget.getLayoutData()).oldSize = size; |
|
| 860 | + } |
|
| 861 | + } |
|
| 862 | + } |
|
| 845 | 863 | |
| 846 | 864 | private void assertIsChild2(Widget widget) { |
| 847 | 865 | assert (widget == null) || (widget.getParent() == this) : "The specified widget is not a child of this panel"; |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java
| ... | ... | @@ -233,6 +233,7 @@ import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; |
| 233 | 233 | import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; |
| 234 | 234 | import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache; |
| 235 | 235 | import com.sap.sailing.domain.leaderboard.caching.LiveLeaderboardUpdater; |
| 236 | +import com.sap.sailing.domain.leaderboard.meta.MetaLeaderboardColumn; |
|
| 236 | 237 | import com.sap.sailing.domain.persistence.DomainObjectFactory; |
| 237 | 238 | import com.sap.sailing.domain.persistence.MongoObjectFactory; |
| 238 | 239 | import com.sap.sailing.domain.persistence.MongoRaceLogStoreFactory; |
| ... | ... | @@ -444,12 +445,7 @@ import com.sap.sse.common.Util.Triple; |
| 444 | 445 | import com.sap.sse.common.impl.MillisecondsTimePoint; |
| 445 | 446 | import com.sap.sse.common.impl.TimeRangeImpl; |
| 446 | 447 | import com.sap.sse.common.mail.MailException; |
| 447 | -import com.sap.sse.common.media.ImageDescriptor; |
|
| 448 | -import com.sap.sse.common.media.ImageDescriptorImpl; |
|
| 449 | -import com.sap.sse.common.media.MediaUtils; |
|
| 450 | 448 | import com.sap.sse.common.media.MimeType; |
| 451 | -import com.sap.sse.common.media.VideoDescriptor; |
|
| 452 | -import com.sap.sse.common.media.VideoDescriptorImpl; |
|
| 453 | 449 | import com.sap.sse.filestorage.FileStorageService; |
| 454 | 450 | import com.sap.sse.filestorage.InvalidPropertiesException; |
| 455 | 451 | import com.sap.sse.gwt.client.ServerInfoDTO; |
| ... | ... | @@ -465,6 +461,11 @@ import com.sap.sse.replication.ReplicationMasterDescriptor; |
| 465 | 461 | import com.sap.sse.replication.ReplicationService; |
| 466 | 462 | import com.sap.sse.replication.impl.ReplicaDescriptor; |
| 467 | 463 | import com.sap.sse.security.SessionUtils; |
| 464 | +import com.sap.sse.shared.media.ImageDescriptor; |
|
| 465 | +import com.sap.sse.shared.media.MediaUtils; |
|
| 466 | +import com.sap.sse.shared.media.VideoDescriptor; |
|
| 467 | +import com.sap.sse.shared.media.impl.ImageDescriptorImpl; |
|
| 468 | +import com.sap.sse.shared.media.impl.VideoDescriptorImpl; |
|
| 468 | 469 | import com.sap.sse.util.ServiceTrackerFactory; |
| 469 | 470 | import com.sapsailing.xrr.structureimport.eventimport.RegattaJSON; |
| 470 | 471 | |
| ... | ... | @@ -853,7 +854,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S |
| 853 | 854 | final RaceColumnDTO raceColumnDTO = columnFactory.createRaceColumnDTO(raceColumn.getName(), |
| 854 | 855 | raceColumn.isMedalRace(), raceColumn.getExplicitFactor(), |
| 855 | 856 | raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null, |
| 856 | - raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null); |
|
| 857 | + raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null, |
|
| 858 | + raceColumn instanceof MetaLeaderboardColumn); |
|
| 857 | 859 | raceColumnDTOs.add(raceColumnDTO); |
| 858 | 860 | } |
| 859 | 861 | return raceColumnDTOs; |
| ... | ... | @@ -1023,7 +1025,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S |
| 1023 | 1025 | @Override |
| 1024 | 1026 | public void trackWithTracTrac(RegattaIdentifier regattaToAddTo, Iterable<TracTracRaceRecordDTO> rrs, String liveURI, String storedURI, |
| 1025 | 1027 | String courseDesignUpdateURI, boolean trackWind, final boolean correctWindByDeclination, |
| 1026 | - final boolean simulateWithStartTimeNow, final boolean useInternalMarkPassingAlgorithm, String tracTracUsername, String tracTracPassword) |
|
| 1028 | + final Duration offsetToStartTimeOfSimulatedRace, final boolean useInternalMarkPassingAlgorithm, String tracTracUsername, String tracTracPassword) |
|
| 1027 | 1029 | throws Exception { |
| 1028 | 1030 | logger.info("tracWithTracTrac for regatta " + regattaToAddTo + " for race records " + rrs + " with liveURI " + liveURI |
| 1029 | 1031 | + " and storedURI " + storedURI); |
| ... | ... | @@ -1054,7 +1056,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S |
| 1054 | 1056 | new URI(courseDesignUpdateURI), new MillisecondsTimePoint(record.getTrackingStartTime().asMillis()), |
| 1055 | 1057 | new MillisecondsTimePoint(record.getTrackingEndTime().asMillis()), |
| 1056 | 1058 | getRaceLogStore(), getRegattaLogStore(), |
| 1057 | - RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS, simulateWithStartTimeNow, |
|
| 1059 | + RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS, offsetToStartTimeOfSimulatedRace, |
|
| 1058 | 1060 | useInternalMarkPassingAlgorithm, tracTracUsername, tracTracPassword, record.getRaceStatus(), record.getRaceVisibility()); |
| 1059 | 1061 | if (trackWind) { |
| 1060 | 1062 | new Thread("Wind tracking starter for race " + record.getEventName() + "/" + record.getName()) { |
| ... | ... | @@ -1946,7 +1948,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S |
| 1946 | 1948 | result.totalLegsCount = trackedRace.getRace().getCourse().getLegs().size(); |
| 1947 | 1949 | result.currentLegNumber = trackedRace.getLastLegStarted(dateAsTimePoint); |
| 1948 | 1950 | result.marks = new HashSet<MarkDTO>(); |
| 1949 | - result.course = convertToRaceCourseDTO(trackedRace.getRace().getCourse(), trackedRace, dateAsTimePoint); |
|
| 1951 | + result.course = convertToRaceCourseDTO(trackedRace.getRace().getCourse(), new TrackedRaceMarkPositionFinder(trackedRace), dateAsTimePoint); |
|
| 1950 | 1952 | // now make sure we don't duplicate the MarkDTO objects but instead use the ones from the RaceCourseDTO |
| 1951 | 1953 | // object and amend them with the Position |
| 1952 | 1954 | result.waypointPositions = new ArrayList<>(); |
| ... | ... | @@ -2020,7 +2022,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S |
| 2020 | 2022 | for (Waypoint waypoint : course.getWaypoints()) { |
| 2021 | 2023 | ControlPointDTO controlPointDTO = controlPointCache.get(waypoint.getControlPoint().getId()); |
| 2022 | 2024 | if (controlPointDTO == null) { |
| 2023 | - controlPointDTO = convertToControlPointDTO(waypoint.getControlPoint(), trackedRace, dateAsTimePoint); |
|
| 2025 | + controlPointDTO = convertToControlPointDTO(waypoint.getControlPoint(), new TrackedRaceMarkPositionFinder(trackedRace), dateAsTimePoint); |
|
| 2024 | 2026 | controlPointCache.put(waypoint.getControlPoint().getId(), controlPointDTO); |
| 2025 | 2027 | } |
| 2026 | 2028 | WaypointDTO waypointDTO = new WaypointDTO(waypoint.getName(), controlPointDTO, |
| ... | ... | @@ -2031,20 +2033,44 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S |
| 2031 | 2033 | return new RaceCourseDTO(waypointDTOs, allMarks); |
| 2032 | 2034 | } |
| 2033 | 2035 | |
| 2034 | - private ControlPointDTO convertToControlPointDTO(ControlPoint controlPoint, TrackedRace trackedRace, TimePoint timePoint) { |
|
| 2036 | + class TrackedRaceMarkPositionFinder implements MarkPositionFinder{ |
|
| 2037 | + private TrackedRace trackedRace; |
|
| 2038 | + |
|
| 2039 | + public TrackedRaceMarkPositionFinder(TrackedRace trackedRace) { |
|
| 2040 | + this.trackedRace = trackedRace; |
|
| 2041 | + } |
|
| 2042 | + |
|
| 2043 | + @Override |
|
| 2044 | + public Position find(Mark mark, TimePoint at) { |
|
| 2045 | + final TimePoint timePointToUse = trackedRace == null ? null : |
|
| 2046 | + at == null ? MillisecondsTimePoint.now().minus(trackedRace.getDelayToLiveInMillis()) : at; |
|
| 2047 | + final Position result; |
|
| 2048 | + if (timePointToUse == null) { |
|
| 2049 | + result = null; |
|
| 2050 | + } else { |
|
| 2051 | + result = trackedRace.getOrCreateTrack(mark).getEstimatedPosition(timePointToUse, /* extrapolate */ false); |
|
| 2052 | + } |
|
| 2053 | + return result; |
|
| 2054 | + } |
|
| 2055 | + } |
|
| 2056 | + |
|
| 2057 | + private interface MarkPositionFinder { |
|
| 2058 | + Position find(Mark mark, TimePoint at); |
|
| 2059 | + } |
|
| 2060 | + |
|
| 2061 | + private ControlPointDTO convertToControlPointDTO(ControlPoint controlPoint, MarkPositionFinder positionFinder, TimePoint timePoint) { |
|
| 2035 | 2062 | ControlPointDTO result; |
| 2036 | - final TimePoint timePointToUse = trackedRace == null ? null : |
|
| 2037 | - timePoint == null ? MillisecondsTimePoint.now().minus(trackedRace.getDelayToLiveInMillis()) : timePoint; |
|
| 2063 | + |
|
| 2038 | 2064 | if (controlPoint instanceof ControlPointWithTwoMarks) { |
| 2039 | 2065 | final Mark left = ((ControlPointWithTwoMarks) controlPoint).getLeft(); |
| 2040 | - final Position leftPos = timePointToUse == null ? null : trackedRace.getOrCreateTrack(left).getEstimatedPosition(timePointToUse, /* extrapolate */ false); |
|
| 2066 | + final Position leftPos = positionFinder.find(left, timePoint); |
|
| 2041 | 2067 | final Mark right = ((ControlPointWithTwoMarks) controlPoint).getRight(); |
| 2042 | - final Position rightPos = timePointToUse == null ? null : trackedRace.getOrCreateTrack(right).getEstimatedPosition(timePointToUse, /* extrapolate */ false); |
|
| 2068 | + final Position rightPos = positionFinder.find(right, timePoint); |
|
| 2043 | 2069 | result = new GateDTO(controlPoint.getId().toString(), controlPoint.getName(), convertToMarkDTO(left, leftPos), convertToMarkDTO(right, rightPos)); |
| 2044 | 2070 | } else { |
| 2045 | - final Position posOfFirst = timePointToUse == null ? null : trackedRace.getOrCreateTrack(controlPoint.getMarks().iterator().next()). |
|
| 2046 | - getEstimatedPosition(timePointToUse, /* extrapolate */ false); |
|
| 2047 | - result = new MarkDTO(controlPoint.getId().toString(), controlPoint.getName(), posOfFirst == null ? -1 : posOfFirst.getLatDeg(), posOfFirst == null ? -1 : posOfFirst.getLngDeg()); |
|
| 2071 | + Mark mark = controlPoint.getMarks().iterator().next(); |
|
| 2072 | + final Position position = positionFinder.find(mark, timePoint); |
|
| 2073 | + result = convertToMarkDTO(mark, position); |
|
| 2048 | 2074 | } |
| 2049 | 2075 | return result; |
| 2050 | 2076 | } |
| ... | ... | @@ -2384,7 +2410,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S |
| 2384 | 2410 | RaceColumnDTO raceColumnDTO = leaderboardDTO.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(), |
| 2385 | 2411 | raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null, |
| 2386 | 2412 | raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null, |
| 2387 | - fleetDTO, raceColumn.isMedalRace(), raceIdentifier, raceDTO); |
|
| 2413 | + fleetDTO, raceColumn.isMedalRace(), raceIdentifier, raceDTO, raceColumn instanceof MetaLeaderboardColumn); |
|
| 2388 | 2414 | RaceLog raceLog = raceColumn.getRaceLog(fleet); |
| 2389 | 2415 | RaceLogTrackingState raceLogTrackingState = raceLog == null ? RaceLogTrackingState.NOT_A_RACELOG_TRACKED_RACE : |
| 2390 | 2416 | new RaceLogTrackingStateAnalyzer(raceLog).analyze(); |
| ... | ... | @@ -5156,10 +5182,10 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S |
| 5156 | 5182 | * the mark positions and attach to the {@link MarkDTO}s. If <code>null<c/code>, the current time point |
| 5157 | 5183 | * will be used as default. |
| 5158 | 5184 | */ |
| 5159 | - private WaypointDTO convertToWaypointDTO(Waypoint waypoint, Map<Serializable, ControlPointDTO> controlPointCache, TrackedRace trackedRace, TimePoint timePoint) { |
|
| 5185 | + private WaypointDTO convertToWaypointDTO(Waypoint waypoint, Map<Serializable, ControlPointDTO> controlPointCache, MarkPositionFinder positionFinder, TimePoint timePoint) { |
|
| 5160 | 5186 | ControlPointDTO cp = controlPointCache.get(waypoint.getControlPoint().getId()); |
| 5161 | 5187 | if (cp == null) { |
| 5162 | - cp = convertToControlPointDTO(waypoint.getControlPoint(), trackedRace, timePoint); |
|
| 5188 | + cp = convertToControlPointDTO(waypoint.getControlPoint(), positionFinder, timePoint); |
|
| 5163 | 5189 | controlPointCache.put(waypoint.getControlPoint().getId(), cp); |
| 5164 | 5190 | } |
| 5165 | 5191 | return new WaypointDTO(waypoint.getName(), cp, waypoint.getPassingInstructions()); |
| ... | ... | @@ -5175,13 +5201,13 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S |
| 5175 | 5201 | * the mark positions and attach to the {@link MarkDTO}s. If <code>null<c/code>, the current time point |
| 5176 | 5202 | * will be used as default. |
| 5177 | 5203 | */ |
| 5178 | - private RaceCourseDTO convertToRaceCourseDTO(CourseBase course, TrackedRace trackedRace, TimePoint timePoint) { |
|
| 5204 | + private RaceCourseDTO convertToRaceCourseDTO(CourseBase course, MarkPositionFinder positionFinder, TimePoint timePoint) { |
|
| 5179 | 5205 | final RaceCourseDTO result; |
| 5180 | 5206 | if (course != null) { |
| 5181 | 5207 | List<WaypointDTO> waypointDTOs = new ArrayList<WaypointDTO>(); |
| 5182 | 5208 | Map<Serializable, ControlPointDTO> controlPointCache = new HashMap<>(); |
| 5183 | 5209 | for (Waypoint waypoint : course.getWaypoints()) { |
| 5184 | - waypointDTOs.add(convertToWaypointDTO(waypoint, controlPointCache, trackedRace, timePoint)); |
|
| 5210 | + waypointDTOs.add(convertToWaypointDTO(waypoint, controlPointCache, positionFinder, timePoint)); |
|
| 5185 | 5211 | } |
| 5186 | 5212 | result = new RaceCourseDTO(waypointDTOs); |
| 5187 | 5213 | } else { |
| ... | ... | @@ -5191,13 +5217,18 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S |
| 5191 | 5217 | } |
| 5192 | 5218 | |
| 5193 | 5219 | @Override |
| 5194 | - public RaceCourseDTO getLastCourseDefinitionInRaceLog(String leaderboardName, String raceColumnName, String fleetName) { |
|
| 5195 | - RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName); |
|
| 5220 | + public RaceCourseDTO getLastCourseDefinitionInRaceLog(final String leaderboardName, String raceColumnName, String fleetName) { |
|
| 5221 | + final RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName); |
|
| 5196 | 5222 | CourseBase lastPublishedCourse = new LastPublishedCourseDesignFinder(raceLog).analyze(); |
| 5197 | 5223 | if (lastPublishedCourse == null) { |
| 5198 | 5224 | lastPublishedCourse = new CourseDataImpl(""); |
| 5199 | 5225 | } |
| 5200 | - return convertToRaceCourseDTO(lastPublishedCourse, /* trackedRace */ null, /* timePoint */ null); |
|
| 5226 | + return convertToRaceCourseDTO(lastPublishedCourse, new MarkPositionFinder() { |
|
| 5227 | + @Override |
|
| 5228 | + public Position find(Mark mark, TimePoint at) { |
|
| 5229 | + return getService().getMarkPosition(mark, (LeaderboardThatHasRegattaLike) getService().getLeaderboardByName(leaderboardName), at, raceLog); |
|
| 5230 | + } |
|
| 5231 | + }, /* timePoint */ MillisecondsTimePoint.now()); |
|
| 5201 | 5232 | } |
| 5202 | 5233 | |
| 5203 | 5234 | @Override |
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMainPanel.java
| ... | ... | @@ -309,7 +309,9 @@ public class SimulatorMainPanel extends SimplePanel { |
| 309 | 309 | timer.setTime(windParams.getStartTime().getTime()); |
| 310 | 310 | int secondsTimeStep = (int) windParams.getTimeStep().asSeconds(); |
| 311 | 311 | timer.setPlaySpeedFactor(secondsTimeStep); |
| 312 | - timePanel = new TimePanel<TimePanelSettings>(timer, timeRangeProvider, stringMessages, false); |
|
| 312 | + timePanel = new TimePanel<TimePanelSettings>(timer, timeRangeProvider, stringMessages, false, |
|
| 313 | + /* isScreenLargeEnoughToOfferChartSupport: no wind or competitor chart is shown; use full horizontal |
|
| 314 | + * extension of time panel */ false); |
|
| 313 | 315 | busyIndicator = new SimpleBusyIndicator(false, 0.8f); |
| 314 | 316 | simulatorMap = new SimulatorMap(simulatorSvc, stringMessages, errorReporter, xRes, yRes, border, streamletPars, |
| 315 | 317 | timer, timePanel, windParams, busyIndicator, mode, this, showMapControls, |
java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html
| ... | ... | @@ -4,6 +4,26 @@ |
| 4 | 4 | <div id="mainContent"> |
| 5 | 5 | <h4 class="articleHeadline">What's New - SAP Sailing Analytics</h4> |
| 6 | 6 | <div class="innerContent"> |
| 7 | + <h5 class="articleSubheadline">December 2015</h5> |
|
| 8 | + |
|
| 9 | + <ul class="bulletList"> |
|
| 10 | + <li>New line hover behavior in race map. When the mouse is moved onto a line, for example the start line, a transparent, |
|
| 11 | + wider line is activated behind it that acts as a buffer for the thin line, where the text displayed by the hover is still shown. |
|
| 12 | + This makes it easier to hover over the lines. These wider lines can be made semi transparent instead of transparent |
|
| 13 | + by unticking the corresponding checkbox in the settings of the race map. Furthermore the stroke weight of the wider lines can be |
|
| 14 | + configured in the same settings dialog. |
|
| 15 | + </li> |
|
| 16 | + <li>The race viewer (RaceBoard.html) now intelligently chooses a layout that suits the screen size. |
|
| 17 | + When only little horizontal space is available, only the sail number and nationality but not the |
|
| 18 | + competitor's long name is displayed by default. When vertical space is scarce, competitor and wind |
|
| 19 | + charts are disabled and more horizontal space can be given to the time slider at the bottom. The |
|
| 20 | + leaderboard section can then be scaled to smaller widths and will reduce the column widths better |
|
| 21 | + than in the past. |
|
| 22 | + </li> |
|
| 23 | + <li>With the changes described above, the RaceBoard is now also accessible from the races page on mobile devices. |
|
| 24 | + </li> |
|
| 25 | + </ul> |
|
| 26 | + |
|
| 7 | 27 | <h5 class="articleSubheadline">November 2015</h5> |
| 8 | 28 | |
| 9 | 29 | <ul class="bulletList"> |
java/com.sap.sailing.mongodb.test/META-INF/MANIFEST.MF
| ... | ... | @@ -27,7 +27,8 @@ Require-Bundle: com.sap.sailing.domain, |
| 27 | 27 | com.sap.sse.operationaltransformation, |
| 28 | 28 | com.sap.sse.replication, |
| 29 | 29 | com.sap.sse, |
| 30 | - com.sap.sse.filestorage |
|
| 30 | + com.sap.sse.filestorage, |
|
| 31 | + com.sap.sse.shared.android |
|
| 31 | 32 | Import-Package: com.sap.sailing.domain.common, |
| 32 | 33 | com.sap.sailing.domain.common.impl, |
| 33 | 34 | com.sap.sailing.domain.common.racelog, |
java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java
| ... | ... | @@ -1,836 +1,836 @@ |
| 1 | -package com.sap.sailing.mongodb.test;
|
|
| 2 | -
|
|
| 3 | -import static org.junit.Assert.assertEquals;
|
|
| 4 | -import static org.junit.Assert.assertFalse;
|
|
| 5 | -import static org.junit.Assert.assertNotNull;
|
|
| 6 | -import static org.junit.Assert.assertNotSame;
|
|
| 7 | -import static org.junit.Assert.assertNull;
|
|
| 8 | -import static org.junit.Assert.assertSame;
|
|
| 9 | -import static org.junit.Assert.assertTrue;
|
|
| 10 | -
|
|
| 11 | -import java.io.Serializable;
|
|
| 12 | -import java.net.MalformedURLException;
|
|
| 13 | -import java.net.URL;
|
|
| 14 | -import java.net.UnknownHostException;
|
|
| 15 | -import java.util.ArrayList;
|
|
| 16 | -import java.util.Arrays;
|
|
| 17 | -import java.util.Calendar;
|
|
| 18 | -import java.util.Collections;
|
|
| 19 | -import java.util.Iterator;
|
|
| 20 | -import java.util.List;
|
|
| 21 | -import java.util.Locale;
|
|
| 22 | -import java.util.UUID;
|
|
| 23 | -import java.util.logging.Logger;
|
|
| 24 | -
|
|
| 25 | -import org.junit.Test;
|
|
| 26 | -
|
|
| 27 | -import com.mongodb.MongoException;
|
|
| 28 | -import com.sap.sailing.domain.base.BoatClass;
|
|
| 29 | -import com.sap.sailing.domain.base.Competitor;
|
|
| 30 | -import com.sap.sailing.domain.base.Course;
|
|
| 31 | -import com.sap.sailing.domain.base.CourseArea;
|
|
| 32 | -import com.sap.sailing.domain.base.DomainFactory;
|
|
| 33 | -import com.sap.sailing.domain.base.Event;
|
|
| 34 | -import com.sap.sailing.domain.base.Fleet;
|
|
| 35 | -import com.sap.sailing.domain.base.RaceColumn;
|
|
| 36 | -import com.sap.sailing.domain.base.RaceColumnInSeries;
|
|
| 37 | -import com.sap.sailing.domain.base.RaceDefinition;
|
|
| 38 | -import com.sap.sailing.domain.base.Regatta;
|
|
| 39 | -import com.sap.sailing.domain.base.Series;
|
|
| 40 | -import com.sap.sailing.domain.base.Venue;
|
|
| 41 | -import com.sap.sailing.domain.base.Waypoint;
|
|
| 42 | -import com.sap.sailing.domain.base.configuration.impl.RegattaConfigurationImpl;
|
|
| 43 | -import com.sap.sailing.domain.base.impl.CompetitorImpl;
|
|
| 44 | -import com.sap.sailing.domain.base.impl.CourseImpl;
|
|
| 45 | -import com.sap.sailing.domain.base.impl.EventImpl;
|
|
| 46 | -import com.sap.sailing.domain.base.impl.FleetImpl;
|
|
| 47 | -import com.sap.sailing.domain.base.impl.RaceColumnInSeriesImpl;
|
|
| 48 | -import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
|
|
| 49 | -import com.sap.sailing.domain.base.impl.RegattaImpl;
|
|
| 50 | -import com.sap.sailing.domain.base.impl.SeriesImpl;
|
|
| 51 | -import com.sap.sailing.domain.base.impl.VenueImpl;
|
|
| 52 | -import com.sap.sailing.domain.common.MaxPointsReason;
|
|
| 53 | -import com.sap.sailing.domain.common.RaceIdentifier;
|
|
| 54 | -import com.sap.sailing.domain.common.RankingMetrics;
|
|
| 55 | -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
|
| 56 | -import com.sap.sailing.domain.common.RegattaNameAndRaceName;
|
|
| 57 | -import com.sap.sailing.domain.common.ScoringSchemeType;
|
|
| 58 | -import com.sap.sailing.domain.common.racelog.RacingProcedureType;
|
|
| 59 | -import com.sap.sailing.domain.leaderboard.EventResolver;
|
|
| 60 | -import com.sap.sailing.domain.leaderboard.Leaderboard;
|
|
| 61 | -import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
|
|
| 62 | -import com.sap.sailing.domain.leaderboard.LeaderboardGroupResolver;
|
|
| 63 | -import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
|
|
| 64 | -import com.sap.sailing.domain.leaderboard.ScoringScheme;
|
|
| 65 | -import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
|
|
| 66 | -import com.sap.sailing.domain.leaderboard.impl.HighPoint;
|
|
| 67 | -import com.sap.sailing.domain.leaderboard.impl.LeaderboardGroupImpl;
|
|
| 68 | -import com.sap.sailing.domain.leaderboard.impl.LowPoint;
|
|
| 69 | -import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl;
|
|
| 70 | -import com.sap.sailing.domain.persistence.DomainObjectFactory;
|
|
| 71 | -import com.sap.sailing.domain.persistence.MongoObjectFactory;
|
|
| 72 | -import com.sap.sailing.domain.persistence.PersistenceFactory;
|
|
| 73 | -import com.sap.sailing.domain.persistence.impl.CollectionNames;
|
|
| 74 | -import com.sap.sailing.domain.persistence.media.MediaDBFactory;
|
|
| 75 | -import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore;
|
|
| 76 | -import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
|
|
| 77 | -import com.sap.sailing.domain.ranking.RankingMetricConstructor;
|
|
| 78 | -import com.sap.sailing.domain.ranking.TimeOnTimeAndDistanceRankingMetric;
|
|
| 79 | -import com.sap.sailing.domain.test.AbstractLeaderboardTest;
|
|
| 80 | -import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithFixedRank;
|
|
| 81 | -import com.sap.sailing.domain.tracking.DynamicTrackedRace;
|
|
| 82 | -import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
|
|
| 83 | -import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
|
|
| 84 | -import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
|
|
| 85 | -import com.sap.sailing.server.RacingEventService;
|
|
| 86 | -import com.sap.sailing.server.impl.RacingEventServiceImpl;
|
|
| 87 | -import com.sap.sailing.server.operationaltransformation.ConnectTrackedRaceToLeaderboardColumn;
|
|
| 88 | -import com.sap.sailing.server.operationaltransformation.UpdateLeaderboardMaxPointsReason;
|
|
| 89 | -import com.sap.sse.common.Color;
|
|
| 90 | -import com.sap.sse.common.TimePoint;
|
|
| 91 | -import com.sap.sse.common.Util;
|
|
| 92 | -import com.sap.sse.common.impl.MillisecondsTimePoint;
|
|
| 93 | -import com.sap.sse.common.media.ImageDescriptor;
|
|
| 94 | -import com.sap.sse.common.media.ImageDescriptorImpl;
|
|
| 95 | -import com.sap.sse.common.media.MediaTagConstants;
|
|
| 96 | -import com.sap.sse.common.media.MimeType;
|
|
| 97 | -import com.sap.sse.common.media.VideoDescriptor;
|
|
| 98 | -import com.sap.sse.common.media.VideoDescriptorImpl;
|
|
| 99 | -
|
|
| 100 | -public class TestStoringAndLoadingEventsAndRegattas extends AbstractMongoDBTest {
|
|
| 101 | - private static final Logger logger = Logger.getLogger(TestStoringAndLoadingEventsAndRegattas.class.getName());
|
|
| 102 | -
|
|
| 103 | - private final TimePoint eventStartDate;
|
|
| 104 | - private final TimePoint eventEndDate;
|
|
| 105 | - private final TimePoint regattaStartDate;
|
|
| 106 | - private final TimePoint regattaEndDate;
|
|
| 107 | -
|
|
| 108 | - public TestStoringAndLoadingEventsAndRegattas() throws UnknownHostException, MongoException {
|
|
| 109 | - super();
|
|
| 110 | -
|
|
| 111 | - Calendar cal = Calendar.getInstance();
|
|
| 112 | -
|
|
| 113 | - cal.set(2012, 12, 1);
|
|
| 114 | - eventStartDate = new MillisecondsTimePoint(cal.getTimeInMillis());
|
|
| 115 | - cal.set(2012, 12, 5);
|
|
| 116 | - eventEndDate = new MillisecondsTimePoint(cal.getTimeInMillis());
|
|
| 117 | -
|
|
| 118 | - cal.set(2012, 12, 2);
|
|
| 119 | - regattaStartDate = new MillisecondsTimePoint(cal.getTimeInMillis());
|
|
| 120 | - cal.set(2012, 12, 3);
|
|
| 121 | - regattaEndDate = new MillisecondsTimePoint(cal.getTimeInMillis());
|
|
| 122 | - }
|
|
| 123 | -
|
|
| 124 | - private LeaderboardGroup createLeaderboardGroup(String name) {
|
|
| 125 | - return new LeaderboardGroupImpl(name, "Description for "+name, /* displayName */ null, /* displayInReverseOrder */ false, Collections.<Leaderboard>emptyList());
|
|
| 126 | - }
|
|
| 127 | -
|
|
| 128 | - @Test
|
|
| 129 | - public void testLoadStoreSimpleEventWithLinkToLeaderboardGroups() throws MalformedURLException {
|
|
| 130 | - final String eventName = "Event Name";
|
|
| 131 | - final String eventDescription = "Event Description";
|
|
| 132 | - final String venueName = "Venue Name";
|
|
| 133 | - final String[] courseAreaNames = new String[] { "Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrott" };
|
|
| 134 | - final URL officialWebsiteURL = new URL("http://official.website.com");
|
|
| 135 | - final URL sailorsInfoWebsiteURL = new URL("http://sailorsinfo.website.com");
|
|
| 136 | - final Venue venue = new VenueImpl(venueName);
|
|
| 137 | -
|
|
| 138 | - for (String courseAreaName : courseAreaNames) {
|
|
| 139 | - CourseArea courseArea = DomainFactory.INSTANCE.getOrCreateCourseArea(UUID.randomUUID(), courseAreaName);
|
|
| 140 | - venue.addCourseArea(courseArea);
|
|
| 141 | - }
|
|
| 142 | - MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
|
|
| 143 | - final Event event = new EventImpl(eventName, eventStartDate, eventEndDate, venue, /*isPublic*/ true, UUID.randomUUID());
|
|
| 144 | - final LeaderboardGroup lg1 = createLeaderboardGroup("lg1");
|
|
| 145 | - final LeaderboardGroup lg2 = createLeaderboardGroup("lg2");
|
|
| 146 | - event.addLeaderboardGroup(lg1);
|
|
| 147 | - event.addLeaderboardGroup(lg2);
|
|
| 148 | - event.setDescription(eventDescription);
|
|
| 149 | - event.setOfficialWebsiteURL(officialWebsiteURL);
|
|
| 150 | - event.setSailorsInfoWebsiteURL(sailorsInfoWebsiteURL);
|
|
| 151 | - mof.storeEvent(event);
|
|
| 152 | -
|
|
| 153 | - DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
|
|
| 154 | - final Event loadedEvent = dof.loadEvent(eventName);
|
|
| 155 | - dof.loadLeaderboardGroupLinksForEvents(new EventResolver() {
|
|
| 156 | - @Override
|
|
| 157 | - public Event getEvent(Serializable id) {
|
|
| 158 | - return id.equals(loadedEvent.getId()) ? loadedEvent : null;
|
|
| 159 | - }
|
|
| 160 | - }, new LeaderboardGroupResolver() {
|
|
| 161 | - @Override
|
|
| 162 | - public LeaderboardGroup getLeaderboardGroupByName(String leaderboardGroupName) {
|
|
| 163 | - return leaderboardGroupName.equals(lg1.getName()) ? lg1 : leaderboardGroupName.equals(lg2.getName()) ? lg2 : null;
|
|
| 164 | - }
|
|
| 165 | -
|
|
| 166 | - @Override
|
|
| 167 | - public LeaderboardGroup getLeaderboardGroupByID(UUID leaderboardGroupID) {
|
|
| 168 | - return leaderboardGroupID.equals(lg1.getId()) ? lg1 : leaderboardGroupID.equals(lg2.getId()) ? lg2 : null;
|
|
| 169 | - }
|
|
| 170 | - });
|
|
| 171 | - assertNotNull(loadedEvent);
|
|
| 172 | - assertEquals(eventName, loadedEvent.getName());
|
|
| 173 | - assertEquals(eventDescription, loadedEvent.getDescription());
|
|
| 174 | - assertEquals(event.getOfficialWebsiteURL(), loadedEvent.getOfficialWebsiteURL());
|
|
| 175 | - assertEquals(event.getSailorsInfoWebsiteURL(), loadedEvent.getSailorsInfoWebsiteURL());
|
|
| 176 | - assertEquals(2, Util.size(loadedEvent.getLeaderboardGroups()));
|
|
| 177 | - Iterator<LeaderboardGroup> lgIter = loadedEvent.getLeaderboardGroups().iterator();
|
|
| 178 | - assertSame(lg1, lgIter.next());
|
|
| 179 | - assertSame(lg2, lgIter.next());
|
|
| 180 | - final Venue loadedVenue = loadedEvent.getVenue();
|
|
| 181 | - assertNotNull(loadedVenue);
|
|
| 182 | - assertEquals(venueName, loadedVenue.getName());
|
|
| 183 | - assertEquals(courseAreaNames.length, Util.size(loadedVenue.getCourseAreas()));
|
|
| 184 | - int i=0;
|
|
| 185 | - for (CourseArea loadedCourseArea : loadedVenue.getCourseAreas()) {
|
|
| 186 | - assertEquals(courseAreaNames[i++], loadedCourseArea.getName());
|
|
| 187 | - }
|
|
| 188 | - }
|
|
| 189 | -
|
|
| 190 | - @Test
|
|
| 191 | - public void testLoadStoreSimpleEventAndRegattaWithCourseArea() {
|
|
| 192 | - final String eventName = "Event Name";
|
|
| 193 | - final String venueName = "Venue Name";
|
|
| 194 | - final String courseAreaName = "Alpha";
|
|
| 195 | - final Venue venue = new VenueImpl(venueName);
|
|
| 196 | - CourseArea courseArea = DomainFactory.INSTANCE.getOrCreateCourseArea(UUID.randomUUID(), courseAreaName);
|
|
| 197 | - venue.addCourseArea(courseArea);
|
|
| 198 | -
|
|
| 199 | - MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
|
|
| 200 | - Event event = new EventImpl(eventName, eventStartDate, eventEndDate, venue, /*isPublic*/ true, UUID.randomUUID());
|
|
| 201 | - mof.storeEvent(event);
|
|
| 202 | -
|
|
| 203 | - final String regattaBaseName = "Kieler Woche";
|
|
| 204 | - BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
|
|
| 205 | - Regatta regatta = createRegatta(RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass,
|
|
| 206 | - regattaStartDate, regattaEndDate, /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), courseArea, OneDesignRankingMetric::new);
|
|
| 207 | - mof.storeRegatta(regatta);
|
|
| 208 | -
|
|
| 209 | - DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
|
|
| 210 | - Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
|
|
| 211 | - assertNotNull(loadedRegatta);
|
|
| 212 | - assertEquals(regatta.getName(), loadedRegatta.getName());
|
|
| 213 | - assertEquals(Util.size(regatta.getSeries()), Util.size(loadedRegatta.getSeries()));
|
|
| 214 | - assertNotNull(loadedRegatta.getDefaultCourseArea());
|
|
| 215 | - assertEquals(loadedRegatta.getDefaultCourseArea().getId(), courseArea.getId());
|
|
| 216 | - assertEquals(loadedRegatta.getDefaultCourseArea().getName(), courseArea.getName());
|
|
| 217 | - assertEquals(loadedRegatta.getStartDate(), regattaStartDate);
|
|
| 218 | - assertEquals(loadedRegatta.getEndDate(), regattaEndDate);
|
|
| 219 | - }
|
|
| 220 | -
|
|
| 221 | - @Test
|
|
| 222 | - public void testLoadStoreSimpleEventWithImages() throws MalformedURLException {
|
|
| 223 | - final URL imageURL = new URL("http://some.host/with/some/file2.jpg");
|
|
| 224 | - final String copyright = "copyright by Alex";
|
|
| 225 | - final String imageTitle = "My image title";
|
|
| 226 | - final String imageSubtitle = "My image subtitle";
|
|
| 227 | - final Integer imageWidth = 500;
|
|
| 228 | - final Integer imageHeight = 300;
|
|
| 229 | - final TimePoint createdAt = MillisecondsTimePoint.now();
|
|
| 230 | - final String eventName = "Event Name";
|
|
| 231 | - final String venueName = "Venue Name";
|
|
| 232 | - final String courseAreaName = "Alpha";
|
|
| 233 | - final Venue venue = new VenueImpl(venueName);
|
|
| 234 | - CourseArea courseArea = DomainFactory.INSTANCE.getOrCreateCourseArea(UUID.randomUUID(), courseAreaName);
|
|
| 235 | - venue.addCourseArea(courseArea);
|
|
| 236 | -
|
|
| 237 | - MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
|
|
| 238 | - Event event = new EventImpl(eventName, eventStartDate, eventEndDate, venue, /*isPublic*/ true, UUID.randomUUID());
|
|
| 239 | -
|
|
| 240 | - ImageDescriptor image1 = new ImageDescriptorImpl(imageURL, createdAt);
|
|
| 241 | - image1.setCopyright(copyright);
|
|
| 242 | - image1.setSize(imageWidth, imageHeight);
|
|
| 243 | - image1.setTitle(imageTitle);
|
|
| 244 | - image1.setSubtitle(imageSubtitle);
|
|
| 245 | - image1.addTag("Tag1");
|
|
| 246 | - image1.addTag("Tag2");
|
|
| 247 | - image1.addTag("Tag3");
|
|
| 248 | - event.addImage(image1);
|
|
| 249 | -
|
|
| 250 | - ImageDescriptor image2 = new ImageDescriptorImpl(new URL("http://some.host/with/some/file2.jpg"), MillisecondsTimePoint.now());
|
|
| 251 | - image2.setCopyright("copyright");
|
|
| 252 | - event.addImage(image2);
|
|
| 253 | -
|
|
| 254 | - mof.storeEvent(event);
|
|
| 255 | -
|
|
| 256 | - DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
|
|
| 257 | - final Event loadedEvent = dof.loadEvent(eventName);
|
|
| 258 | - assertEquals(2, Util.size(loadedEvent.getImages()));
|
|
| 259 | - ImageDescriptor loadedImage1 = loadedEvent.getImages().iterator().next();
|
|
| 260 | - assertNotNull(loadedImage1);
|
|
| 261 | - assertEquals(imageURL, loadedImage1.getURL());
|
|
| 262 | - assertEquals(copyright, loadedImage1.getCopyright());
|
|
| 263 | - assertEquals(copyright, loadedImage1.getCopyright());
|
|
| 264 | - assertEquals(imageTitle, loadedImage1.getTitle());
|
|
| 265 | - assertEquals(imageSubtitle, loadedImage1.getSubtitle());
|
|
| 266 | - assertEquals(createdAt, loadedImage1.getCreatedAtDate());
|
|
| 267 | - assertEquals(imageWidth, loadedImage1.getWidthInPx());
|
|
| 268 | - assertEquals(imageHeight, loadedImage1.getHeightInPx());
|
|
| 269 | - assertEquals(3, Util.size(loadedImage1.getTags()));
|
|
| 270 | - }
|
|
| 271 | -
|
|
| 272 | - @Test
|
|
| 273 | - public void testLoadStoreSimpleEventWithVideos() throws MalformedURLException {
|
|
| 274 | - final URL videoURL = new URL("http://some.host/with/some/video.mpg");
|
|
| 275 | - final URL videoThumbnailURL = new URL("http://some.host/with/some/video_thumbnail.jpg");
|
|
| 276 | - final Locale locale = Locale.GERMAN;
|
|
| 277 | - final Integer videoLengthInSeconds = 2 * 60 * 60 * 1000; // 2h
|
|
| 278 | - final MimeType mimeType = MimeType.mp4;
|
|
| 279 | - final String copyright = "copyright by Don";
|
|
| 280 | - final String videoTitle = "My video title";
|
|
| 281 | - final String videoSubtitle = "My video subtitle";
|
|
| 282 | - final TimePoint createdAt = MillisecondsTimePoint.now();
|
|
| 283 | - final String eventName = "Event Name";
|
|
| 284 | - final String venueName = "Venue Name";
|
|
| 285 | - final String courseAreaName = "Alpha";
|
|
| 286 | - final Venue venue = new VenueImpl(venueName);
|
|
| 287 | - CourseArea courseArea = DomainFactory.INSTANCE.getOrCreateCourseArea(UUID.randomUUID(), courseAreaName);
|
|
| 288 | - venue.addCourseArea(courseArea);
|
|
| 289 | -
|
|
| 290 | - MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
|
|
| 291 | - Event event = new EventImpl(eventName, eventStartDate, eventEndDate, venue, /*isPublic*/ true, UUID.randomUUID());
|
|
| 292 | -
|
|
| 293 | - VideoDescriptor video1 = new VideoDescriptorImpl(videoURL, mimeType, createdAt);
|
|
| 294 | - video1.setCopyright(copyright);
|
|
| 295 | - video1.setTitle(videoTitle);
|
|
| 296 | - video1.setLocale(locale);
|
|
| 297 | - video1.setSubtitle(videoSubtitle);
|
|
| 298 | - video1.setThumbnailURL(videoThumbnailURL);
|
|
| 299 | - video1.setLengthInSeconds(videoLengthInSeconds);
|
|
| 300 | - video1.addTag("Tag1");
|
|
| 301 | - video1.addTag("Tag2");
|
|
| 302 | - video1.addTag("Tag3");
|
|
| 303 | - event.addVideo(video1);
|
|
| 304 | -
|
|
| 305 | - VideoDescriptor video2 = new VideoDescriptorImpl(new URL("http://some.host/with/some/file2.ogg"), MimeType.ogg, MillisecondsTimePoint.now());
|
|
| 306 | - video2.setCopyright("copyright");
|
|
| 307 | - event.addVideo(video2);
|
|
| 308 | -
|
|
| 309 | - mof.storeEvent(event);
|
|
| 310 | -
|
|
| 311 | - DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
|
|
| 312 | - final Event loadedEvent = dof.loadEvent(eventName);
|
|
| 313 | - assertEquals(2, Util.size(loadedEvent.getVideos()));
|
|
| 314 | - VideoDescriptor loadedVideo1 = loadedEvent.getVideos().iterator().next();
|
|
| 315 | - assertNotNull(loadedVideo1);
|
|
| 316 | - assertEquals(videoURL, loadedVideo1.getURL());
|
|
| 317 | - assertEquals(videoThumbnailURL, loadedVideo1.getThumbnailURL());
|
|
| 318 | - assertEquals(videoLengthInSeconds, loadedVideo1.getLengthInSeconds());
|
|
| 319 | - assertEquals(copyright, loadedVideo1.getCopyright());
|
|
| 320 | - assertEquals(locale, loadedVideo1.getLocale());
|
|
| 321 | - assertEquals(videoTitle, loadedVideo1.getTitle());
|
|
| 322 | - assertEquals(videoSubtitle, loadedVideo1.getSubtitle());
|
|
| 323 | - assertEquals(createdAt, loadedVideo1.getCreatedAtDate());
|
|
| 324 | - assertEquals(3, Util.size(loadedVideo1.getTags()));
|
|
| 325 | - }
|
|
| 326 | -
|
|
| 327 | - @SuppressWarnings("deprecation")
|
|
| 328 | - @Test
|
|
| 329 | - /**
|
|
| 330 | - * We expected that the migration code creates also an image URL for each image we create.
|
|
| 331 | - * Images with the 'Sponsor' tag should create a corresponding sponsor image URL
|
|
| 332 | - * Videos should create a video URL.
|
|
| 333 | - */
|
|
| 334 | - public void testLoadStoreSimpleEventWithImageAndVideoURLMigration() throws MalformedURLException {
|
|
| 335 | - final URL imageURL = new URL("http://some.host/with/some/bla.jpg");
|
|
| 336 | - final URL sponsorImageURL = new URL("http://some.host/with/some/sponsor.jpg");
|
|
| 337 | - final URL videoURL = new URL("http://some.host/with/some/video.mpg");
|
|
| 338 | - final TimePoint createdAt = MillisecondsTimePoint.now();
|
|
| 339 | - final String eventName = "Event Name";
|
|
| 340 | - final Venue venue = new VenueImpl("My Venue");
|
|
| 341 | - CourseArea courseArea = DomainFactory.INSTANCE.getOrCreateCourseArea(UUID.randomUUID(), "Alfa");
|
|
| 342 | - venue.addCourseArea(courseArea);
|
|
| 343 | -
|
|
| 344 | - MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
|
|
| 345 | - Event event = new EventImpl(eventName, eventStartDate, eventEndDate, venue, /*isPublic*/ true, UUID.randomUUID());
|
|
| 346 | -
|
|
| 347 | - ImageDescriptor image1 = new ImageDescriptorImpl(imageURL, createdAt);
|
|
| 348 | - image1.addTag(MediaTagConstants.GALLERY);
|
|
| 349 | - event.addImage(image1);
|
|
| 350 | -
|
|
| 351 | - ImageDescriptor image2 = new ImageDescriptorImpl(sponsorImageURL, createdAt);
|
|
| 352 | - event.addImage(image2);
|
|
| 353 | - image2.addTag(MediaTagConstants.SPONSOR);
|
|
| 354 | -
|
|
| 355 | - VideoDescriptor video1 = new VideoDescriptorImpl(videoURL, MimeType.mp4, createdAt);
|
|
| 356 | - event.addVideo(video1);
|
|
| 357 | -
|
|
| 358 | - mof.storeEvent(event);
|
|
| 359 | -
|
|
| 360 | - DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
|
|
| 361 | - final Event loadedEvent = dof.loadEvent(eventName);
|
|
| 362 | - assertEquals(2, Util.size(loadedEvent.getImages()));
|
|
| 363 | - assertEquals(1, Util.size(loadedEvent.getVideos()));
|
|
| 364 | - assertEquals(1, Util.size(loadedEvent.getImageURLs()));
|
|
| 365 | - assertEquals(1, Util.size(loadedEvent.getSponsorImageURLs()));
|
|
| 366 | - assertEquals(1, Util.size(loadedEvent.getVideoURLs()));
|
|
| 367 | - }
|
|
| 368 | -
|
|
| 369 | -
|
|
| 370 | - @Test
|
|
| 371 | - public void testLoadStoreRegattaConfiguration() {
|
|
| 372 | -
|
|
| 373 | - RegattaConfigurationImpl configuration = new RegattaConfigurationImpl();
|
|
| 374 | - configuration.setDefaultRacingProcedureType(RacingProcedureType.BASIC);
|
|
| 375 | -
|
|
| 376 | - BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("ESS40", false);
|
|
| 377 | - Regatta regatta = createRegattaAndAddRaceColumns(1, 1, RegattaImpl.getDefaultName("RR", boatClass.getName()), boatClass,
|
|
| 378 | - regattaStartDate, regattaEndDate, false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.HIGH_POINT), OneDesignRankingMetric::new);
|
|
| 379 | - regatta.setRegattaConfiguration(configuration);
|
|
| 380 | -
|
|
| 381 | - MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
|
|
| 382 | - mof.storeRegatta(regatta);
|
|
| 383 | - DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
|
|
| 384 | - Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), null);
|
|
| 385 | -
|
|
| 386 | - assertNotNull(loadedRegatta.getRegattaConfiguration());
|
|
| 387 | - assertEquals(RacingProcedureType.BASIC, loadedRegatta.getRegattaConfiguration().getDefaultRacingProcedureType());
|
|
| 388 | - }
|
|
| 389 | -
|
|
| 390 | - @Test
|
|
| 391 | - public void testLoadStoreSimpleRegattaLeaderboard() {
|
|
| 392 | - RacingEventService res = new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE), PersistenceFactory.INSTANCE
|
|
| 393 | - .getMongoObjectFactory(getMongoService()), MediaDBFactory.INSTANCE.getMediaDB(getMongoService()), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE);
|
|
| 394 | - final int numberOfQualifyingRaces = 5;
|
|
| 395 | - final int numberOfFinalRaces = 7;
|
|
| 396 | - final String regattaBaseName = "Kieler Woche";
|
|
| 397 | - BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
|
|
| 398 | - Regatta regattaProxy = createRegatta(RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass,
|
|
| 399 | - regattaStartDate, regattaEndDate, /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, OneDesignRankingMetric::new);
|
|
| 400 | - final String regattaName = regattaProxy.getName();
|
|
| 401 | - Regatta regatta = res.createRegatta(regattaName, regattaProxy.getBoatClass().getName(), regattaStartDate, regattaEndDate,
|
|
| 402 | - "123", regattaProxy.getSeries(), regattaProxy.isPersistent(), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new);
|
|
| 403 | - addRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regatta);
|
|
| 404 | - res.addRegattaLeaderboard(regatta.getRegattaIdentifier(), null, new int[] { 3, 5 });
|
|
| 405 | - DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
|
|
| 406 | - Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
|
|
| 407 | - assertNotNull(loadedRegatta);
|
|
| 408 | - assertEquals(regatta.getName(), loadedRegatta.getName());
|
|
| 409 | - assertEquals(Util.size(regatta.getSeries()), Util.size(loadedRegatta.getSeries()));
|
|
| 410 | -
|
|
| 411 | - Leaderboard loadedLeaderboard = dof.loadLeaderboard(regatta.getName(), res);
|
|
| 412 | - assertNotNull(loadedLeaderboard);
|
|
| 413 | - assertTrue(loadedLeaderboard instanceof RegattaLeaderboard);
|
|
| 414 | - RegattaLeaderboard loadedRegattaLeaderboard = (RegattaLeaderboard) loadedLeaderboard;
|
|
| 415 | - assertSame(regatta, loadedRegattaLeaderboard.getRegatta());
|
|
| 416 | - }
|
|
| 417 | -
|
|
| 418 | - @Test
|
|
| 419 | - public void testLoadStoreRegattaLeaderboardWithScoreCorrections() {
|
|
| 420 | - // for some reason the dropping of collections doesn't work reliably on Linux... explicitly drop those collections that we depend on
|
|
| 421 | - getMongoService().getDB().getCollection(CollectionNames.LEADERBOARDS.name()).drop();
|
|
| 422 | - getMongoService().getDB().getCollection(CollectionNames.REGATTAS.name()).drop();
|
|
| 423 | - Competitor hasso = AbstractLeaderboardTest.createCompetitor("Dr. Hasso Plattner");
|
|
| 424 | - BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
|
|
| 425 | - final DynamicTrackedRegatta[] trackedRegatta = new DynamicTrackedRegatta[1];
|
|
| 426 | - final DynamicTrackedRace q2YellowTrackedRace = new MockedTrackedRaceWithFixedRank(hasso, /* rank */ 1, /* started */ false, boatClass) {
|
|
| 427 | - private static final long serialVersionUID = 1234L;
|
|
| 428 | - @Override
|
|
| 429 | - public RegattaAndRaceIdentifier getRaceIdentifier() {
|
|
| 430 | - return new RegattaNameAndRaceName("Kieler Woche (29ERXX)", "Yellow Race 2");
|
|
| 431 | - }
|
|
| 432 | - @Override
|
|
| 433 | - public DynamicTrackedRegatta getTrackedRegatta() {
|
|
| 434 | - return trackedRegatta[0];
|
|
| 435 | - }
|
|
| 436 | - };
|
|
| 437 | - RacingEventService res = createRacingEventServiceWithOneMockedTrackedRace(q2YellowTrackedRace);
|
|
| 438 | - final int numberOfQualifyingRaces = 5;
|
|
| 439 | - final int numberOfFinalRaces = 7;
|
|
| 440 | - final String regattaBaseName = "Kieler Woche";
|
|
| 441 | -
|
|
| 442 | - Regatta regattaProxy = createRegatta(RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass, regattaStartDate, regattaEndDate, /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, OneDesignRankingMetric::new);
|
|
| 443 | - Regatta regatta = res.createRegatta(regattaProxy.getName(), regattaProxy.getBoatClass().getName(), regattaProxy.getStartDate(), regattaProxy.getEndDate(),
|
|
| 444 | - "123", regattaProxy.getSeries(), regattaProxy.isPersistent(), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new);
|
|
| 445 | - trackedRegatta[0] = new DynamicTrackedRegattaImpl(regatta);
|
|
| 446 | - addRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regatta);
|
|
| 447 | - logColumnsInRegatta(regatta);
|
|
| 448 | - RegattaLeaderboard regattaLeaderboard = res.addRegattaLeaderboard(regatta.getRegattaIdentifier(), null, new int[] { 3, 5 });
|
|
| 449 | - assertSame(regatta, regattaLeaderboard.getRegatta());
|
|
| 450 | - final RaceColumnInSeries q2 = regatta.getSeriesByName("Qualifying").getRaceColumnByName("Q2");
|
|
| 451 | - final Fleet yellow = q2.getFleetByName("Yellow");
|
|
| 452 | - logColumnsInRegatta(regatta);
|
|
| 453 | - logColumnsInRegattaLeaderboard(regattaLeaderboard);
|
|
| 454 | - assertNotNull(regattaLeaderboard.getRaceColumnByName(q2.getName()));
|
|
| 455 | - res.apply(new ConnectTrackedRaceToLeaderboardColumn(regattaLeaderboard.getName(), q2.getName(), yellow
|
|
| 456 | - .getName(), q2YellowTrackedRace.getRaceIdentifier()));
|
|
| 457 | - res.apply(new UpdateLeaderboardMaxPointsReason(regattaLeaderboard.getName(), q2.getName(), hasso.getId().toString(),
|
|
| 458 | - MaxPointsReason.DNF, MillisecondsTimePoint.now()));
|
|
| 459 | -
|
|
| 460 | - // load new RacingEventService including regatta and leaderboard
|
|
| 461 | - RacingEventService resForLoading = createRacingEventServiceWithOneMockedTrackedRace(q2YellowTrackedRace);
|
|
| 462 | - Regatta loadedRegatta = resForLoading.getRegattaByName("Kieler Woche (29ERXX)");
|
|
| 463 | - assertNotNull(loadedRegatta);
|
|
| 464 | - assertEquals(regatta.getName(), loadedRegatta.getName());
|
|
| 465 | - assertEquals(Util.size(regatta.getSeries()), Util.size(loadedRegatta.getSeries()));
|
|
| 466 | - Leaderboard loadedLeaderboard = resForLoading.getLeaderboardByName(loadedRegatta.getName());
|
|
| 467 | - assertNotNull(loadedLeaderboard);
|
|
| 468 | - assertEquals(((ThresholdBasedResultDiscardingRule) regattaLeaderboard.getResultDiscardingRule()).getDiscardIndexResultsStartingWithHowManyRaces().length,
|
|
| 469 | - ((ThresholdBasedResultDiscardingRule) loadedLeaderboard.getResultDiscardingRule()).getDiscardIndexResultsStartingWithHowManyRaces().length);
|
|
| 470 | - assertTrue(Arrays.equals(((ThresholdBasedResultDiscardingRule) regattaLeaderboard.getResultDiscardingRule()).getDiscardIndexResultsStartingWithHowManyRaces(),
|
|
| 471 | - ((ThresholdBasedResultDiscardingRule) loadedLeaderboard.getResultDiscardingRule()).getDiscardIndexResultsStartingWithHowManyRaces()));
|
|
| 472 | - assertTrue(loadedLeaderboard instanceof RegattaLeaderboard);
|
|
| 473 | - RegattaLeaderboard loadedRegattaLeaderboard = (RegattaLeaderboard) loadedLeaderboard;
|
|
| 474 | - assertSame(loadedRegatta, loadedRegattaLeaderboard.getRegatta());
|
|
| 475 | - // now re-associate the tracked race to let score correction "snap" to competitor:
|
|
| 476 | - final RaceColumnInSeries loadedQ2 = loadedRegatta.getSeriesByName("Qualifying").getRaceColumnByName("Q2");
|
|
| 477 | - final Fleet loadedYellow = loadedQ2.getFleetByName("Yellow");
|
|
| 478 | - // adjust tracked regatta for tracked race:
|
|
| 479 | - trackedRegatta[0] = new DynamicTrackedRegattaImpl(loadedRegatta);
|
|
| 480 | - resForLoading.apply(new ConnectTrackedRaceToLeaderboardColumn(loadedLeaderboard.getName(), loadedQ2.getName(), loadedYellow
|
|
| 481 | - .getName(), q2YellowTrackedRace.getRaceIdentifier()));
|
|
| 482 | - MaxPointsReason hassosLoadedMaxPointsReason = loadedLeaderboard.getScoreCorrection().getMaxPointsReason(hasso, loadedQ2, MillisecondsTimePoint.now());
|
|
| 483 | - assertEquals(MaxPointsReason.DNF, hassosLoadedMaxPointsReason);
|
|
| 484 | - }
|
|
| 485 | -
|
|
| 486 | - private void logColumnsInRegattaLeaderboard(RegattaLeaderboard regattaLeaderboard) {
|
|
| 487 | - StringBuilder rlbrcNames = new StringBuilder();
|
|
| 488 | - for (RaceColumn rlbrc : regattaLeaderboard.getRaceColumns()) {
|
|
| 489 | - rlbrcNames.append("; ");
|
|
| 490 | - rlbrcNames.append(rlbrc.getName());
|
|
| 491 | - }
|
|
| 492 | - logger.info("columns in regatta leaderboard for regatta "+regattaLeaderboard.getRegatta().getName()+" ("+
|
|
| 493 | - regattaLeaderboard.getRegatta().hashCode()+"): "+rlbrcNames);
|
|
| 494 | - logColumnsInRegatta(regattaLeaderboard.getRegatta());
|
|
| 495 | - }
|
|
| 496 | -
|
|
| 497 | - private void logColumnsInRegatta(Regatta regatta) {
|
|
| 498 | - StringBuilder rrcNames = new StringBuilder();
|
|
| 499 | - for (Series series : regatta.getSeries()) {
|
|
| 500 | - for (RaceColumn raceColumn : series.getRaceColumns()) {
|
|
| 501 | - rrcNames.append("; ");
|
|
| 502 | - rrcNames.append(raceColumn.getName());
|
|
| 503 | - }
|
|
| 504 | - }
|
|
| 505 | - logger.info("columns in regatta "+regatta.getName()+" ("+regatta.hashCode()+") : "+rrcNames);
|
|
| 506 | - }
|
|
| 507 | -
|
|
| 508 | - private RacingEventServiceImpl createRacingEventServiceWithOneMockedTrackedRace(final DynamicTrackedRace q2YellowTrackedRace) {
|
|
| 509 | - return new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE), PersistenceFactory.INSTANCE
|
|
| 510 | - .getMongoObjectFactory(getMongoService()), MediaDBFactory.INSTANCE.getMediaDB(getMongoService()), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE) {
|
|
| 511 | - @Override
|
|
| 512 | - public DynamicTrackedRace getExistingTrackedRace(RegattaAndRaceIdentifier raceIdentifier) {
|
|
| 513 | - return q2YellowTrackedRace;
|
|
| 514 | - }
|
|
| 515 | - };
|
|
| 516 | - }
|
|
| 517 | -
|
|
| 518 | - @Test
|
|
| 519 | - public void testLoadStoreSimpleRegatta() {
|
|
| 520 | - final int numberOfQualifyingRaces = 5;
|
|
| 521 | - final int numberOfFinalRaces = 7;
|
|
| 522 | - final String regattaBaseName = "Kieler Woche";
|
|
| 523 | - BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */true);
|
|
| 524 | - final String regattaName = RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName());
|
|
| 525 | - Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regattaName,
|
|
| 526 | - boatClass, regattaStartDate, regattaEndDate, /* persistent */false,
|
|
| 527 | - DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new);
|
|
| 528 | - MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
|
|
| 529 | - mof.storeRegatta(regatta);
|
|
| 530 | -
|
|
| 531 | - DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
|
|
| 532 | - Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
|
|
| 533 | - assertSame(LowPoint.class, loadedRegatta.getScoringScheme().getClass());
|
|
| 534 | - assertEquals(regattaName, loadedRegatta.getName());
|
|
| 535 | - Iterator<? extends Series> seriesIter = loadedRegatta.getSeries().iterator();
|
|
| 536 | - Series loadedQualifyingSeries = seriesIter.next();
|
|
| 537 | - assertEquals(numberOfQualifyingRaces, Util.size(loadedQualifyingSeries.getRaceColumns()));
|
|
| 538 | - assertEquals(0, loadedQualifyingSeries.getFleetByName("Yellow").compareTo(loadedQualifyingSeries.getFleetByName("Blue")));
|
|
| 539 | - Series loadedFinalSeries = seriesIter.next();
|
|
| 540 | - assertEquals(numberOfFinalRaces, Util.size(loadedFinalSeries.getRaceColumns()));
|
|
| 541 | - assertTrue(loadedFinalSeries.getFleetByName("Silver").compareTo(loadedFinalSeries.getFleetByName("Gold")) > 0);
|
|
| 542 | - Series loadedMedalSeries = seriesIter.next();
|
|
| 543 | - assertEquals(1, Util.size(loadedMedalSeries.getRaceColumns()));
|
|
| 544 | - assertEquals(loadedRegatta.getStartDate(), regattaStartDate);
|
|
| 545 | - assertEquals(loadedRegatta.getEndDate(), regattaEndDate);
|
|
| 546 | - assertEquals(RankingMetrics.ONE_DESIGN, loadedRegatta.getRankingMetricType());
|
|
| 547 | - }
|
|
| 548 | -
|
|
| 549 | - @Test
|
|
| 550 | - public void testLoadStoreRegattaWithHandicapRanking() {
|
|
| 551 | - final int numberOfQualifyingRaces = 5;
|
|
| 552 | - final int numberOfFinalRaces = 7;
|
|
| 553 | - final String regattaBaseName = "Kieler Woche";
|
|
| 554 | - BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */true);
|
|
| 555 | - final String regattaName = RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName());
|
|
| 556 | - Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regattaName,
|
|
| 557 | - boatClass, regattaStartDate, regattaEndDate, /* persistent */false,
|
|
| 558 | - DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), TimeOnTimeAndDistanceRankingMetric::new);
|
|
| 559 | - assertEquals(RankingMetrics.TIME_ON_TIME_AND_DISTANCE, regatta.getRankingMetricType());
|
|
| 560 | - MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
|
|
| 561 | - mof.storeRegatta(regatta);
|
|
| 562 | -
|
|
| 563 | - DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
|
|
| 564 | - Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
|
|
| 565 | - assertSame(LowPoint.class, loadedRegatta.getScoringScheme().getClass());
|
|
| 566 | - assertEquals(regattaName, loadedRegatta.getName());
|
|
| 567 | - Iterator<? extends Series> seriesIter = loadedRegatta.getSeries().iterator();
|
|
| 568 | - Series loadedQualifyingSeries = seriesIter.next();
|
|
| 569 | - assertEquals(numberOfQualifyingRaces, Util.size(loadedQualifyingSeries.getRaceColumns()));
|
|
| 570 | - assertEquals(0, loadedQualifyingSeries.getFleetByName("Yellow").compareTo(loadedQualifyingSeries.getFleetByName("Blue")));
|
|
| 571 | - Series loadedFinalSeries = seriesIter.next();
|
|
| 572 | - assertEquals(numberOfFinalRaces, Util.size(loadedFinalSeries.getRaceColumns()));
|
|
| 573 | - assertTrue(loadedFinalSeries.getFleetByName("Silver").compareTo(loadedFinalSeries.getFleetByName("Gold")) > 0);
|
|
| 574 | - Series loadedMedalSeries = seriesIter.next();
|
|
| 575 | - assertEquals(1, Util.size(loadedMedalSeries.getRaceColumns()));
|
|
| 576 | - assertEquals(loadedRegatta.getStartDate(), regattaStartDate);
|
|
| 577 | - assertEquals(loadedRegatta.getEndDate(), regattaEndDate);
|
|
| 578 | - assertEquals(RankingMetrics.TIME_ON_TIME_AND_DISTANCE, loadedRegatta.getRankingMetricType());
|
|
| 579 | - }
|
|
| 580 | -
|
|
| 581 | - @Test
|
|
| 582 | - public void testLoadStoreSimpleRegattaWithEmptyStartAndEndDate() {
|
|
| 583 | - final int numberOfQualifyingRaces = 1;
|
|
| 584 | - final int numberOfFinalRaces = 1;
|
|
| 585 | - final String regattaBaseName = "Kieler Woche";
|
|
| 586 | - BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */true);
|
|
| 587 | - final String regattaName = RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName());
|
|
| 588 | - Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regattaName,
|
|
| 589 | - boatClass, null, null, /* persistent */false,
|
|
| 590 | - DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new);
|
|
| 591 | - MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
|
|
| 592 | - mof.storeRegatta(regatta);
|
|
| 593 | -
|
|
| 594 | - DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
|
|
| 595 | - Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
|
|
| 596 | - assertEquals(regattaName, loadedRegatta.getName());
|
|
| 597 | - assertEquals(loadedRegatta.getStartDate(), null);
|
|
| 598 | - assertEquals(loadedRegatta.getEndDate(), null);
|
|
| 599 | - }
|
|
| 600 | -
|
|
| 601 | - @Test
|
|
| 602 | - public void testLoadStoreSimpleRegattaWithSeriesScoringScheme() {
|
|
| 603 | - final int numberOfQualifyingRaces = 5;
|
|
| 604 | - final int numberOfFinalRaces = 7;
|
|
| 605 | - final String regattaBaseName = "Kieler Woche";
|
|
| 606 | - BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
|
|
| 607 | - Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()),
|
|
| 608 | - boatClass, regattaStartDate, regattaEndDate, /* persistent */false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new);
|
|
| 609 | - regatta.getSeriesByName("Qualifying").setResultDiscardingRule(new ThresholdBasedResultDiscardingRuleImpl(new int[] { 1, 2, 3 }));
|
|
| 610 | - MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
|
|
| 611 | - mof.storeRegatta(regatta);
|
|
| 612 | -
|
|
| 613 | - DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
|
|
| 614 | - Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
|
|
| 615 | - assertTrue(Arrays.equals(new int[] { 1, 2, 3 },
|
|
| 616 | - loadedRegatta.getSeriesByName("Qualifying").getResultDiscardingRule().getDiscardIndexResultsStartingWithHowManyRaces()));
|
|
| 617 | - }
|
|
| 618 | -
|
|
| 619 | - @Test
|
|
| 620 | - public void testLoadStoreSimpleRegattaWithScoreForMedalStartingWithZero() {
|
|
| 621 | - final int numberOfQualifyingRaces = 5;
|
|
| 622 | - final int numberOfFinalRaces = 7;
|
|
| 623 | - final String regattaBaseName = "Kieler Woche";
|
|
| 624 | - BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
|
|
| 625 | - Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()),
|
|
| 626 | - boatClass, regattaStartDate, regattaEndDate, /* persistent */false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new);
|
|
| 627 | - regatta.getSeriesByName("Medal").setStartsWithZeroScore(true);
|
|
| 628 | - MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
|
|
| 629 | - mof.storeRegatta(regatta);
|
|
| 630 | -
|
|
| 631 | - DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
|
|
| 632 | - Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
|
|
| 633 | - assertFalse(loadedRegatta.getSeriesByName("Qualifying").isStartsWithZeroScore());
|
|
| 634 | - assertTrue(loadedRegatta.getSeriesByName("Medal").isStartsWithZeroScore());
|
|
| 635 | - }
|
|
| 636 | -
|
|
| 637 | - @Test
|
|
| 638 | - public void testLoadStoreSimpleRegattaWithHighPointScoringScheme() {
|
|
| 639 | - final int numberOfQualifyingRaces = 5;
|
|
| 640 | - final int numberOfFinalRaces = 7;
|
|
| 641 | - final String regattaBaseName = "ESS40 Cardiff 2012";
|
|
| 642 | - BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("ESS40", /* typicallyStartsUpwind */ false);
|
|
| 643 | - Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()),
|
|
| 644 | - boatClass, regattaStartDate, regattaEndDate, /* persistent */false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.HIGH_POINT), OneDesignRankingMetric::new);
|
|
| 645 | - MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
|
|
| 646 | - mof.storeRegatta(regatta);
|
|
| 647 | -
|
|
| 648 | - DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
|
|
| 649 | - Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
|
|
| 650 | - assertSame(HighPoint.class, loadedRegatta.getScoringScheme().getClass());
|
|
| 651 | - }
|
|
| 652 | -
|
|
| 653 | - @Test
|
|
| 654 | - public void testLoadStoreRegattaWithFleetsEnsuringIdenticalFleetsInSeriesAndRaceColumns() {
|
|
| 655 | - final int numberOfQualifyingRaces = 5;
|
|
| 656 | - final int numberOfFinalRaces = 7;
|
|
| 657 | - final String regattaBaseName = "Kieler Woche";
|
|
| 658 | - BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
|
|
| 659 | - final String regattaName = RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName());
|
|
| 660 | - Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces,
|
|
| 661 | - regattaName, boatClass, regattaStartDate, regattaEndDate,
|
|
| 662 | - /* persistent */false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new);
|
|
| 663 | - MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
|
|
| 664 | - mof.storeRegatta(regatta);
|
|
| 665 | -
|
|
| 666 | - DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
|
|
| 667 | - Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
|
|
| 668 | - assertEquals(regattaName, loadedRegatta.getName());
|
|
| 669 | - Iterator<? extends Series> seriesIter = loadedRegatta.getSeries().iterator();
|
|
| 670 | - Series loadedQualifyingSeries = seriesIter.next();
|
|
| 671 | - int i=1;
|
|
| 672 | - for (RaceColumn raceColumn : loadedQualifyingSeries.getRaceColumns()) {
|
|
| 673 | - assertTrue(raceColumn instanceof RaceColumnInSeriesImpl);
|
|
| 674 | - assertEquals("Q"+i, raceColumn.getName());
|
|
| 675 | - assertTrue(Util.equals(loadedQualifyingSeries.getFleets(), raceColumn.getFleets()));
|
|
| 676 | - i++;
|
|
| 677 | - }
|
|
| 678 | - Series loadedFinalSeries = seriesIter.next();
|
|
| 679 | - i=1;
|
|
| 680 | - for (RaceColumn raceColumn : loadedFinalSeries.getRaceColumns()) {
|
|
| 681 | - assertTrue(raceColumn instanceof RaceColumnInSeriesImpl);
|
|
| 682 | - assertEquals("F"+i, raceColumn.getName());
|
|
| 683 | - assertTrue(Util.equals(loadedFinalSeries.getFleets(), raceColumn.getFleets()));
|
|
| 684 | - i++;
|
|
| 685 | - }
|
|
| 686 | - Series loadedMedalSeries = seriesIter.next();
|
|
| 687 | - for (RaceColumn raceColumn : loadedMedalSeries.getRaceColumns()) {
|
|
| 688 | - assertTrue(raceColumn instanceof RaceColumnInSeriesImpl);
|
|
| 689 | - assertEquals("M", raceColumn.getName());
|
|
| 690 | - assertTrue(Util.equals(loadedMedalSeries.getFleets(), raceColumn.getFleets()));
|
|
| 691 | - }
|
|
| 692 | - }
|
|
| 693 | -
|
|
| 694 | - @Test
|
|
| 695 | - public void testLoadStoreRegattaWithFleetsEnsuringFleetOrdering() {
|
|
| 696 | - final String regattaBaseName = "Kieler Woche";
|
|
| 697 | - BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
|
|
| 698 | - final String regattaName = RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName());
|
|
| 699 | - Regatta regatta = createRegatta(regattaName, boatClass, regattaStartDate, regattaEndDate,
|
|
| 700 | - /* persistent */ false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, OneDesignRankingMetric::new);
|
|
| 701 | - MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
|
|
| 702 | - mof.storeRegatta(regatta);
|
|
| 703 | -
|
|
| 704 | - DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
|
|
| 705 | - Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
|
|
| 706 | - assertEquals(regattaName, loadedRegatta.getName());
|
|
| 707 | -
|
|
| 708 | - Iterator<? extends Series> seriesIter = loadedRegatta.getSeries().iterator();
|
|
| 709 | - Series loadedQualifyingSeries = seriesIter.next();
|
|
| 710 | -
|
|
| 711 | - Iterator<? extends Fleet> qualiFleetIt = loadedQualifyingSeries.getFleets().iterator();
|
|
| 712 | - Fleet qualiFleet1 = qualiFleetIt.next();
|
|
| 713 | - assertEquals(qualiFleet1.getName(), "Yellow");
|
|
| 714 | - Fleet qualiFleet2 = qualiFleetIt.next();
|
|
| 715 | - assertEquals(qualiFleet2.getName(), "Blue");
|
|
| 716 | -
|
|
| 717 | - Series loadedFinalSeries = seriesIter.next();
|
|
| 718 | - Iterator<? extends Fleet> finalFleetIt = loadedFinalSeries.getFleets().iterator();
|
|
| 719 | - Fleet finalFleet1 = finalFleetIt.next();
|
|
| 720 | - assertEquals(finalFleet1.getName(), "Gold");
|
|
| 721 | - Fleet finalFleet2 = finalFleetIt.next();
|
|
| 722 | - assertEquals(finalFleet2.getName(), "Silver");
|
|
| 723 | - }
|
|
| 724 | -
|
|
| 725 | - @Test
|
|
| 726 | - public void testStorageOfRaceIdentifiersOnRaceColumnInSeries() {
|
|
| 727 | - final int numberOfQualifyingRaces = 5;
|
|
| 728 | - final int numberOfFinalRaces = 7;
|
|
| 729 | - final String regattaBaseName = "Kieler Woche";
|
|
| 730 | - BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true);
|
|
| 731 | - Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces,
|
|
| 732 | - RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass, regattaStartDate, regattaEndDate,
|
|
| 733 | - /* persistent */false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new);
|
|
| 734 | - Series qualifyingSeries = regatta.getSeries().iterator().next();
|
|
| 735 | - RaceColumn q2 = qualifyingSeries.getRaceColumnByName("Q2");
|
|
| 736 | - final RegattaNameAndRaceName q2TrackedRaceIdentifier = new RegattaNameAndRaceName(regatta.getName(), "Q2 TracTrac");
|
|
| 737 | - q2.setRaceIdentifier(qualifyingSeries.getFleetByName("Yellow"), q2TrackedRaceIdentifier);
|
|
| 738 | - MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService());
|
|
| 739 | - mof.storeRegatta(regatta);
|
|
| 740 | -
|
|
| 741 | - DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE);
|
|
| 742 | - Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null);
|
|
| 743 | - Series loadedQualifyingSeries = loadedRegatta.getSeries().iterator().next();
|
|
| 744 | - RaceColumn loadedQ2 = loadedQualifyingSeries.getRaceColumnByName("Q2");
|
|
| 745 | - RaceIdentifier loadedQ2TrackedRaceIdentifier = loadedQ2.getRaceIdentifier(loadedQualifyingSeries.getFleetByName("Yellow"));
|
|
| 746 | - assertEquals(q2TrackedRaceIdentifier, loadedQ2TrackedRaceIdentifier);
|
|
| 747 | - assertNotSame(q2TrackedRaceIdentifier, loadedQ2TrackedRaceIdentifier);
|
|
| 748 | - assertNull(loadedQualifyingSeries.getRaceColumnByName("Q1").getRaceIdentifier(loadedQualifyingSeries.getFleetByName("Yellow")));
|
|
| 749 | - assertNull(loadedQualifyingSeries.getRaceColumnByName("Q2").getRaceIdentifier(loadedQualifyingSeries.getFleetByName("Blue")));
|
|
| 750 | - }
|
|
| 751 | -
|
|
| 752 | - private Regatta createRegattaAndAddRaceColumns(final int numberOfQualifyingRaces, final int numberOfFinalRaces,
|
|
| 753 | - final String regattaName, BoatClass boatClass, TimePoint startDate, TimePoint endDate, boolean persistent,
|
|
| 754 | - ScoringScheme scoringScheme, RankingMetricConstructor rankingMetricConstructor) {
|
|
| 755 | - Regatta regatta = createRegatta(regattaName, boatClass, startDate, endDate, persistent, scoringScheme, null,
|
|
| 756 | - rankingMetricConstructor);
|
|
| 757 | - addRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regatta);
|
|
| 758 | - return regatta;
|
|
| 759 | - }
|
|
| 760 | -
|
|
| 761 | - private void addRaceColumns(final int numberOfQualifyingRaces, final int numberOfFinalRaces, Regatta regatta) {
|
|
| 762 | - List<String> finalRaceColumnNames = new ArrayList<String>();
|
|
| 763 | - for (int i=1; i<=numberOfFinalRaces; i++) {
|
|
| 764 | - finalRaceColumnNames.add("F"+i);
|
|
| 765 | - }
|
|
| 766 | - List<String> qualifyingRaceColumnNames = new ArrayList<String>();
|
|
| 767 | - for (int i=1; i<=numberOfQualifyingRaces; i++) {
|
|
| 768 | - qualifyingRaceColumnNames.add("Q"+i);
|
|
| 769 | - }
|
|
| 770 | - List<String> medalRaceColumnNames = new ArrayList<String>();
|
|
| 771 | - medalRaceColumnNames.add("M");
|
|
| 772 | - addRaceColumnsToSeries(qualifyingRaceColumnNames, regatta.getSeriesByName("Qualifying"));
|
|
| 773 | - addRaceColumnsToSeries(finalRaceColumnNames, regatta.getSeriesByName("Final"));
|
|
| 774 | - addRaceColumnsToSeries(medalRaceColumnNames, regatta.getSeriesByName("Medal"));
|
|
| 775 | - }
|
|
| 776 | -
|
|
| 777 | - private Regatta createRegatta(final String regattaName, BoatClass boatClass, TimePoint startDate,
|
|
| 778 | - TimePoint endDate, boolean persistent, ScoringScheme scoringScheme, CourseArea courseArea,
|
|
| 779 | - RankingMetricConstructor rankingMetricConstructor) {
|
|
| 780 | - List<String> emptyRaceColumnNames = Collections.emptyList();
|
|
| 781 | - List<Series> series = new ArrayList<Series>();
|
|
| 782 | -
|
|
| 783 | - // -------- qualifying series ------------
|
|
| 784 | - List<Fleet> qualifyingFleets = new ArrayList<Fleet>();
|
|
| 785 | - qualifyingFleets.add(new FleetImpl("Yellow"));
|
|
| 786 | - qualifyingFleets.add(new FleetImpl("Blue"));
|
|
| 787 | - Series qualifyingSeries = new SeriesImpl("Qualifying", /* isMedal */false, qualifyingFleets,
|
|
| 788 | - emptyRaceColumnNames, /* trackedRegattaRegistry */ null);
|
|
| 789 | - series.add(qualifyingSeries);
|
|
| 790 | -
|
|
| 791 | - // -------- final series ------------
|
|
| 792 | - List<Fleet> finalFleets = new ArrayList<Fleet>();
|
|
| 793 | - finalFleets.add(new FleetImpl("Gold", 1));
|
|
| 794 | - finalFleets.add(new FleetImpl("Silver", 2));
|
|
| 795 | - Series finalSeries = new SeriesImpl("Final", /* isMedal */ false, finalFleets, emptyRaceColumnNames, /* trackedRegattaRegistry */ null);
|
|
| 796 | - series.add(finalSeries);
|
|
| 797 | -
|
|
| 798 | - // ------------ medal --------------
|
|
| 799 | - List<Fleet> medalFleets = new ArrayList<Fleet>();
|
|
| 800 | - medalFleets.add(new FleetImpl("Medal"));
|
|
| 801 | - Series medalSeries = new SeriesImpl("Medal", /* isMedal */ true, medalFleets, emptyRaceColumnNames, /* trackedRegattaRegistry */ null);
|
|
| 802 | - series.add(medalSeries);
|
|
| 803 | - Regatta regatta = new RegattaImpl(regattaName, boatClass, startDate, endDate, series, persistent, scoringScheme, "123", courseArea, rankingMetricConstructor);
|
|
| 804 | - return regatta;
|
|
| 805 | - }
|
|
| 806 | -
|
|
| 807 | - private void addRaceColumnsToSeries(List<String> finalRaceColumnNames, Series finalSeries) {
|
|
| 808 | - for (String raceColumnName : finalRaceColumnNames) {
|
|
| 809 | - finalSeries.addRaceColumn(raceColumnName, /* trackedRegattaRegistry */ null);
|
|
| 810 | - }
|
|
| 811 | - }
|
|
| 812 | -
|
|
| 813 | - @Test
|
|
| 814 | - public void testRegattaRaceAssociationStore() throws Exception {
|
|
| 815 | - BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("112er", /* typicallyStartsUpwind */ true);
|
|
| 816 | - Regatta regatta = createRegatta(RegattaImpl.getDefaultName("Cologne Masters", boatClass.getName()), boatClass,
|
|
| 817 | - regattaStartDate, regattaEndDate, /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, OneDesignRankingMetric::new);
|
|
| 818 | -
|
|
| 819 | - List<Competitor> competitors = new ArrayList<Competitor>();
|
|
| 820 | - competitors.add(new CompetitorImpl("Axel", "Axel Uhl", Color.RED, null, null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null));
|
|
| 821 | - Iterable<Waypoint> waypoints = Collections.emptyList();
|
|
| 822 | - Course course = new CourseImpl("Course", waypoints);
|
|
| 823 | -
|
|
| 824 | - RaceDefinition racedef = new RaceDefinitionImpl("M1", course, boatClass, competitors);
|
|
| 825 | - regatta.addRace(racedef);
|
|
| 826 | -
|
|
| 827 | - RacingEventServiceImpl evs = new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE), PersistenceFactory.INSTANCE
|
|
| 828 | - .getMongoObjectFactory(getMongoService()), MediaDBFactory.INSTANCE.getMediaDB(getMongoService()), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE);
|
|
| 829 | - assertNull(evs.getRememberedRegattaForRace(racedef.getId()));
|
|
| 830 | - evs.raceAdded(regatta, racedef);
|
|
| 831 | - assertNotNull(evs.getRememberedRegattaForRace(racedef.getId()));
|
|
| 832 | - evs.removeRegatta(regatta);
|
|
| 833 | - assertNull(evs.getRememberedRegattaForRace(racedef.getId()));
|
|
| 834 | - }
|
|
| 835 | -
|
|
| 836 | -}
|
|
| 1 | +package com.sap.sailing.mongodb.test; |
|
| 2 | + |
|
| 3 | +import static org.junit.Assert.assertEquals; |
|
| 4 | +import static org.junit.Assert.assertFalse; |
|
| 5 | +import static org.junit.Assert.assertNotNull; |
|
| 6 | +import static org.junit.Assert.assertNotSame; |
|
| 7 | +import static org.junit.Assert.assertNull; |
|
| 8 | +import static org.junit.Assert.assertSame; |
|
| 9 | +import static org.junit.Assert.assertTrue; |
|
| 10 | + |
|
| 11 | +import java.io.Serializable; |
|
| 12 | +import java.net.MalformedURLException; |
|
| 13 | +import java.net.URL; |
|
| 14 | +import java.net.UnknownHostException; |
|
| 15 | +import java.util.ArrayList; |
|
| 16 | +import java.util.Arrays; |
|
| 17 | +import java.util.Calendar; |
|
| 18 | +import java.util.Collections; |
|
| 19 | +import java.util.Iterator; |
|
| 20 | +import java.util.List; |
|
| 21 | +import java.util.Locale; |
|
| 22 | +import java.util.UUID; |
|
| 23 | +import java.util.logging.Logger; |
|
| 24 | + |
|
| 25 | +import org.junit.Test; |
|
| 26 | + |
|
| 27 | +import com.mongodb.MongoException; |
|
| 28 | +import com.sap.sailing.domain.base.BoatClass; |
|
| 29 | +import com.sap.sailing.domain.base.Competitor; |
|
| 30 | +import com.sap.sailing.domain.base.Course; |
|
| 31 | +import com.sap.sailing.domain.base.CourseArea; |
|
| 32 | +import com.sap.sailing.domain.base.DomainFactory; |
|
| 33 | +import com.sap.sailing.domain.base.Event; |
|
| 34 | +import com.sap.sailing.domain.base.Fleet; |
|
| 35 | +import com.sap.sailing.domain.base.RaceColumn; |
|
| 36 | +import com.sap.sailing.domain.base.RaceColumnInSeries; |
|
| 37 | +import com.sap.sailing.domain.base.RaceDefinition; |
|
| 38 | +import com.sap.sailing.domain.base.Regatta; |
|
| 39 | +import com.sap.sailing.domain.base.Series; |
|
| 40 | +import com.sap.sailing.domain.base.Venue; |
|
| 41 | +import com.sap.sailing.domain.base.Waypoint; |
|
| 42 | +import com.sap.sailing.domain.base.configuration.impl.RegattaConfigurationImpl; |
|
| 43 | +import com.sap.sailing.domain.base.impl.CompetitorImpl; |
|
| 44 | +import com.sap.sailing.domain.base.impl.CourseImpl; |
|
| 45 | +import com.sap.sailing.domain.base.impl.EventImpl; |
|
| 46 | +import com.sap.sailing.domain.base.impl.FleetImpl; |
|
| 47 | +import com.sap.sailing.domain.base.impl.RaceColumnInSeriesImpl; |
|
| 48 | +import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; |
|
| 49 | +import com.sap.sailing.domain.base.impl.RegattaImpl; |
|
| 50 | +import com.sap.sailing.domain.base.impl.SeriesImpl; |
|
| 51 | +import com.sap.sailing.domain.base.impl.VenueImpl; |
|
| 52 | +import com.sap.sailing.domain.common.MaxPointsReason; |
|
| 53 | +import com.sap.sailing.domain.common.RaceIdentifier; |
|
| 54 | +import com.sap.sailing.domain.common.RankingMetrics; |
|
| 55 | +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; |
|
| 56 | +import com.sap.sailing.domain.common.RegattaNameAndRaceName; |
|
| 57 | +import com.sap.sailing.domain.common.ScoringSchemeType; |
|
| 58 | +import com.sap.sailing.domain.common.racelog.RacingProcedureType; |
|
| 59 | +import com.sap.sailing.domain.leaderboard.EventResolver; |
|
| 60 | +import com.sap.sailing.domain.leaderboard.Leaderboard; |
|
| 61 | +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; |
|
| 62 | +import com.sap.sailing.domain.leaderboard.LeaderboardGroupResolver; |
|
| 63 | +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; |
|
| 64 | +import com.sap.sailing.domain.leaderboard.ScoringScheme; |
|
| 65 | +import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; |
|
| 66 | +import com.sap.sailing.domain.leaderboard.impl.HighPoint; |
|
| 67 | +import com.sap.sailing.domain.leaderboard.impl.LeaderboardGroupImpl; |
|
| 68 | +import com.sap.sailing.domain.leaderboard.impl.LowPoint; |
|
| 69 | +import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl; |
|
| 70 | +import com.sap.sailing.domain.persistence.DomainObjectFactory; |
|
| 71 | +import com.sap.sailing.domain.persistence.MongoObjectFactory; |
|
| 72 | +import com.sap.sailing.domain.persistence.PersistenceFactory; |
|
| 73 | +import com.sap.sailing.domain.persistence.impl.CollectionNames; |
|
| 74 | +import com.sap.sailing.domain.persistence.media.MediaDBFactory; |
|
| 75 | +import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; |
|
| 76 | +import com.sap.sailing.domain.ranking.OneDesignRankingMetric; |
|
| 77 | +import com.sap.sailing.domain.ranking.RankingMetricConstructor; |
|
| 78 | +import com.sap.sailing.domain.ranking.TimeOnTimeAndDistanceRankingMetric; |
|
| 79 | +import com.sap.sailing.domain.test.AbstractLeaderboardTest; |
|
| 80 | +import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithFixedRank; |
|
| 81 | +import com.sap.sailing.domain.tracking.DynamicTrackedRace; |
|
| 82 | +import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; |
|
| 83 | +import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl; |
|
| 84 | +import com.sap.sailing.domain.tracking.impl.EmptyWindStore; |
|
| 85 | +import com.sap.sailing.server.RacingEventService; |
|
| 86 | +import com.sap.sailing.server.impl.RacingEventServiceImpl; |
|
| 87 | +import com.sap.sailing.server.operationaltransformation.ConnectTrackedRaceToLeaderboardColumn; |
|
| 88 | +import com.sap.sailing.server.operationaltransformation.UpdateLeaderboardMaxPointsReason; |
|
| 89 | +import com.sap.sse.common.Color; |
|
| 90 | +import com.sap.sse.common.TimePoint; |
|
| 91 | +import com.sap.sse.common.Util; |
|
| 92 | +import com.sap.sse.common.impl.MillisecondsTimePoint; |
|
| 93 | +import com.sap.sse.common.media.MediaTagConstants; |
|
| 94 | +import com.sap.sse.common.media.MimeType; |
|
| 95 | +import com.sap.sse.shared.media.ImageDescriptor; |
|
| 96 | +import com.sap.sse.shared.media.VideoDescriptor; |
|
| 97 | +import com.sap.sse.shared.media.impl.ImageDescriptorImpl; |
|
| 98 | +import com.sap.sse.shared.media.impl.VideoDescriptorImpl; |
|
| 99 | + |
|
| 100 | +public class TestStoringAndLoadingEventsAndRegattas extends AbstractMongoDBTest { |
|
| 101 | + private static final Logger logger = Logger.getLogger(TestStoringAndLoadingEventsAndRegattas.class.getName()); |
|
| 102 | + |
|
| 103 | + private final TimePoint eventStartDate; |
|
| 104 | + private final TimePoint eventEndDate; |
|
| 105 | + private final TimePoint regattaStartDate; |
|
| 106 | + private final TimePoint regattaEndDate; |
|
| 107 | + |
|
| 108 | + public TestStoringAndLoadingEventsAndRegattas() throws UnknownHostException, MongoException { |
|
| 109 | + super(); |
|
| 110 | + |
|
| 111 | + Calendar cal = Calendar.getInstance(); |
|
| 112 | + |
|
| 113 | + cal.set(2012, 12, 1); |
|
| 114 | + eventStartDate = new MillisecondsTimePoint(cal.getTimeInMillis()); |
|
| 115 | + cal.set(2012, 12, 5); |
|
| 116 | + eventEndDate = new MillisecondsTimePoint(cal.getTimeInMillis()); |
|
| 117 | + |
|
| 118 | + cal.set(2012, 12, 2); |
|
| 119 | + regattaStartDate = new MillisecondsTimePoint(cal.getTimeInMillis()); |
|
| 120 | + cal.set(2012, 12, 3); |
|
| 121 | + regattaEndDate = new MillisecondsTimePoint(cal.getTimeInMillis()); |
|
| 122 | + } |
|
| 123 | + |
|
| 124 | + private LeaderboardGroup createLeaderboardGroup(String name) { |
|
| 125 | + return new LeaderboardGroupImpl(name, "Description for "+name, /* displayName */ null, /* displayInReverseOrder */ false, Collections.<Leaderboard>emptyList()); |
|
| 126 | + } |
|
| 127 | + |
|
| 128 | + @Test |
|
| 129 | + public void testLoadStoreSimpleEventWithLinkToLeaderboardGroups() throws MalformedURLException { |
|
| 130 | + final String eventName = "Event Name"; |
|
| 131 | + final String eventDescription = "Event Description"; |
|
| 132 | + final String venueName = "Venue Name"; |
|
| 133 | + final String[] courseAreaNames = new String[] { "Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrott" }; |
|
| 134 | + final URL officialWebsiteURL = new URL("http://official.website.com"); |
|
| 135 | + final URL sailorsInfoWebsiteURL = new URL("http://sailorsinfo.website.com"); |
|
| 136 | + final Venue venue = new VenueImpl(venueName); |
|
| 137 | + |
|
| 138 | + for (String courseAreaName : courseAreaNames) { |
|
| 139 | + CourseArea courseArea = DomainFactory.INSTANCE.getOrCreateCourseArea(UUID.randomUUID(), courseAreaName); |
|
| 140 | + venue.addCourseArea(courseArea); |
|
| 141 | + } |
|
| 142 | + MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService()); |
|
| 143 | + final Event event = new EventImpl(eventName, eventStartDate, eventEndDate, venue, /*isPublic*/ true, UUID.randomUUID()); |
|
| 144 | + final LeaderboardGroup lg1 = createLeaderboardGroup("lg1"); |
|
| 145 | + final LeaderboardGroup lg2 = createLeaderboardGroup("lg2"); |
|
| 146 | + event.addLeaderboardGroup(lg1); |
|
| 147 | + event.addLeaderboardGroup(lg2); |
|
| 148 | + event.setDescription(eventDescription); |
|
| 149 | + event.setOfficialWebsiteURL(officialWebsiteURL); |
|
| 150 | + event.setSailorsInfoWebsiteURL(sailorsInfoWebsiteURL); |
|
| 151 | + mof.storeEvent(event); |
|
| 152 | + |
|
| 153 | + DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); |
|
| 154 | + final Event loadedEvent = dof.loadEvent(eventName); |
|
| 155 | + dof.loadLeaderboardGroupLinksForEvents(new EventResolver() { |
|
| 156 | + @Override |
|
| 157 | + public Event getEvent(Serializable id) { |
|
| 158 | + return id.equals(loadedEvent.getId()) ? loadedEvent : null; |
|
| 159 | + } |
|
| 160 | + }, new LeaderboardGroupResolver() { |
|
| 161 | + @Override |
|
| 162 | + public LeaderboardGroup getLeaderboardGroupByName(String leaderboardGroupName) { |
|
| 163 | + return leaderboardGroupName.equals(lg1.getName()) ? lg1 : leaderboardGroupName.equals(lg2.getName()) ? lg2 : null; |
|
| 164 | + } |
|
| 165 | + |
|
| 166 | + @Override |
|
| 167 | + public LeaderboardGroup getLeaderboardGroupByID(UUID leaderboardGroupID) { |
|
| 168 | + return leaderboardGroupID.equals(lg1.getId()) ? lg1 : leaderboardGroupID.equals(lg2.getId()) ? lg2 : null; |
|
| 169 | + } |
|
| 170 | + }); |
|
| 171 | + assertNotNull(loadedEvent); |
|
| 172 | + assertEquals(eventName, loadedEvent.getName()); |
|
| 173 | + assertEquals(eventDescription, loadedEvent.getDescription()); |
|
| 174 | + assertEquals(event.getOfficialWebsiteURL(), loadedEvent.getOfficialWebsiteURL()); |
|
| 175 | + assertEquals(event.getSailorsInfoWebsiteURL(), loadedEvent.getSailorsInfoWebsiteURL()); |
|
| 176 | + assertEquals(2, Util.size(loadedEvent.getLeaderboardGroups())); |
|
| 177 | + Iterator<LeaderboardGroup> lgIter = loadedEvent.getLeaderboardGroups().iterator(); |
|
| 178 | + assertSame(lg1, lgIter.next()); |
|
| 179 | + assertSame(lg2, lgIter.next()); |
|
| 180 | + final Venue loadedVenue = loadedEvent.getVenue(); |
|
| 181 | + assertNotNull(loadedVenue); |
|
| 182 | + assertEquals(venueName, loadedVenue.getName()); |
|
| 183 | + assertEquals(courseAreaNames.length, Util.size(loadedVenue.getCourseAreas())); |
|
| 184 | + int i=0; |
|
| 185 | + for (CourseArea loadedCourseArea : loadedVenue.getCourseAreas()) { |
|
| 186 | + assertEquals(courseAreaNames[i++], loadedCourseArea.getName()); |
|
| 187 | + } |
|
| 188 | + } |
|
| 189 | + |
|
| 190 | + @Test |
|
| 191 | + public void testLoadStoreSimpleEventAndRegattaWithCourseArea() { |
|
| 192 | + final String eventName = "Event Name"; |
|
| 193 | + final String venueName = "Venue Name"; |
|
| 194 | + final String courseAreaName = "Alpha"; |
|
| 195 | + final Venue venue = new VenueImpl(venueName); |
|
| 196 | + CourseArea courseArea = DomainFactory.INSTANCE.getOrCreateCourseArea(UUID.randomUUID(), courseAreaName); |
|
| 197 | + venue.addCourseArea(courseArea); |
|
| 198 | + |
|
| 199 | + MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService()); |
|
| 200 | + Event event = new EventImpl(eventName, eventStartDate, eventEndDate, venue, /*isPublic*/ true, UUID.randomUUID()); |
|
| 201 | + mof.storeEvent(event); |
|
| 202 | + |
|
| 203 | + final String regattaBaseName = "Kieler Woche"; |
|
| 204 | + BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true); |
|
| 205 | + Regatta regatta = createRegatta(RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass, |
|
| 206 | + regattaStartDate, regattaEndDate, /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), courseArea, OneDesignRankingMetric::new); |
|
| 207 | + mof.storeRegatta(regatta); |
|
| 208 | + |
|
| 209 | + DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); |
|
| 210 | + Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null); |
|
| 211 | + assertNotNull(loadedRegatta); |
|
| 212 | + assertEquals(regatta.getName(), loadedRegatta.getName()); |
|
| 213 | + assertEquals(Util.size(regatta.getSeries()), Util.size(loadedRegatta.getSeries())); |
|
| 214 | + assertNotNull(loadedRegatta.getDefaultCourseArea()); |
|
| 215 | + assertEquals(loadedRegatta.getDefaultCourseArea().getId(), courseArea.getId()); |
|
| 216 | + assertEquals(loadedRegatta.getDefaultCourseArea().getName(), courseArea.getName()); |
|
| 217 | + assertEquals(loadedRegatta.getStartDate(), regattaStartDate); |
|
| 218 | + assertEquals(loadedRegatta.getEndDate(), regattaEndDate); |
|
| 219 | + } |
|
| 220 | + |
|
| 221 | + @Test |
|
| 222 | + public void testLoadStoreSimpleEventWithImages() throws MalformedURLException { |
|
| 223 | + final URL imageURL = new URL("http://some.host/with/some/file2.jpg"); |
|
| 224 | + final String copyright = "copyright by Alex"; |
|
| 225 | + final String imageTitle = "My image title"; |
|
| 226 | + final String imageSubtitle = "My image subtitle"; |
|
| 227 | + final Integer imageWidth = 500; |
|
| 228 | + final Integer imageHeight = 300; |
|
| 229 | + final TimePoint createdAt = MillisecondsTimePoint.now(); |
|
| 230 | + final String eventName = "Event Name"; |
|
| 231 | + final String venueName = "Venue Name"; |
|
| 232 | + final String courseAreaName = "Alpha"; |
|
| 233 | + final Venue venue = new VenueImpl(venueName); |
|
| 234 | + CourseArea courseArea = DomainFactory.INSTANCE.getOrCreateCourseArea(UUID.randomUUID(), courseAreaName); |
|
| 235 | + venue.addCourseArea(courseArea); |
|
| 236 | + |
|
| 237 | + MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService()); |
|
| 238 | + Event event = new EventImpl(eventName, eventStartDate, eventEndDate, venue, /*isPublic*/ true, UUID.randomUUID()); |
|
| 239 | + |
|
| 240 | + ImageDescriptor image1 = new ImageDescriptorImpl(imageURL, createdAt); |
|
| 241 | + image1.setCopyright(copyright); |
|
| 242 | + image1.setSize(imageWidth, imageHeight); |
|
| 243 | + image1.setTitle(imageTitle); |
|
| 244 | + image1.setSubtitle(imageSubtitle); |
|
| 245 | + image1.addTag("Tag1"); |
|
| 246 | + image1.addTag("Tag2"); |
|
| 247 | + image1.addTag("Tag3"); |
|
| 248 | + event.addImage(image1); |
|
| 249 | + |
|
| 250 | + ImageDescriptor image2 = new ImageDescriptorImpl(new URL("http://some.host/with/some/file2.jpg"), MillisecondsTimePoint.now()); |
|
| 251 | + image2.setCopyright("copyright"); |
|
| 252 | + event.addImage(image2); |
|
| 253 | + |
|
| 254 | + mof.storeEvent(event); |
|
| 255 | + |
|
| 256 | + DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); |
|
| 257 | + final Event loadedEvent = dof.loadEvent(eventName); |
|
| 258 | + assertEquals(2, Util.size(loadedEvent.getImages())); |
|
| 259 | + ImageDescriptor loadedImage1 = loadedEvent.getImages().iterator().next(); |
|
| 260 | + assertNotNull(loadedImage1); |
|
| 261 | + assertEquals(imageURL, loadedImage1.getURL()); |
|
| 262 | + assertEquals(copyright, loadedImage1.getCopyright()); |
|
| 263 | + assertEquals(copyright, loadedImage1.getCopyright()); |
|
| 264 | + assertEquals(imageTitle, loadedImage1.getTitle()); |
|
| 265 | + assertEquals(imageSubtitle, loadedImage1.getSubtitle()); |
|
| 266 | + assertEquals(createdAt, loadedImage1.getCreatedAtDate()); |
|
| 267 | + assertEquals(imageWidth, loadedImage1.getWidthInPx()); |
|
| 268 | + assertEquals(imageHeight, loadedImage1.getHeightInPx()); |
|
| 269 | + assertEquals(3, Util.size(loadedImage1.getTags())); |
|
| 270 | + } |
|
| 271 | + |
|
| 272 | + @Test |
|
| 273 | + public void testLoadStoreSimpleEventWithVideos() throws MalformedURLException { |
|
| 274 | + final URL videoURL = new URL("http://some.host/with/some/video.mpg"); |
|
| 275 | + final URL videoThumbnailURL = new URL("http://some.host/with/some/video_thumbnail.jpg"); |
|
| 276 | + final Locale locale = Locale.GERMAN; |
|
| 277 | + final Integer videoLengthInSeconds = 2 * 60 * 60 * 1000; // 2h |
|
| 278 | + final MimeType mimeType = MimeType.mp4; |
|
| 279 | + final String copyright = "copyright by Don"; |
|
| 280 | + final String videoTitle = "My video title"; |
|
| 281 | + final String videoSubtitle = "My video subtitle"; |
|
| 282 | + final TimePoint createdAt = MillisecondsTimePoint.now(); |
|
| 283 | + final String eventName = "Event Name"; |
|
| 284 | + final String venueName = "Venue Name"; |
|
| 285 | + final String courseAreaName = "Alpha"; |
|
| 286 | + final Venue venue = new VenueImpl(venueName); |
|
| 287 | + CourseArea courseArea = DomainFactory.INSTANCE.getOrCreateCourseArea(UUID.randomUUID(), courseAreaName); |
|
| 288 | + venue.addCourseArea(courseArea); |
|
| 289 | + |
|
| 290 | + MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService()); |
|
| 291 | + Event event = new EventImpl(eventName, eventStartDate, eventEndDate, venue, /*isPublic*/ true, UUID.randomUUID()); |
|
| 292 | + |
|
| 293 | + VideoDescriptor video1 = new VideoDescriptorImpl(videoURL, mimeType, createdAt); |
|
| 294 | + video1.setCopyright(copyright); |
|
| 295 | + video1.setTitle(videoTitle); |
|
| 296 | + video1.setLocale(locale); |
|
| 297 | + video1.setSubtitle(videoSubtitle); |
|
| 298 | + video1.setThumbnailURL(videoThumbnailURL); |
|
| 299 | + video1.setLengthInSeconds(videoLengthInSeconds); |
|
| 300 | + video1.addTag("Tag1"); |
|
| 301 | + video1.addTag("Tag2"); |
|
| 302 | + video1.addTag("Tag3"); |
|
| 303 | + event.addVideo(video1); |
|
| 304 | + |
|
| 305 | + VideoDescriptor video2 = new VideoDescriptorImpl(new URL("http://some.host/with/some/file2.ogg"), MimeType.ogg, MillisecondsTimePoint.now()); |
|
| 306 | + video2.setCopyright("copyright"); |
|
| 307 | + event.addVideo(video2); |
|
| 308 | + |
|
| 309 | + mof.storeEvent(event); |
|
| 310 | + |
|
| 311 | + DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); |
|
| 312 | + final Event loadedEvent = dof.loadEvent(eventName); |
|
| 313 | + assertEquals(2, Util.size(loadedEvent.getVideos())); |
|
| 314 | + VideoDescriptor loadedVideo1 = loadedEvent.getVideos().iterator().next(); |
|
| 315 | + assertNotNull(loadedVideo1); |
|
| 316 | + assertEquals(videoURL, loadedVideo1.getURL()); |
|
| 317 | + assertEquals(videoThumbnailURL, loadedVideo1.getThumbnailURL()); |
|
| 318 | + assertEquals(videoLengthInSeconds, loadedVideo1.getLengthInSeconds()); |
|
| 319 | + assertEquals(copyright, loadedVideo1.getCopyright()); |
|
| 320 | + assertEquals(locale, loadedVideo1.getLocale()); |
|
| 321 | + assertEquals(videoTitle, loadedVideo1.getTitle()); |
|
| 322 | + assertEquals(videoSubtitle, loadedVideo1.getSubtitle()); |
|
| 323 | + assertEquals(createdAt, loadedVideo1.getCreatedAtDate()); |
|
| 324 | + assertEquals(3, Util.size(loadedVideo1.getTags())); |
|
| 325 | + } |
|
| 326 | + |
|
| 327 | + @SuppressWarnings("deprecation") |
|
| 328 | + @Test |
|
| 329 | + /** |
|
| 330 | + * We expected that the migration code creates also an image URL for each image we create. |
|
| 331 | + * Images with the 'Sponsor' tag should create a corresponding sponsor image URL |
|
| 332 | + * Videos should create a video URL. |
|
| 333 | + */ |
|
| 334 | + public void testLoadStoreSimpleEventWithImageAndVideoURLMigration() throws MalformedURLException { |
|
| 335 | + final URL imageURL = new URL("http://some.host/with/some/bla.jpg"); |
|
| 336 | + final URL sponsorImageURL = new URL("http://some.host/with/some/sponsor.jpg"); |
|
| 337 | + final URL videoURL = new URL("http://some.host/with/some/video.mpg"); |
|
| 338 | + final TimePoint createdAt = MillisecondsTimePoint.now(); |
|
| 339 | + final String eventName = "Event Name"; |
|
| 340 | + final Venue venue = new VenueImpl("My Venue"); |
|
| 341 | + CourseArea courseArea = DomainFactory.INSTANCE.getOrCreateCourseArea(UUID.randomUUID(), "Alfa"); |
|
| 342 | + venue.addCourseArea(courseArea); |
|
| 343 | + |
|
| 344 | + MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService()); |
|
| 345 | + Event event = new EventImpl(eventName, eventStartDate, eventEndDate, venue, /*isPublic*/ true, UUID.randomUUID()); |
|
| 346 | + |
|
| 347 | + ImageDescriptor image1 = new ImageDescriptorImpl(imageURL, createdAt); |
|
| 348 | + image1.addTag(MediaTagConstants.GALLERY); |
|
| 349 | + event.addImage(image1); |
|
| 350 | + |
|
| 351 | + ImageDescriptor image2 = new ImageDescriptorImpl(sponsorImageURL, createdAt); |
|
| 352 | + event.addImage(image2); |
|
| 353 | + image2.addTag(MediaTagConstants.SPONSOR); |
|
| 354 | + |
|
| 355 | + VideoDescriptor video1 = new VideoDescriptorImpl(videoURL, MimeType.mp4, createdAt); |
|
| 356 | + event.addVideo(video1); |
|
| 357 | + |
|
| 358 | + mof.storeEvent(event); |
|
| 359 | + |
|
| 360 | + DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); |
|
| 361 | + final Event loadedEvent = dof.loadEvent(eventName); |
|
| 362 | + assertEquals(2, Util.size(loadedEvent.getImages())); |
|
| 363 | + assertEquals(1, Util.size(loadedEvent.getVideos())); |
|
| 364 | + assertEquals(1, Util.size(loadedEvent.getImageURLs())); |
|
| 365 | + assertEquals(1, Util.size(loadedEvent.getSponsorImageURLs())); |
|
| 366 | + assertEquals(1, Util.size(loadedEvent.getVideoURLs())); |
|
| 367 | + } |
|
| 368 | + |
|
| 369 | + |
|
| 370 | + @Test |
|
| 371 | + public void testLoadStoreRegattaConfiguration() { |
|
| 372 | + |
|
| 373 | + RegattaConfigurationImpl configuration = new RegattaConfigurationImpl(); |
|
| 374 | + configuration.setDefaultRacingProcedureType(RacingProcedureType.BASIC); |
|
| 375 | + |
|
| 376 | + BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("ESS40", false); |
|
| 377 | + Regatta regatta = createRegattaAndAddRaceColumns(1, 1, RegattaImpl.getDefaultName("RR", boatClass.getName()), boatClass, |
|
| 378 | + regattaStartDate, regattaEndDate, false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.HIGH_POINT), OneDesignRankingMetric::new); |
|
| 379 | + regatta.setRegattaConfiguration(configuration); |
|
| 380 | + |
|
| 381 | + MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService()); |
|
| 382 | + mof.storeRegatta(regatta); |
|
| 383 | + DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); |
|
| 384 | + Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), null); |
|
| 385 | + |
|
| 386 | + assertNotNull(loadedRegatta.getRegattaConfiguration()); |
|
| 387 | + assertEquals(RacingProcedureType.BASIC, loadedRegatta.getRegattaConfiguration().getDefaultRacingProcedureType()); |
|
| 388 | + } |
|
| 389 | + |
|
| 390 | + @Test |
|
| 391 | + public void testLoadStoreSimpleRegattaLeaderboard() { |
|
| 392 | + RacingEventService res = new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE), PersistenceFactory.INSTANCE |
|
| 393 | + .getMongoObjectFactory(getMongoService()), MediaDBFactory.INSTANCE.getMediaDB(getMongoService()), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); |
|
| 394 | + final int numberOfQualifyingRaces = 5; |
|
| 395 | + final int numberOfFinalRaces = 7; |
|
| 396 | + final String regattaBaseName = "Kieler Woche"; |
|
| 397 | + BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true); |
|
| 398 | + Regatta regattaProxy = createRegatta(RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass, |
|
| 399 | + regattaStartDate, regattaEndDate, /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, OneDesignRankingMetric::new); |
|
| 400 | + final String regattaName = regattaProxy.getName(); |
|
| 401 | + Regatta regatta = res.createRegatta(regattaName, regattaProxy.getBoatClass().getName(), regattaStartDate, regattaEndDate, |
|
| 402 | + "123", regattaProxy.getSeries(), regattaProxy.isPersistent(), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new); |
|
| 403 | + addRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regatta); |
|
| 404 | + res.addRegattaLeaderboard(regatta.getRegattaIdentifier(), null, new int[] { 3, 5 }); |
|
| 405 | + DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); |
|
| 406 | + Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null); |
|
| 407 | + assertNotNull(loadedRegatta); |
|
| 408 | + assertEquals(regatta.getName(), loadedRegatta.getName()); |
|
| 409 | + assertEquals(Util.size(regatta.getSeries()), Util.size(loadedRegatta.getSeries())); |
|
| 410 | + |
|
| 411 | + Leaderboard loadedLeaderboard = dof.loadLeaderboard(regatta.getName(), res); |
|
| 412 | + assertNotNull(loadedLeaderboard); |
|
| 413 | + assertTrue(loadedLeaderboard instanceof RegattaLeaderboard); |
|
| 414 | + RegattaLeaderboard loadedRegattaLeaderboard = (RegattaLeaderboard) loadedLeaderboard; |
|
| 415 | + assertSame(regatta, loadedRegattaLeaderboard.getRegatta()); |
|
| 416 | + } |
|
| 417 | + |
|
| 418 | + @Test |
|
| 419 | + public void testLoadStoreRegattaLeaderboardWithScoreCorrections() { |
|
| 420 | + // for some reason the dropping of collections doesn't work reliably on Linux... explicitly drop those collections that we depend on |
|
| 421 | + getMongoService().getDB().getCollection(CollectionNames.LEADERBOARDS.name()).drop(); |
|
| 422 | + getMongoService().getDB().getCollection(CollectionNames.REGATTAS.name()).drop(); |
|
| 423 | + Competitor hasso = AbstractLeaderboardTest.createCompetitor("Dr. Hasso Plattner"); |
|
| 424 | + BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true); |
|
| 425 | + final DynamicTrackedRegatta[] trackedRegatta = new DynamicTrackedRegatta[1]; |
|
| 426 | + final DynamicTrackedRace q2YellowTrackedRace = new MockedTrackedRaceWithFixedRank(hasso, /* rank */ 1, /* started */ false, boatClass) { |
|
| 427 | + private static final long serialVersionUID = 1234L; |
|
| 428 | + @Override |
|
| 429 | + public RegattaAndRaceIdentifier getRaceIdentifier() { |
|
| 430 | + return new RegattaNameAndRaceName("Kieler Woche (29ERXX)", "Yellow Race 2"); |
|
| 431 | + } |
|
| 432 | + @Override |
|
| 433 | + public DynamicTrackedRegatta getTrackedRegatta() { |
|
| 434 | + return trackedRegatta[0]; |
|
| 435 | + } |
|
| 436 | + }; |
|
| 437 | + RacingEventService res = createRacingEventServiceWithOneMockedTrackedRace(q2YellowTrackedRace); |
|
| 438 | + final int numberOfQualifyingRaces = 5; |
|
| 439 | + final int numberOfFinalRaces = 7; |
|
| 440 | + final String regattaBaseName = "Kieler Woche"; |
|
| 441 | + |
|
| 442 | + Regatta regattaProxy = createRegatta(RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass, regattaStartDate, regattaEndDate, /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, OneDesignRankingMetric::new); |
|
| 443 | + Regatta regatta = res.createRegatta(regattaProxy.getName(), regattaProxy.getBoatClass().getName(), regattaProxy.getStartDate(), regattaProxy.getEndDate(), |
|
| 444 | + "123", regattaProxy.getSeries(), regattaProxy.isPersistent(), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, OneDesignRankingMetric::new); |
|
| 445 | + trackedRegatta[0] = new DynamicTrackedRegattaImpl(regatta); |
|
| 446 | + addRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regatta); |
|
| 447 | + logColumnsInRegatta(regatta); |
|
| 448 | + RegattaLeaderboard regattaLeaderboard = res.addRegattaLeaderboard(regatta.getRegattaIdentifier(), null, new int[] { 3, 5 }); |
|
| 449 | + assertSame(regatta, regattaLeaderboard.getRegatta()); |
|
| 450 | + final RaceColumnInSeries q2 = regatta.getSeriesByName("Qualifying").getRaceColumnByName("Q2"); |
|
| 451 | + final Fleet yellow = q2.getFleetByName("Yellow"); |
|
| 452 | + logColumnsInRegatta(regatta); |
|
| 453 | + logColumnsInRegattaLeaderboard(regattaLeaderboard); |
|
| 454 | + assertNotNull(regattaLeaderboard.getRaceColumnByName(q2.getName())); |
|
| 455 | + res.apply(new ConnectTrackedRaceToLeaderboardColumn(regattaLeaderboard.getName(), q2.getName(), yellow |
|
| 456 | + .getName(), q2YellowTrackedRace.getRaceIdentifier())); |
|
| 457 | + res.apply(new UpdateLeaderboardMaxPointsReason(regattaLeaderboard.getName(), q2.getName(), hasso.getId().toString(), |
|
| 458 | + MaxPointsReason.DNF, MillisecondsTimePoint.now())); |
|
| 459 | + |
|
| 460 | + // load new RacingEventService including regatta and leaderboard |
|
| 461 | + RacingEventService resForLoading = createRacingEventServiceWithOneMockedTrackedRace(q2YellowTrackedRace); |
|
| 462 | + Regatta loadedRegatta = resForLoading.getRegattaByName("Kieler Woche (29ERXX)"); |
|
| 463 | + assertNotNull(loadedRegatta); |
|
| 464 | + assertEquals(regatta.getName(), loadedRegatta.getName()); |
|
| 465 | + assertEquals(Util.size(regatta.getSeries()), Util.size(loadedRegatta.getSeries())); |
|
| 466 | + Leaderboard loadedLeaderboard = resForLoading.getLeaderboardByName(loadedRegatta.getName()); |
|
| 467 | + assertNotNull(loadedLeaderboard); |
|
| 468 | + assertEquals(((ThresholdBasedResultDiscardingRule) regattaLeaderboard.getResultDiscardingRule()).getDiscardIndexResultsStartingWithHowManyRaces().length, |
|
| 469 | + ((ThresholdBasedResultDiscardingRule) loadedLeaderboard.getResultDiscardingRule()).getDiscardIndexResultsStartingWithHowManyRaces().length); |
|
| 470 | + assertTrue(Arrays.equals(((ThresholdBasedResultDiscardingRule) regattaLeaderboard.getResultDiscardingRule()).getDiscardIndexResultsStartingWithHowManyRaces(), |
|
| 471 | + ((ThresholdBasedResultDiscardingRule) loadedLeaderboard.getResultDiscardingRule()).getDiscardIndexResultsStartingWithHowManyRaces())); |
|
| 472 | + assertTrue(loadedLeaderboard instanceof RegattaLeaderboard); |
|
| 473 | + RegattaLeaderboard loadedRegattaLeaderboard = (RegattaLeaderboard) loadedLeaderboard; |
|
| 474 | + assertSame(loadedRegatta, loadedRegattaLeaderboard.getRegatta()); |
|
| 475 | + // now re-associate the tracked race to let score correction "snap" to competitor: |
|
| 476 | + final RaceColumnInSeries loadedQ2 = loadedRegatta.getSeriesByName("Qualifying").getRaceColumnByName("Q2"); |
|
| 477 | + final Fleet loadedYellow = loadedQ2.getFleetByName("Yellow"); |
|
| 478 | + // adjust tracked regatta for tracked race: |
|
| 479 | + trackedRegatta[0] = new DynamicTrackedRegattaImpl(loadedRegatta); |
|
| 480 | + resForLoading.apply(new ConnectTrackedRaceToLeaderboardColumn(loadedLeaderboard.getName(), loadedQ2.getName(), loadedYellow |
|
| 481 | + .getName(), q2YellowTrackedRace.getRaceIdentifier())); |
|
| 482 | + MaxPointsReason hassosLoadedMaxPointsReason = loadedLeaderboard.getScoreCorrection().getMaxPointsReason(hasso, loadedQ2, MillisecondsTimePoint.now()); |
|
| 483 | + assertEquals(MaxPointsReason.DNF, hassosLoadedMaxPointsReason); |
|
| 484 | + } |
|
| 485 | + |
|
| 486 | + private void logColumnsInRegattaLeaderboard(RegattaLeaderboard regattaLeaderboard) { |
|
| 487 | + StringBuilder rlbrcNames = new StringBuilder(); |
|
| 488 | + for (RaceColumn rlbrc : regattaLeaderboard.getRaceColumns()) { |
|
| 489 | + rlbrcNames.append("; "); |
|
| 490 | + rlbrcNames.append(rlbrc.getName()); |
|
| 491 | + } |
|
| 492 | + logger.info("columns in regatta leaderboard for regatta "+regattaLeaderboard.getRegatta().getName()+" ("+ |
|
| 493 | + regattaLeaderboard.getRegatta().hashCode()+"): "+rlbrcNames); |
|
| 494 | + logColumnsInRegatta(regattaLeaderboard.getRegatta()); |
|
| 495 | + } |
|
| 496 | + |
|
| 497 | + private void logColumnsInRegatta(Regatta regatta) { |
|
| 498 | + StringBuilder rrcNames = new StringBuilder(); |
|
| 499 | + for (Series series : regatta.getSeries()) { |
|
| 500 | + for (RaceColumn raceColumn : series.getRaceColumns()) { |
|
| 501 | + rrcNames.append("; "); |
|
| 502 | + rrcNames.append(raceColumn.getName()); |
|
| 503 | + } |
|
| 504 | + } |
|
| 505 | + logger.info("columns in regatta "+regatta.getName()+" ("+regatta.hashCode()+") : "+rrcNames); |
|
| 506 | + } |
|
| 507 | + |
|
| 508 | + private RacingEventServiceImpl createRacingEventServiceWithOneMockedTrackedRace(final DynamicTrackedRace q2YellowTrackedRace) { |
|
| 509 | + return new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE), PersistenceFactory.INSTANCE |
|
| 510 | + .getMongoObjectFactory(getMongoService()), MediaDBFactory.INSTANCE.getMediaDB(getMongoService()), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE) { |
|
| 511 | + @Override |
|
| 512 | + public DynamicTrackedRace getExistingTrackedRace(RegattaAndRaceIdentifier raceIdentifier) { |
|
| 513 | + return q2YellowTrackedRace; |
|
| 514 | + } |
|
| 515 | + }; |
|
| 516 | + } |
|
| 517 | + |
|
| 518 | + @Test |
|
| 519 | + public void testLoadStoreSimpleRegatta() { |
|
| 520 | + final int numberOfQualifyingRaces = 5; |
|
| 521 | + final int numberOfFinalRaces = 7; |
|
| 522 | + final String regattaBaseName = "Kieler Woche"; |
|
| 523 | + BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */true); |
|
| 524 | + final String regattaName = RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()); |
|
| 525 | + Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regattaName, |
|
| 526 | + boatClass, regattaStartDate, regattaEndDate, /* persistent */false, |
|
| 527 | + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new); |
|
| 528 | + MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService()); |
|
| 529 | + mof.storeRegatta(regatta); |
|
| 530 | + |
|
| 531 | + DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); |
|
| 532 | + Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null); |
|
| 533 | + assertSame(LowPoint.class, loadedRegatta.getScoringScheme().getClass()); |
|
| 534 | + assertEquals(regattaName, loadedRegatta.getName()); |
|
| 535 | + Iterator<? extends Series> seriesIter = loadedRegatta.getSeries().iterator(); |
|
| 536 | + Series loadedQualifyingSeries = seriesIter.next(); |
|
| 537 | + assertEquals(numberOfQualifyingRaces, Util.size(loadedQualifyingSeries.getRaceColumns())); |
|
| 538 | + assertEquals(0, loadedQualifyingSeries.getFleetByName("Yellow").compareTo(loadedQualifyingSeries.getFleetByName("Blue"))); |
|
| 539 | + Series loadedFinalSeries = seriesIter.next(); |
|
| 540 | + assertEquals(numberOfFinalRaces, Util.size(loadedFinalSeries.getRaceColumns())); |
|
| 541 | + assertTrue(loadedFinalSeries.getFleetByName("Silver").compareTo(loadedFinalSeries.getFleetByName("Gold")) > 0); |
|
| 542 | + Series loadedMedalSeries = seriesIter.next(); |
|
| 543 | + assertEquals(1, Util.size(loadedMedalSeries.getRaceColumns())); |
|
| 544 | + assertEquals(loadedRegatta.getStartDate(), regattaStartDate); |
|
| 545 | + assertEquals(loadedRegatta.getEndDate(), regattaEndDate); |
|
| 546 | + assertEquals(RankingMetrics.ONE_DESIGN, loadedRegatta.getRankingMetricType()); |
|
| 547 | + } |
|
| 548 | + |
|
| 549 | + @Test |
|
| 550 | + public void testLoadStoreRegattaWithHandicapRanking() { |
|
| 551 | + final int numberOfQualifyingRaces = 5; |
|
| 552 | + final int numberOfFinalRaces = 7; |
|
| 553 | + final String regattaBaseName = "Kieler Woche"; |
|
| 554 | + BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */true); |
|
| 555 | + final String regattaName = RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()); |
|
| 556 | + Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regattaName, |
|
| 557 | + boatClass, regattaStartDate, regattaEndDate, /* persistent */false, |
|
| 558 | + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), TimeOnTimeAndDistanceRankingMetric::new); |
|
| 559 | + assertEquals(RankingMetrics.TIME_ON_TIME_AND_DISTANCE, regatta.getRankingMetricType()); |
|
| 560 | + MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService()); |
|
| 561 | + mof.storeRegatta(regatta); |
|
| 562 | + |
|
| 563 | + DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); |
|
| 564 | + Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null); |
|
| 565 | + assertSame(LowPoint.class, loadedRegatta.getScoringScheme().getClass()); |
|
| 566 | + assertEquals(regattaName, loadedRegatta.getName()); |
|
| 567 | + Iterator<? extends Series> seriesIter = loadedRegatta.getSeries().iterator(); |
|
| 568 | + Series loadedQualifyingSeries = seriesIter.next(); |
|
| 569 | + assertEquals(numberOfQualifyingRaces, Util.size(loadedQualifyingSeries.getRaceColumns())); |
|
| 570 | + assertEquals(0, loadedQualifyingSeries.getFleetByName("Yellow").compareTo(loadedQualifyingSeries.getFleetByName("Blue"))); |
|
| 571 | + Series loadedFinalSeries = seriesIter.next(); |
|
| 572 | + assertEquals(numberOfFinalRaces, Util.size(loadedFinalSeries.getRaceColumns())); |
|
| 573 | + assertTrue(loadedFinalSeries.getFleetByName("Silver").compareTo(loadedFinalSeries.getFleetByName("Gold")) > 0); |
|
| 574 | + Series loadedMedalSeries = seriesIter.next(); |
|
| 575 | + assertEquals(1, Util.size(loadedMedalSeries.getRaceColumns())); |
|
| 576 | + assertEquals(loadedRegatta.getStartDate(), regattaStartDate); |
|
| 577 | + assertEquals(loadedRegatta.getEndDate(), regattaEndDate); |
|
| 578 | + assertEquals(RankingMetrics.TIME_ON_TIME_AND_DISTANCE, loadedRegatta.getRankingMetricType()); |
|
| 579 | + } |
|
| 580 | + |
|
| 581 | + @Test |
|
| 582 | + public void testLoadStoreSimpleRegattaWithEmptyStartAndEndDate() { |
|
| 583 | + final int numberOfQualifyingRaces = 1; |
|
| 584 | + final int numberOfFinalRaces = 1; |
|
| 585 | + final String regattaBaseName = "Kieler Woche"; |
|
| 586 | + BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */true); |
|
| 587 | + final String regattaName = RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()); |
|
| 588 | + Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regattaName, |
|
| 589 | + boatClass, null, null, /* persistent */false, |
|
| 590 | + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new); |
|
| 591 | + MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService()); |
|
| 592 | + mof.storeRegatta(regatta); |
|
| 593 | + |
|
| 594 | + DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); |
|
| 595 | + Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null); |
|
| 596 | + assertEquals(regattaName, loadedRegatta.getName()); |
|
| 597 | + assertEquals(loadedRegatta.getStartDate(), null); |
|
| 598 | + assertEquals(loadedRegatta.getEndDate(), null); |
|
| 599 | + } |
|
| 600 | + |
|
| 601 | + @Test |
|
| 602 | + public void testLoadStoreSimpleRegattaWithSeriesScoringScheme() { |
|
| 603 | + final int numberOfQualifyingRaces = 5; |
|
| 604 | + final int numberOfFinalRaces = 7; |
|
| 605 | + final String regattaBaseName = "Kieler Woche"; |
|
| 606 | + BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true); |
|
| 607 | + Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), |
|
| 608 | + boatClass, regattaStartDate, regattaEndDate, /* persistent */false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new); |
|
| 609 | + regatta.getSeriesByName("Qualifying").setResultDiscardingRule(new ThresholdBasedResultDiscardingRuleImpl(new int[] { 1, 2, 3 })); |
|
| 610 | + MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService()); |
|
| 611 | + mof.storeRegatta(regatta); |
|
| 612 | + |
|
| 613 | + DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); |
|
| 614 | + Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null); |
|
| 615 | + assertTrue(Arrays.equals(new int[] { 1, 2, 3 }, |
|
| 616 | + loadedRegatta.getSeriesByName("Qualifying").getResultDiscardingRule().getDiscardIndexResultsStartingWithHowManyRaces())); |
|
| 617 | + } |
|
| 618 | + |
|
| 619 | + @Test |
|
| 620 | + public void testLoadStoreSimpleRegattaWithScoreForMedalStartingWithZero() { |
|
| 621 | + final int numberOfQualifyingRaces = 5; |
|
| 622 | + final int numberOfFinalRaces = 7; |
|
| 623 | + final String regattaBaseName = "Kieler Woche"; |
|
| 624 | + BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true); |
|
| 625 | + Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), |
|
| 626 | + boatClass, regattaStartDate, regattaEndDate, /* persistent */false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new); |
|
| 627 | + regatta.getSeriesByName("Medal").setStartsWithZeroScore(true); |
|
| 628 | + MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService()); |
|
| 629 | + mof.storeRegatta(regatta); |
|
| 630 | + |
|
| 631 | + DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); |
|
| 632 | + Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null); |
|
| 633 | + assertFalse(loadedRegatta.getSeriesByName("Qualifying").isStartsWithZeroScore()); |
|
| 634 | + assertTrue(loadedRegatta.getSeriesByName("Medal").isStartsWithZeroScore()); |
|
| 635 | + } |
|
| 636 | + |
|
| 637 | + @Test |
|
| 638 | + public void testLoadStoreSimpleRegattaWithHighPointScoringScheme() { |
|
| 639 | + final int numberOfQualifyingRaces = 5; |
|
| 640 | + final int numberOfFinalRaces = 7; |
|
| 641 | + final String regattaBaseName = "ESS40 Cardiff 2012"; |
|
| 642 | + BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("ESS40", /* typicallyStartsUpwind */ false); |
|
| 643 | + Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), |
|
| 644 | + boatClass, regattaStartDate, regattaEndDate, /* persistent */false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.HIGH_POINT), OneDesignRankingMetric::new); |
|
| 645 | + MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService()); |
|
| 646 | + mof.storeRegatta(regatta); |
|
| 647 | + |
|
| 648 | + DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); |
|
| 649 | + Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null); |
|
| 650 | + assertSame(HighPoint.class, loadedRegatta.getScoringScheme().getClass()); |
|
| 651 | + } |
|
| 652 | + |
|
| 653 | + @Test |
|
| 654 | + public void testLoadStoreRegattaWithFleetsEnsuringIdenticalFleetsInSeriesAndRaceColumns() { |
|
| 655 | + final int numberOfQualifyingRaces = 5; |
|
| 656 | + final int numberOfFinalRaces = 7; |
|
| 657 | + final String regattaBaseName = "Kieler Woche"; |
|
| 658 | + BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true); |
|
| 659 | + final String regattaName = RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()); |
|
| 660 | + Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, |
|
| 661 | + regattaName, boatClass, regattaStartDate, regattaEndDate, |
|
| 662 | + /* persistent */false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new); |
|
| 663 | + MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService()); |
|
| 664 | + mof.storeRegatta(regatta); |
|
| 665 | + |
|
| 666 | + DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); |
|
| 667 | + Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null); |
|
| 668 | + assertEquals(regattaName, loadedRegatta.getName()); |
|
| 669 | + Iterator<? extends Series> seriesIter = loadedRegatta.getSeries().iterator(); |
|
| 670 | + Series loadedQualifyingSeries = seriesIter.next(); |
|
| 671 | + int i=1; |
|
| 672 | + for (RaceColumn raceColumn : loadedQualifyingSeries.getRaceColumns()) { |
|
| 673 | + assertTrue(raceColumn instanceof RaceColumnInSeriesImpl); |
|
| 674 | + assertEquals("Q"+i, raceColumn.getName()); |
|
| 675 | + assertTrue(Util.equals(loadedQualifyingSeries.getFleets(), raceColumn.getFleets())); |
|
| 676 | + i++; |
|
| 677 | + } |
|
| 678 | + Series loadedFinalSeries = seriesIter.next(); |
|
| 679 | + i=1; |
|
| 680 | + for (RaceColumn raceColumn : loadedFinalSeries.getRaceColumns()) { |
|
| 681 | + assertTrue(raceColumn instanceof RaceColumnInSeriesImpl); |
|
| 682 | + assertEquals("F"+i, raceColumn.getName()); |
|
| 683 | + assertTrue(Util.equals(loadedFinalSeries.getFleets(), raceColumn.getFleets())); |
|
| 684 | + i++; |
|
| 685 | + } |
|
| 686 | + Series loadedMedalSeries = seriesIter.next(); |
|
| 687 | + for (RaceColumn raceColumn : loadedMedalSeries.getRaceColumns()) { |
|
| 688 | + assertTrue(raceColumn instanceof RaceColumnInSeriesImpl); |
|
| 689 | + assertEquals("M", raceColumn.getName()); |
|
| 690 | + assertTrue(Util.equals(loadedMedalSeries.getFleets(), raceColumn.getFleets())); |
|
| 691 | + } |
|
| 692 | + } |
|
| 693 | + |
|
| 694 | + @Test |
|
| 695 | + public void testLoadStoreRegattaWithFleetsEnsuringFleetOrdering() { |
|
| 696 | + final String regattaBaseName = "Kieler Woche"; |
|
| 697 | + BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true); |
|
| 698 | + final String regattaName = RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()); |
|
| 699 | + Regatta regatta = createRegatta(regattaName, boatClass, regattaStartDate, regattaEndDate, |
|
| 700 | + /* persistent */ false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, OneDesignRankingMetric::new); |
|
| 701 | + MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService()); |
|
| 702 | + mof.storeRegatta(regatta); |
|
| 703 | + |
|
| 704 | + DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); |
|
| 705 | + Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null); |
|
| 706 | + assertEquals(regattaName, loadedRegatta.getName()); |
|
| 707 | + |
|
| 708 | + Iterator<? extends Series> seriesIter = loadedRegatta.getSeries().iterator(); |
|
| 709 | + Series loadedQualifyingSeries = seriesIter.next(); |
|
| 710 | + |
|
| 711 | + Iterator<? extends Fleet> qualiFleetIt = loadedQualifyingSeries.getFleets().iterator(); |
|
| 712 | + Fleet qualiFleet1 = qualiFleetIt.next(); |
|
| 713 | + assertEquals(qualiFleet1.getName(), "Yellow"); |
|
| 714 | + Fleet qualiFleet2 = qualiFleetIt.next(); |
|
| 715 | + assertEquals(qualiFleet2.getName(), "Blue"); |
|
| 716 | + |
|
| 717 | + Series loadedFinalSeries = seriesIter.next(); |
|
| 718 | + Iterator<? extends Fleet> finalFleetIt = loadedFinalSeries.getFleets().iterator(); |
|
| 719 | + Fleet finalFleet1 = finalFleetIt.next(); |
|
| 720 | + assertEquals(finalFleet1.getName(), "Gold"); |
|
| 721 | + Fleet finalFleet2 = finalFleetIt.next(); |
|
| 722 | + assertEquals(finalFleet2.getName(), "Silver"); |
|
| 723 | + } |
|
| 724 | + |
|
| 725 | + @Test |
|
| 726 | + public void testStorageOfRaceIdentifiersOnRaceColumnInSeries() { |
|
| 727 | + final int numberOfQualifyingRaces = 5; |
|
| 728 | + final int numberOfFinalRaces = 7; |
|
| 729 | + final String regattaBaseName = "Kieler Woche"; |
|
| 730 | + BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29erXX", /* typicallyStartsUpwind */ true); |
|
| 731 | + Regatta regatta = createRegattaAndAddRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, |
|
| 732 | + RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass, regattaStartDate, regattaEndDate, |
|
| 733 | + /* persistent */false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), OneDesignRankingMetric::new); |
|
| 734 | + Series qualifyingSeries = regatta.getSeries().iterator().next(); |
|
| 735 | + RaceColumn q2 = qualifyingSeries.getRaceColumnByName("Q2"); |
|
| 736 | + final RegattaNameAndRaceName q2TrackedRaceIdentifier = new RegattaNameAndRaceName(regatta.getName(), "Q2 TracTrac"); |
|
| 737 | + q2.setRaceIdentifier(qualifyingSeries.getFleetByName("Yellow"), q2TrackedRaceIdentifier); |
|
| 738 | + MongoObjectFactory mof = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService()); |
|
| 739 | + mof.storeRegatta(regatta); |
|
| 740 | + |
|
| 741 | + DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); |
|
| 742 | + Regatta loadedRegatta = dof.loadRegatta(regatta.getName(), /* trackedRegattaRegistry */ null); |
|
| 743 | + Series loadedQualifyingSeries = loadedRegatta.getSeries().iterator().next(); |
|
| 744 | + RaceColumn loadedQ2 = loadedQualifyingSeries.getRaceColumnByName("Q2"); |
|
| 745 | + RaceIdentifier loadedQ2TrackedRaceIdentifier = loadedQ2.getRaceIdentifier(loadedQualifyingSeries.getFleetByName("Yellow")); |
|
| 746 | + assertEquals(q2TrackedRaceIdentifier, loadedQ2TrackedRaceIdentifier); |
|
| 747 | + assertNotSame(q2TrackedRaceIdentifier, loadedQ2TrackedRaceIdentifier); |
|
| 748 | + assertNull(loadedQualifyingSeries.getRaceColumnByName("Q1").getRaceIdentifier(loadedQualifyingSeries.getFleetByName("Yellow"))); |
|
| 749 | + assertNull(loadedQualifyingSeries.getRaceColumnByName("Q2").getRaceIdentifier(loadedQualifyingSeries.getFleetByName("Blue"))); |
|
| 750 | + } |
|
| 751 | + |
|
| 752 | + private Regatta createRegattaAndAddRaceColumns(final int numberOfQualifyingRaces, final int numberOfFinalRaces, |
|
| 753 | + final String regattaName, BoatClass boatClass, TimePoint startDate, TimePoint endDate, boolean persistent, |
|
| 754 | + ScoringScheme scoringScheme, RankingMetricConstructor rankingMetricConstructor) { |
|
| 755 | + Regatta regatta = createRegatta(regattaName, boatClass, startDate, endDate, persistent, scoringScheme, null, |
|
| 756 | + rankingMetricConstructor); |
|
| 757 | + addRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regatta); |
|
| 758 | + return regatta; |
|
| 759 | + } |
|
| 760 | + |
|
| 761 | + private void addRaceColumns(final int numberOfQualifyingRaces, final int numberOfFinalRaces, Regatta regatta) { |
|
| 762 | + List<String> finalRaceColumnNames = new ArrayList<String>(); |
|
| 763 | + for (int i=1; i<=numberOfFinalRaces; i++) { |
|
| 764 | + finalRaceColumnNames.add("F"+i); |
|
| 765 | + } |
|
| 766 | + List<String> qualifyingRaceColumnNames = new ArrayList<String>(); |
|
| 767 | + for (int i=1; i<=numberOfQualifyingRaces; i++) { |
|
| 768 | + qualifyingRaceColumnNames.add("Q"+i); |
|
| 769 | + } |
|
| 770 | + List<String> medalRaceColumnNames = new ArrayList<String>(); |
|
| 771 | + medalRaceColumnNames.add("M"); |
|
| 772 | + addRaceColumnsToSeries(qualifyingRaceColumnNames, regatta.getSeriesByName("Qualifying")); |
|
| 773 | + addRaceColumnsToSeries(finalRaceColumnNames, regatta.getSeriesByName("Final")); |
|
| 774 | + addRaceColumnsToSeries(medalRaceColumnNames, regatta.getSeriesByName("Medal")); |
|
| 775 | + } |
|
| 776 | + |
|
| 777 | + private Regatta createRegatta(final String regattaName, BoatClass boatClass, TimePoint startDate, |
|
| 778 | + TimePoint endDate, boolean persistent, ScoringScheme scoringScheme, CourseArea courseArea, |
|
| 779 | + RankingMetricConstructor rankingMetricConstructor) { |
|
| 780 | + List<String> emptyRaceColumnNames = Collections.emptyList(); |
|
| 781 | + List<Series> series = new ArrayList<Series>(); |
|
| 782 | + |
|
| 783 | + // -------- qualifying series ------------ |
|
| 784 | + List<Fleet> qualifyingFleets = new ArrayList<Fleet>(); |
|
| 785 | + qualifyingFleets.add(new FleetImpl("Yellow")); |
|
| 786 | + qualifyingFleets.add(new FleetImpl("Blue")); |
|
| 787 | + Series qualifyingSeries = new SeriesImpl("Qualifying", /* isMedal */false, qualifyingFleets, |
|
| 788 | + emptyRaceColumnNames, /* trackedRegattaRegistry */ null); |
|
| 789 | + series.add(qualifyingSeries); |
|
| 790 | + |
|
| 791 | + // -------- final series ------------ |
|
| 792 | + List<Fleet> finalFleets = new ArrayList<Fleet>(); |
|
| 793 | + finalFleets.add(new FleetImpl("Gold", 1)); |
|
| 794 | + finalFleets.add(new FleetImpl("Silver", 2)); |
|
| 795 | + Series finalSeries = new SeriesImpl("Final", /* isMedal */ false, finalFleets, emptyRaceColumnNames, /* trackedRegattaRegistry */ null); |
|
| 796 | + series.add(finalSeries); |
|
| 797 | + |
|
| 798 | + // ------------ medal -------------- |
|
| 799 | + List<Fleet> medalFleets = new ArrayList<Fleet>(); |
|
| 800 | + medalFleets.add(new FleetImpl("Medal")); |
|
| 801 | + Series medalSeries = new SeriesImpl("Medal", /* isMedal */ true, medalFleets, emptyRaceColumnNames, /* trackedRegattaRegistry */ null); |
|
| 802 | + series.add(medalSeries); |
|
| 803 | + Regatta regatta = new RegattaImpl(regattaName, boatClass, startDate, endDate, series, persistent, scoringScheme, "123", courseArea, rankingMetricConstructor); |
|
| 804 | + return regatta; |
|
| 805 | + } |
|
| 806 | + |
|
| 807 | + private void addRaceColumnsToSeries(List<String> finalRaceColumnNames, Series finalSeries) { |
|
| 808 | + for (String raceColumnName : finalRaceColumnNames) { |
|
| 809 | + finalSeries.addRaceColumn(raceColumnName, /* trackedRegattaRegistry */ null); |
|
| 810 | + } |
|
| 811 | + } |
|
| 812 | + |
|
| 813 | + @Test |
|
| 814 | + public void testRegattaRaceAssociationStore() throws Exception { |
|
| 815 | + BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("112er", /* typicallyStartsUpwind */ true); |
|
| 816 | + Regatta regatta = createRegatta(RegattaImpl.getDefaultName("Cologne Masters", boatClass.getName()), boatClass, |
|
| 817 | + regattaStartDate, regattaEndDate, /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, OneDesignRankingMetric::new); |
|
| 818 | + |
|
| 819 | + List<Competitor> competitors = new ArrayList<Competitor>(); |
|
| 820 | + competitors.add(new CompetitorImpl("Axel", "Axel Uhl", Color.RED, null, null, null, null, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null)); |
|
| 821 | + Iterable<Waypoint> waypoints = Collections.emptyList(); |
|
| 822 | + Course course = new CourseImpl("Course", waypoints); |
|
| 823 | + |
|
| 824 | + RaceDefinition racedef = new RaceDefinitionImpl("M1", course, boatClass, competitors); |
|
| 825 | + regatta.addRace(racedef); |
|
| 826 | + |
|
| 827 | + RacingEventServiceImpl evs = new RacingEventServiceImpl(PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE), PersistenceFactory.INSTANCE |
|
| 828 | + .getMongoObjectFactory(getMongoService()), MediaDBFactory.INSTANCE.getMediaDB(getMongoService()), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE); |
|
| 829 | + assertNull(evs.getRememberedRegattaForRace(racedef.getId())); |
|
| 830 | + evs.raceAdded(regatta, racedef); |
|
| 831 | + assertNotNull(evs.getRememberedRegattaForRace(racedef.getId())); |
|
| 832 | + evs.removeRegatta(regatta); |
|
| 833 | + assertNull(evs.getRememberedRegattaForRace(racedef.getId())); |
|
| 834 | + } |
|
| 835 | + |
|
| 836 | +} |
java/com.sap.sailing.polars.datamining/src/com/sap/sailing/polars/datamining/data/impl/GPSFixWithPolarContext.java
| ... | ... | @@ -38,7 +38,7 @@ public class GPSFixWithPolarContext implements HasGPSFixPolarContext { |
| 38 | 38 | |
| 39 | 39 | @Override |
| 40 | 40 | public ClusterDTO getWindSpeedRange() { |
| 41 | - return new ClusterDTO(windSpeedRangeGroup.getClusterFor(wind.getObject()).toString()); |
|
| 41 | + return new ClusterDTO(wind == null || wind.getObject() == null ? "null" : windSpeedRangeGroup.getClusterFor(wind.getObject()).toString()); |
|
| 42 | 42 | } |
| 43 | 43 | |
| 44 | 44 | @Override |
java/com.sap.sailing.server.gateway.serialization.shared.android/META-INF/MANIFEST.MF
| ... | ... | @@ -8,7 +8,8 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.7 |
| 8 | 8 | Require-Bundle: com.sap.sailing.domain.common, |
| 9 | 9 | com.sap.sailing.domain.shared.android, |
| 10 | 10 | org.json.simple;bundle-version="1.1.0", |
| 11 | - com.sap.sse.common |
|
| 11 | + com.sap.sse.common, |
|
| 12 | + com.sap.sse.shared.android |
|
| 12 | 13 | Export-Package: com.sap.sailing.server.gateway.deserialization, |
| 13 | 14 | com.sap.sailing.server.gateway.deserialization.coursedata.impl, |
| 14 | 15 | com.sap.sailing.server.gateway.deserialization.impl, |
java/com.sap.sailing.server.gateway.serialization.shared.android/pom.xml
| ... | ... | @@ -1,25 +1,25 @@ |
| 1 | -<?xml version="1.0" encoding="UTF-8"?>
|
|
| 2 | -<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
|
| 3 | - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
| 4 | - <modelVersion>4.0.0</modelVersion>
|
|
| 5 | - <parent>
|
|
| 6 | - <artifactId>root</artifactId>
|
|
| 7 | - <groupId>com.sap.sailing</groupId>
|
|
| 8 | - <version>1.0.0-SNAPSHOT</version>
|
|
| 9 | - </parent>
|
|
| 10 | - <artifactId>com.sap.sailing.server.gateway.serialization.shared.android</artifactId>
|
|
| 11 | - <packaging>eclipse-plugin</packaging>
|
|
| 12 | - <build>
|
|
| 13 | - <plugins>
|
|
| 14 | - <plugin>
|
|
| 15 | - <groupId>org.eclipse.tycho</groupId>
|
|
| 16 | - <artifactId>tycho-compiler-plugin</artifactId>
|
|
| 17 | - <version>${tycho-version}</version>
|
|
| 18 | - <configuration>
|
|
| 19 | - <source>1.7</source>
|
|
| 20 | - <target>1.7</target>
|
|
| 21 | - </configuration>
|
|
| 22 | - </plugin>
|
|
| 23 | - </plugins>
|
|
| 24 | - </build>
|
|
| 25 | -</project>
|
|
| 1 | +<?xml version="1.0" encoding="UTF-8"?> |
|
| 2 | +<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" |
|
| 3 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
|
| 4 | + <modelVersion>4.0.0</modelVersion> |
|
| 5 | + <parent> |
|
| 6 | + <artifactId>root</artifactId> |
|
| 7 | + <groupId>com.sap.sailing</groupId> |
|
| 8 | + <version>1.0.0-SNAPSHOT</version> |
|
| 9 | + </parent> |
|
| 10 | + <artifactId>com.sap.sailing.server.gateway.serialization.shared.android</artifactId> |
|
| 11 | + <packaging>eclipse-plugin</packaging> |
|
| 12 | + <build> |
|
| 13 | + <plugins> |
|
| 14 | + <plugin> |
|
| 15 | + <groupId>org.eclipse.tycho</groupId> |
|
| 16 | + <artifactId>tycho-compiler-plugin</artifactId> |
|
| 17 | + <version>${tycho-version}</version> |
|
| 18 | + <configuration> |
|
| 19 | + <source>1.7</source> |
|
| 20 | + <target>1.7</target> |
|
| 21 | + </configuration> |
|
| 22 | + </plugin> |
|
| 23 | + </plugins> |
|
| 24 | + </build> |
|
| 25 | +</project> |
java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/EventBaseJsonDeserializer.java
| ... | ... | @@ -23,12 +23,12 @@ import com.sap.sailing.server.gateway.serialization.impl.EventBaseJsonSerializer |
| 23 | 23 | import com.sap.sse.common.TimePoint; |
| 24 | 24 | import com.sap.sse.common.Util; |
| 25 | 25 | import com.sap.sse.common.impl.MillisecondsTimePoint; |
| 26 | -import com.sap.sse.common.media.ImageDescriptor; |
|
| 27 | -import com.sap.sse.common.media.ImageDescriptorImpl; |
|
| 28 | 26 | import com.sap.sse.common.media.ImageSize; |
| 29 | 27 | import com.sap.sse.common.media.MimeType; |
| 30 | -import com.sap.sse.common.media.VideoDescriptor; |
|
| 31 | -import com.sap.sse.common.media.VideoDescriptorImpl; |
|
| 28 | +import com.sap.sse.shared.media.ImageDescriptor; |
|
| 29 | +import com.sap.sse.shared.media.VideoDescriptor; |
|
| 30 | +import com.sap.sse.shared.media.impl.ImageDescriptorImpl; |
|
| 31 | +import com.sap.sse.shared.media.impl.VideoDescriptorImpl; |
|
| 32 | 32 | |
| 33 | 33 | public class EventBaseJsonDeserializer implements JsonDeserializer<EventBase> { |
| 34 | 34 | private final JsonDeserializer<Venue> venueDeserializer; |
java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/EventBaseJsonSerializer.java
| ... | ... | @@ -9,8 +9,8 @@ import com.sap.sailing.domain.base.EventBase; |
| 9 | 9 | import com.sap.sailing.domain.base.LeaderboardGroupBase; |
| 10 | 10 | import com.sap.sailing.domain.base.Venue; |
| 11 | 11 | import com.sap.sailing.server.gateway.serialization.JsonSerializer; |
| 12 | -import com.sap.sse.common.media.ImageDescriptor; |
|
| 13 | -import com.sap.sse.common.media.VideoDescriptor; |
|
| 12 | +import com.sap.sse.shared.media.ImageDescriptor; |
|
| 13 | +import com.sap.sse.shared.media.VideoDescriptor; |
|
| 14 | 14 | |
| 15 | 15 | public class EventBaseJsonSerializer implements JsonSerializer<EventBase> { |
| 16 | 16 | public static final String FIELD_ID = "id"; |
java/com.sap.sailing.server.gateway.serialization.test/META-INF/MANIFEST.MF
| ... | ... | @@ -14,4 +14,5 @@ Require-Bundle: com.sap.sailing.domain;bundle-version="1.0.0", |
| 14 | 14 | org.json.simple, |
| 15 | 15 | org.mockito.mockito-core;bundle-version="1.9.5", |
| 16 | 16 | org.objenesis;bundle-version="1.3.0", |
| 17 | - com.sap.sse.common |
|
| 17 | + com.sap.sse.common, |
|
| 18 | + com.sap.sse.shared.android |
java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventDataJsonSerializerTest.java
| ... | ... | @@ -32,8 +32,8 @@ import com.sap.sailing.server.gateway.serialization.impl.LeaderboardGroupBaseJso |
| 32 | 32 | import com.sap.sailing.server.gateway.serialization.impl.VenueJsonSerializer; |
| 33 | 33 | import com.sap.sse.common.TimePoint; |
| 34 | 34 | import com.sap.sse.common.impl.MillisecondsTimePoint; |
| 35 | -import com.sap.sse.common.media.ImageDescriptor; |
|
| 36 | -import com.sap.sse.common.media.VideoDescriptor; |
|
| 35 | +import com.sap.sse.shared.media.ImageDescriptor; |
|
| 36 | +import com.sap.sse.shared.media.VideoDescriptor; |
|
| 37 | 37 | |
| 38 | 38 | public class EventDataJsonSerializerTest { |
| 39 | 39 | protected final UUID expectedId = UUID.randomUUID(); |
java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventDataJsonSerializerWithImagesAndVideosTest.java
| ... | ... | @@ -36,11 +36,11 @@ import com.sap.sailing.server.gateway.serialization.impl.VenueJsonSerializer; |
| 36 | 36 | import com.sap.sse.common.TimePoint; |
| 37 | 37 | import com.sap.sse.common.Util; |
| 38 | 38 | import com.sap.sse.common.impl.MillisecondsTimePoint; |
| 39 | -import com.sap.sse.common.media.ImageDescriptor; |
|
| 40 | -import com.sap.sse.common.media.ImageDescriptorImpl; |
|
| 41 | 39 | import com.sap.sse.common.media.MimeType; |
| 42 | -import com.sap.sse.common.media.VideoDescriptor; |
|
| 43 | -import com.sap.sse.common.media.VideoDescriptorImpl; |
|
| 40 | +import com.sap.sse.shared.media.ImageDescriptor; |
|
| 41 | +import com.sap.sse.shared.media.VideoDescriptor; |
|
| 42 | +import com.sap.sse.shared.media.impl.ImageDescriptorImpl; |
|
| 43 | +import com.sap.sse.shared.media.impl.VideoDescriptorImpl; |
|
| 44 | 44 | |
| 45 | 45 | public class EventDataJsonSerializerWithImagesAndVideosTest { |
| 46 | 46 | private final UUID expectedId = UUID.randomUUID(); |
java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventDataJsonSerializerWithNullValuesTest.java
| ... | ... | @@ -32,8 +32,8 @@ import com.sap.sailing.server.gateway.serialization.impl.LeaderboardGroupBaseJso |
| 32 | 32 | import com.sap.sailing.server.gateway.serialization.impl.VenueJsonSerializer; |
| 33 | 33 | import com.sap.sse.common.TimePoint; |
| 34 | 34 | import com.sap.sse.common.impl.MillisecondsTimePoint; |
| 35 | -import com.sap.sse.common.media.ImageDescriptor; |
|
| 36 | -import com.sap.sse.common.media.VideoDescriptor; |
|
| 35 | +import com.sap.sse.shared.media.ImageDescriptor; |
|
| 36 | +import com.sap.sse.shared.media.VideoDescriptor; |
|
| 37 | 37 | |
| 38 | 38 | public class EventDataJsonSerializerWithNullValuesTest { |
| 39 | 39 | protected final UUID expectedId = UUID.randomUUID(); |
java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EventWithNullStartAndEndDataJsonSerializerTest.java
| ... | ... | @@ -31,8 +31,8 @@ import com.sap.sailing.server.gateway.serialization.impl.EventBaseJsonSerializer |
| 31 | 31 | import com.sap.sailing.server.gateway.serialization.impl.LeaderboardGroupBaseJsonSerializer; |
| 32 | 32 | import com.sap.sailing.server.gateway.serialization.impl.VenueJsonSerializer; |
| 33 | 33 | import com.sap.sse.common.TimePoint; |
| 34 | -import com.sap.sse.common.media.ImageDescriptor; |
|
| 35 | -import com.sap.sse.common.media.VideoDescriptor; |
|
| 34 | +import com.sap.sse.shared.media.ImageDescriptor; |
|
| 35 | +import com.sap.sse.shared.media.VideoDescriptor; |
|
| 36 | 36 | |
| 37 | 37 | public class EventWithNullStartAndEndDataJsonSerializerTest { |
| 38 | 38 |
java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackRaceBoatCompetitorMetadataReplicationTest.java
| ... | ... | @@ -70,7 +70,7 @@ public class TrackRaceBoatCompetitorMetadataReplicationTest extends AbstractServ |
| 70 | 70 | trackingParams = com.sap.sailing.domain.tractracadapter.DomainFactory.INSTANCE |
| 71 | 71 | .createTrackingConnectivityParameters(paramURL, liveURI, storedURI, courseDesignUpdateURI, |
| 72 | 72 | startOfTracking, endOfTracking, /* delayToLiveInMillis */ |
| 73 | - 0l, /* simulateWithStartTimeNow */false, /*ignoreTracTracMarkPassings*/ false, EmptyRaceLogStore.INSTANCE, |
|
| 73 | + 0l, /* offsetToStartTimeOfSimulatedRace */null, /*ignoreTracTracMarkPassings*/ false, EmptyRaceLogStore.INSTANCE, |
|
| 74 | 74 | EmptyRegattaLogStore.INSTANCE, tracTracUsername, tracTracPassword, "", ""); |
| 75 | 75 | } |
| 76 | 76 |
java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackRaceReplicationTest.java
| ... | ... | @@ -88,7 +88,7 @@ public class TrackRaceReplicationTest extends AbstractServerReplicationTest { |
| 88 | 88 | trackingParams = com.sap.sailing.domain.tractracadapter.DomainFactory.INSTANCE
|
| 89 | 89 | .createTrackingConnectivityParameters(paramURL, liveURI, storedURI, courseDesignUpdateURI,
|
| 90 | 90 | startOfTracking, endOfTracking, /* delayToLiveInMillis */
|
| 91 | - 0l, /* simulateWithStartTimeNow */false, /*ignoreTracTracMarkPassings*/ false, EmptyRaceLogStore.INSTANCE,
|
|
| 91 | + 0l, /* offsetToStartTimeOfSimulatedRace */null, /*ignoreTracTracMarkPassings*/ false, EmptyRaceLogStore.INSTANCE,
|
|
| 92 | 92 | EmptyRegattaLogStore.INSTANCE, tracTracUsername, tracTracPassword, "", "");
|
| 93 | 93 | }
|
| 94 | 94 |
java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java
| ... | ... | @@ -129,11 +129,11 @@ import com.sap.sse.common.TimePoint; |
| 129 | 129 | import com.sap.sse.common.Util; |
| 130 | 130 | import com.sap.sse.common.impl.MillisecondsDurationImpl; |
| 131 | 131 | import com.sap.sse.common.impl.MillisecondsTimePoint; |
| 132 | -import com.sap.sse.common.media.ImageDescriptor; |
|
| 133 | 132 | import com.sap.sse.common.media.MimeType; |
| 134 | -import com.sap.sse.common.media.VideoDescriptor; |
|
| 135 | 133 | import com.sap.sse.mongodb.MongoDBConfiguration; |
| 136 | 134 | import com.sap.sse.mongodb.MongoDBService; |
| 135 | +import com.sap.sse.shared.media.ImageDescriptor; |
|
| 136 | +import com.sap.sse.shared.media.VideoDescriptor; |
|
| 137 | 137 | |
| 138 | 138 | public class MasterDataImportTest { |
| 139 | 139 |
java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java
| ... | ... | @@ -72,10 +72,10 @@ import com.sap.sse.common.Color; |
| 72 | 72 | import com.sap.sse.common.TimePoint; |
| 73 | 73 | import com.sap.sse.common.Util; |
| 74 | 74 | import com.sap.sse.common.impl.MillisecondsTimePoint; |
| 75 | -import com.sap.sse.common.media.ImageDescriptor; |
|
| 76 | -import com.sap.sse.common.media.VideoDescriptor; |
|
| 77 | 75 | import com.sap.sse.common.search.KeywordQuery; |
| 78 | 76 | import com.sap.sse.common.search.Result; |
| 77 | +import com.sap.sse.shared.media.ImageDescriptor; |
|
| 78 | +import com.sap.sse.shared.media.VideoDescriptor; |
|
| 79 | 79 | |
| 80 | 80 | public class SearchServiceTest { |
| 81 | 81 | private RacingEventService server; |
java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java
| ... | ... | @@ -1,623 +1,623 @@ |
| 1 | -package com.sap.sailing.server;
|
|
| 2 | -
|
|
| 3 | -import java.io.IOException;
|
|
| 4 | -import java.io.Serializable;
|
|
| 5 | -import java.net.MalformedURLException;
|
|
| 6 | -import java.net.SocketException;
|
|
| 7 | -import java.net.URI;
|
|
| 8 | -import java.net.URL;
|
|
| 9 | -import java.util.Collection;
|
|
| 10 | -import java.util.ConcurrentModificationException;
|
|
| 11 | -import java.util.List;
|
|
| 12 | -import java.util.Map;
|
|
| 13 | -import java.util.UUID;
|
|
| 14 | -import java.util.concurrent.ConcurrentHashMap;
|
|
| 15 | -
|
|
| 16 | -import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
|
|
| 17 | -import com.sap.sailing.domain.abstractlog.race.RaceLog;
|
|
| 18 | -import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent;
|
|
| 19 | -import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
|
|
| 20 | -import com.sap.sailing.domain.base.Competitor;
|
|
| 21 | -import com.sap.sailing.domain.base.CompetitorStore;
|
|
| 22 | -import com.sap.sailing.domain.base.CourseArea;
|
|
| 23 | -import com.sap.sailing.domain.base.DomainFactory;
|
|
| 24 | -import com.sap.sailing.domain.base.Event;
|
|
| 25 | -import com.sap.sailing.domain.base.EventBase;
|
|
| 26 | -import com.sap.sailing.domain.base.Fleet;
|
|
| 27 | -import com.sap.sailing.domain.base.LeaderboardSearchResult;
|
|
| 28 | -import com.sap.sailing.domain.base.LeaderboardSearchResultBase;
|
|
| 29 | -import com.sap.sailing.domain.base.Mark;
|
|
| 30 | -import com.sap.sailing.domain.base.RaceColumn;
|
|
| 31 | -import com.sap.sailing.domain.base.RaceDefinition;
|
|
| 32 | -import com.sap.sailing.domain.base.Regatta;
|
|
| 33 | -import com.sap.sailing.domain.base.RegattaRegistry;
|
|
| 34 | -import com.sap.sailing.domain.base.RemoteSailingServerReference;
|
|
| 35 | -import com.sap.sailing.domain.base.SailingServerConfiguration;
|
|
| 36 | -import com.sap.sailing.domain.base.Series;
|
|
| 37 | -import com.sap.sailing.domain.base.configuration.DeviceConfiguration;
|
|
| 38 | -import com.sap.sailing.domain.base.configuration.DeviceConfigurationIdentifier;
|
|
| 39 | -import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher;
|
|
| 40 | -import com.sap.sailing.domain.base.configuration.RegattaConfiguration;
|
|
| 41 | -import com.sap.sailing.domain.common.DataImportProgress;
|
|
| 42 | -import com.sap.sailing.domain.common.Position;
|
|
| 43 | -import com.sap.sailing.domain.common.RaceFetcher;
|
|
| 44 | -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
|
| 45 | -import com.sap.sailing.domain.common.RegattaFetcher;
|
|
| 46 | -import com.sap.sailing.domain.common.RegattaIdentifier;
|
|
| 47 | -import com.sap.sailing.domain.common.RegattaName;
|
|
| 48 | -import com.sap.sailing.domain.common.ScoringSchemeType;
|
|
| 49 | -import com.sap.sailing.domain.common.media.MediaTrack;
|
|
| 50 | -import com.sap.sailing.domain.common.racelog.RacingProcedureType;
|
|
| 51 | -import com.sap.sailing.domain.leaderboard.EventResolver;
|
|
| 52 | -import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
|
|
| 53 | -import com.sap.sailing.domain.leaderboard.Leaderboard;
|
|
| 54 | -import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
|
|
| 55 | -import com.sap.sailing.domain.leaderboard.LeaderboardGroupResolver;
|
|
| 56 | -import com.sap.sailing.domain.leaderboard.LeaderboardRegistry;
|
|
| 57 | -import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
|
|
| 58 | -import com.sap.sailing.domain.leaderboard.ScoringScheme;
|
|
| 59 | -import com.sap.sailing.domain.persistence.DomainObjectFactory;
|
|
| 60 | -import com.sap.sailing.domain.persistence.MongoObjectFactory;
|
|
| 61 | -import com.sap.sailing.domain.polars.PolarDataService;
|
|
| 62 | -import com.sap.sailing.domain.racelog.tracking.GPSFixStore;
|
|
| 63 | -import com.sap.sailing.domain.ranking.RankingMetricConstructor;
|
|
| 64 | -import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
|
|
| 65 | -import com.sap.sailing.domain.tracking.DynamicTrackedRace;
|
|
| 66 | -import com.sap.sailing.domain.tracking.RaceListener;
|
|
| 67 | -import com.sap.sailing.domain.tracking.RaceTracker;
|
|
| 68 | -import com.sap.sailing.domain.tracking.TrackedRace;
|
|
| 69 | -import com.sap.sailing.domain.tracking.TrackedRegatta;
|
|
| 70 | -import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
|
|
| 71 | -import com.sap.sailing.domain.tracking.TrackerManager;
|
|
| 72 | -import com.sap.sailing.domain.tracking.WindStore;
|
|
| 73 | -import com.sap.sailing.server.masterdata.DataImportLockWithProgress;
|
|
| 74 | -import com.sap.sailing.server.simulation.SimulationService;
|
|
| 75 | -import com.sap.sse.common.TimePoint;
|
|
| 76 | -import com.sap.sse.common.TypeBasedServiceFinderFactory;
|
|
| 77 | -import com.sap.sse.common.Util;
|
|
| 78 | -import com.sap.sse.common.Util.Triple;
|
|
| 79 | -import com.sap.sse.common.media.ImageDescriptor;
|
|
| 80 | -import com.sap.sse.common.media.VideoDescriptor;
|
|
| 81 | -import com.sap.sse.common.search.KeywordQuery;
|
|
| 82 | -import com.sap.sse.common.search.Result;
|
|
| 83 | -import com.sap.sse.common.search.Searchable;
|
|
| 84 | -import com.sap.sse.filestorage.FileStorageManagementService;
|
|
| 85 | -import com.sap.sse.replication.impl.ReplicableWithObjectInputStream;
|
|
| 86 | -
|
|
| 87 | -/**
|
|
| 88 | - * An OSGi service that can be used to track boat races using a TracTrac connector that pushes live GPS boat location,
|
|
| 89 | - * waypoint, coarse and mark passing data.
|
|
| 90 | - * <p>
|
|
| 91 | - *
|
|
| 92 | - * If a race/regatta is already being tracked, another {@link #addTracTracRace(URL, URI, URI, WindStore, long)} or
|
|
| 93 | - * {@link #addRegatta(URL, URI, URI, WindStore, long)} call will have no effect, even if a different {@link WindStore}
|
|
| 94 | - * is requested.
|
|
| 95 | - * <p>
|
|
| 96 | - *
|
|
| 97 | - * When the tracking of a race/regatta is {@link #stopTracking(Regatta, RaceDefinition) stopped}, the next time it's
|
|
| 98 | - * started to be tracked, a new {@link TrackedRace} at least will be constructed. This also means that when a
|
|
| 99 | - * {@link TrackedRegatta} exists that still holds other {@link TrackedRace}s, the no longer tracked {@link TrackedRace}
|
|
| 100 | - * will be removed from the {@link TrackedRegatta}. corresponding information is removed also from the
|
|
| 101 | - * {@link DomainFactory}'s caches to ensure that clean, fresh data is received should another tracking request be issued
|
|
| 102 | - * later.
|
|
| 103 | - * <p>
|
|
| 104 | - *
|
|
| 105 | - * During receiving the initial load for a replication in {@link #initiallyFillFromInternal(java.io.ObjectInputStream)},
|
|
| 106 | - * tracked regattas read from the stream are observed (see {@link RaceListener}) by this object for automatic updates to
|
|
| 107 | - * the default leaderboard and for automatic linking to leaderboard columns. It is assumed that no explicit replication
|
|
| 108 | - * of these operations will happen based on the changes performed on the replication master.
|
|
| 109 | - *
|
|
| 110 | - * @author Axel Uhl (d043530)
|
|
| 111 | - *
|
|
| 112 | - */
|
|
| 113 | -public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetcher, RegattaRegistry, RaceFetcher,
|
|
| 114 | - LeaderboardRegistry, EventResolver, LeaderboardGroupResolver, TrackerManager, Searchable<LeaderboardSearchResult, KeywordQuery>,
|
|
| 115 | - ReplicableWithObjectInputStream<RacingEventService, RacingEventServiceOperation<?>>, RaceLogResolver {
|
|
| 116 | - @Override
|
|
| 117 | - Regatta getRegatta(RegattaName regattaName);
|
|
| 118 | -
|
|
| 119 | - @Override
|
|
| 120 | - RaceDefinition getRace(RegattaAndRaceIdentifier raceIdentifier);
|
|
| 121 | -
|
|
| 122 | - DynamicTrackedRace getTrackedRace(Regatta regatta, RaceDefinition race);
|
|
| 123 | -
|
|
| 124 | - DynamicTrackedRace getTrackedRace(RegattaAndRaceIdentifier raceIdentifier);
|
|
| 125 | -
|
|
| 126 | - /**
|
|
| 127 | - * Obtains an unmodifiable map of the leaderboard configured in this service keyed by their names.
|
|
| 128 | - */
|
|
| 129 | - Map<String, Leaderboard> getLeaderboards();
|
|
| 130 | -
|
|
| 131 | - /**
|
|
| 132 | - * @return a leaderboard whose {@link Leaderboard#getName()} method returns the value of the <code>name</code>
|
|
| 133 | - * parameter, or <code>null</code> if no such leaderboard is known to this service
|
|
| 134 | - */
|
|
| 135 | - Leaderboard getLeaderboardByName(String name);
|
|
| 136 | -
|
|
| 137 | - /**
|
|
| 138 | - * Looks at the mark tracks in the tracked races attached to the <code>leaderboard</code>. If it doesn't find a
|
|
| 139 | - * track for the <code>mark</code> requested there which has fixes before and after <code>timePoint</code> (to
|
|
| 140 | - * ensure that no track cropping has taken place, removing the fixes for the interesting time period), looks in the
|
|
| 141 | - * leaderboard's regatta log and the specific <code>raceLog</code> (if provided) or all race logs attached to the
|
|
| 142 | - * leaderboard (if not provided) for device mappings for the mark and tries to load fixes from the
|
|
| 143 | - * {@link GPSFixStore}. The latter is only necessary if the mark isn't found in any tracked race with fixes
|
|
| 144 | - * surrounding <code>timePoint</code> because should there be a tracked race in the leaderboard that has the mark
|
|
| 145 | - * then it will also have received the fixes from the {@link GPSFixStore} through the regatta log mapping.
|
|
| 146 | - * <p>
|
|
| 147 | - *
|
|
| 148 | - * @return the position obtained by interpolation but never extrapolation from the track identified as described
|
|
| 149 | - * above
|
|
| 150 | - */
|
|
| 151 | - Position getMarkPosition(Mark mark, LeaderboardThatHasRegattaLike leaderboard, TimePoint timePoint, RaceLog raceLog);
|
|
| 152 | -
|
|
| 153 | - /**
|
|
| 154 | - * Stops tracking all races of the regatta specified. This will also stop tracking wind for all races of this regatta.
|
|
| 155 | - * See {@link #stopTrackingWind(Regatta, RaceDefinition)}. If there were multiple calls to
|
|
| 156 | - * {@link #addTracTracRace(URL, URI, URI, WindStore, long)} with an equal combination of URLs/URIs, the {@link TracTracRaceTracker}
|
|
| 157 | - * already tracking the race was re-used. The trackers will be stopped by this call regardless of how many calls
|
|
| 158 | - * were made that ensured they were tracking.
|
|
| 159 | - */
|
|
| 160 | - void stopTracking(Regatta regatta) throws MalformedURLException, IOException, InterruptedException;
|
|
| 161 | -
|
|
| 162 | - /**
|
|
| 163 | - * Removes <code>race</code> and any corresponding {@link #getTrackedRace(Regatta, RaceDefinition) tracked race}
|
|
| 164 | - * from this service. If it was the last {@link RaceDefinition} in its {@link Regatta} and the regatta
|
|
| 165 | - * {@link Regatta#isPersistent() is not stored persistently}, the <code>regatta</code> is removed as well and will no
|
|
| 166 | - * longer be returned by {@link #getAllRegattas()}. The wind tracking is stopped for <code>race</code>.
|
|
| 167 | - * <p>
|
|
| 168 | - *
|
|
| 169 | - * Any {@link RaceTracker} for which <code>race</race> is the last race tracked that is still reachable
|
|
| 170 | - * from {@link #getAllRegattas()} will be {@link RaceTracker#stop(boolean) stopped}.
|
|
| 171 | - *
|
|
| 172 | - * The <code>race</code> will be also removed from all leaderboards containing a column that has <code>race</code>'s
|
|
| 173 | - * {@link #getTrackedRace(Regatta, RaceDefinition) corresponding} {@link TrackedRace} as its
|
|
| 174 | - * {@link RaceColumn#getTrackedRace(Fleet)}.
|
|
| 175 | - *
|
|
| 176 | - * @param regatta
|
|
| 177 | - * the regatta from which to remove the race
|
|
| 178 | - * @param race
|
|
| 179 | - * the race to remove
|
|
| 180 | - */
|
|
| 181 | - void removeRace(Regatta regatta, RaceDefinition race) throws MalformedURLException, IOException,InterruptedException;
|
|
| 182 | -
|
|
| 183 | - /**
|
|
| 184 | - * @param port
|
|
| 185 | - * the UDP port on which to listen for incoming messages from Expedition clients
|
|
| 186 | - * @param correctByDeclination
|
|
| 187 | - * An optional service to convert the wind bearings (which the receiver may
|
|
| 188 | - * believe to be true bearings) from magnetic to true bearings.
|
|
| 189 | - * @throws SocketException
|
|
| 190 | - * thrown, e.g., in case there is already another listener on the port requested
|
|
| 191 | - */
|
|
| 192 | - void startTrackingWind(Regatta regatta, RaceDefinition race, boolean correctByDeclination) throws Exception;
|
|
| 193 | -
|
|
| 194 | - void stopTrackingWind(Regatta regatta, RaceDefinition race) throws SocketException, IOException;
|
|
| 195 | -
|
|
| 196 | - /**
|
|
| 197 | - * The {@link Triple#getC() third component} of the triples returned is a wind tracker-specific
|
|
| 198 | - * comment where a wind tracker may provide information such as its type name or, if applicable,
|
|
| 199 | - * connectivity information such as the network port on which it receives wind information.
|
|
| 200 | - */
|
|
| 201 | - Iterable<Util.Triple<Regatta, RaceDefinition, String>> getWindTrackedRaces();
|
|
| 202 | -
|
|
| 203 | - /**
|
|
| 204 | - * Creates a new leaderboard with the <code>name</code> specified.
|
|
| 205 | - * @param discardThresholds
|
|
| 206 | - * Tells the thresholds from which on a next higher number of worst races will be discarded per
|
|
| 207 | - * competitor. Example: <code>[3, 6]</code> means that starting from three races the single worst race
|
|
| 208 | - * will be discarded; starting from six races, the two worst races per competitor are discarded.
|
|
| 209 | - *
|
|
| 210 | - * @return the leaderboard created
|
|
| 211 | - */
|
|
| 212 | - FlexibleLeaderboard addFlexibleLeaderboard(String leaderboardName, String leaderboardDisplayName, int[] discardThresholds, ScoringScheme scoringScheme, Serializable courseAreaId);
|
|
| 213 | -
|
|
| 214 | - RegattaLeaderboard addRegattaLeaderboard(RegattaIdentifier regattaIdentifier, String leaderboardDisplayName, int[] discardThresholds);
|
|
| 215 | -
|
|
| 216 | - void removeLeaderboard(String leaderboardName);
|
|
| 217 | -
|
|
| 218 | - /**
|
|
| 219 | - * Renames a leaderboard. If a leaderboard by the name <code>oldName</code> does not exist in {@link #getLeaderboards()},
|
|
| 220 | - * or if a leaderboard with the name <code>newName</code> already exists, an {@link IllegalArgumentException} is thrown.
|
|
| 221 | - * If the method completes normally, the rename has been successful, and the leaderboard previously obtained by calling
|
|
| 222 | - * {@link #getLeaderboardByName(String) getLeaderboardByName(oldName)} can now be obtained by calling
|
|
| 223 | - * {@link #getLeaderboardByName(String) getLeaderboardByName(newName)}.
|
|
| 224 | - */
|
|
| 225 | - void renameLeaderboard(String oldName, String newName);
|
|
| 226 | -
|
|
| 227 | - RaceColumn addColumnToLeaderboard(String columnName, String leaderboardName, boolean medalRace);
|
|
| 228 | -
|
|
| 229 | - void moveLeaderboardColumnUp(String leaderboardName, String columnName);
|
|
| 230 | -
|
|
| 231 | - void moveLeaderboardColumnDown(String leaderboardName, String columnName);
|
|
| 232 | -
|
|
| 233 | - void removeLeaderboardColumn(String leaderboardName, String columnName);
|
|
| 234 | -
|
|
| 235 | - void renameLeaderboardColumn(String leaderboardName, String oldColumnName, String newColumnName);
|
|
| 236 | -
|
|
| 237 | - /**
|
|
| 238 | - * @see RaceColumn#setFactor(Double)
|
|
| 239 | - */
|
|
| 240 | - void updateLeaderboardColumnFactor(String leaderboardName, String columnName, Double factor);
|
|
| 241 | -
|
|
| 242 | - /**
|
|
| 243 | - * Updates the leaderboard data in the persistent store
|
|
| 244 | - */
|
|
| 245 | - void updateStoredLeaderboard(Leaderboard leaderboard);
|
|
| 246 | -
|
|
| 247 | - void updateStoredRegatta(Regatta regatta);
|
|
| 248 | -
|
|
| 249 | - void stopTrackingAndRemove(Regatta regatta) throws MalformedURLException, IOException, InterruptedException;
|
|
| 250 | -
|
|
| 251 | - /**
|
|
| 252 | - * Removes the regatta as well as all regatta leaderboards for that regatta
|
|
| 253 | - */
|
|
| 254 | - void removeRegatta(Regatta regatta) throws MalformedURLException, IOException, InterruptedException;
|
|
| 255 | -
|
|
| 256 | - /**
|
|
| 257 | - * Removes the given series
|
|
| 258 | - */
|
|
| 259 | - void removeSeries(Series series) throws MalformedURLException, IOException, InterruptedException;
|
|
| 260 | -
|
|
| 261 | - DynamicTrackedRace getExistingTrackedRace(RegattaAndRaceIdentifier raceIdentifier);
|
|
| 262 | -
|
|
| 263 | - /**
|
|
| 264 | - * Obtains an unmodifiable map of the leaderboard groups configured in this service keyed by their names.
|
|
| 265 | - */
|
|
| 266 | - Map<String, LeaderboardGroup> getLeaderboardGroups();
|
|
| 267 | -
|
|
| 268 | - /**
|
|
| 269 | - * Creates a new group with the name <code>groupName</code>, the description <code>desciption</code> and the
|
|
| 270 | - * leaderboards with the names in <code>leaderboardNames</code> and saves it in the database.
|
|
| 271 | - * @param id TODO
|
|
| 272 | - * @param groupName
|
|
| 273 | - * The name of the new group
|
|
| 274 | - * @param description
|
|
| 275 | - * The description of the new group
|
|
| 276 | - * @param displayName TODO
|
|
| 277 | - * @param displayGroupsInReverseOrder TODO
|
|
| 278 | - * @param leaderboardNames
|
|
| 279 | - * The names of the leaderboards, which should be contained by the new group.<br />
|
|
| 280 | - * If there isn't a leaderboard with one of these names an {@link IllegalArgumentException} is thrown.
|
|
| 281 | - * @return The new leaderboard group
|
|
| 282 | - */
|
|
| 283 | - LeaderboardGroup addLeaderboardGroup(UUID id, String groupName, String description,
|
|
| 284 | - String displayName, boolean displayGroupsInReverseOrder, List<String> leaderboardNames, int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType);
|
|
| 285 | -
|
|
| 286 | - /**
|
|
| 287 | - * Removes the group with the name <code>groupName</code> from the service and the database.
|
|
| 288 | - * @param groupName The name of the group which shall be removed.
|
|
| 289 | - */
|
|
| 290 | - void removeLeaderboardGroup(String groupName);
|
|
| 291 | -
|
|
| 292 | - /**
|
|
| 293 | - * Renames the group with the name <code>oldName</code> to the <code>newName</code>.<br />
|
|
| 294 | - * If there's no group with the name <code>oldName</code> or there's already a group with the name
|
|
| 295 | - * <code>newName</code> a {@link IllegalArgumentException} is thrown.
|
|
| 296 | - *
|
|
| 297 | - * @param oldName The old name of the group
|
|
| 298 | - * @param newName The new name of the group
|
|
| 299 | - */
|
|
| 300 | - void renameLeaderboardGroup(String oldName, String newName);
|
|
| 301 | -
|
|
| 302 | - /**
|
|
| 303 | - * Updates the group data in the persistant store.
|
|
| 304 | - */
|
|
| 305 | - void updateStoredLeaderboardGroup(LeaderboardGroup leaderboardGroup);
|
|
| 306 | -
|
|
| 307 | - DynamicTrackedRace createTrackedRace(RegattaAndRaceIdentifier raceIdentifier, WindStore windStore, GPSFixStore gpsFixStore,
|
|
| 308 | - long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, boolean useMarkPassingCalculator);
|
|
| 309 | -
|
|
| 310 | - Regatta getOrCreateDefaultRegatta(String name, String boatClassName, Serializable id);
|
|
| 311 | -
|
|
| 312 | - /**
|
|
| 313 | - * @param series the series must not have any {@link RaceColumn}s yet
|
|
| 314 | - */
|
|
| 315 | - Regatta createRegatta(String regattaName, String boatClassName, TimePoint startDate, TimePoint endDate, Serializable id, Iterable<? extends Series> series,
|
|
| 316 | - boolean persistent, ScoringScheme scoringScheme, Serializable defaultCourseAreaId,
|
|
| 317 | - boolean useStartTimeInference, RankingMetricConstructor rankingMetricConstructor);
|
|
| 318 | -
|
|
| 319 | - Regatta updateRegatta(RegattaIdentifier regattaIdentifier, TimePoint startDate, TimePoint endDate, Serializable newDefaultCourseAreaId, RegattaConfiguration regattaConfiguration, Iterable<? extends Series> series, boolean useStartTimeInference);
|
|
| 320 | -
|
|
| 321 | - /**
|
|
| 322 | - * Adds <code>raceDefinition</code> to the {@link Regatta} such that it will appear in {@link Regatta#getAllRaces()}
|
|
| 323 | - * and {@link Regatta#getRaceByName(String)}.
|
|
| 324 | - *
|
|
| 325 | - * @param addToRegatta identifier of an regatta that must exist already
|
|
| 326 | - */
|
|
| 327 | - void addRace(RegattaIdentifier addToRegatta, RaceDefinition raceDefinition);
|
|
| 328 | -
|
|
| 329 | - void updateLeaderboardGroup(String oldName, String newName, String description, String displayName,
|
|
| 330 | - List<String> leaderboardNames, int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType);
|
|
| 331 | -
|
|
| 332 | - /**
|
|
| 333 | - * @return a thread-safe copy of the events currently known by the service; it's safe for callers to iterate over
|
|
| 334 | - * the iterable returned, and no risk of a {@link ConcurrentModificationException} exists
|
|
| 335 | - */
|
|
| 336 | - Iterable<Event> getAllEvents();
|
|
| 337 | -
|
|
| 338 | - /**
|
|
| 339 | - * Creates a new event with the name <code>eventName</code>, the venue <code>venue</code> and the regattas with the
|
|
| 340 | - * names in <code>regattaNames</code>, saves it in the database and replicates it. Use for TESTING only!
|
|
| 341 | - *
|
|
| 342 | - * @param eventName
|
|
| 343 | - * The name of the new event
|
|
| 344 | - * @param eventDescription TODO
|
|
| 345 | - * @param startDate
|
|
| 346 | - * The start date of the event
|
|
| 347 | - * @param endDate
|
|
| 348 | - * The end date of the event
|
|
| 349 | - * @param isPublic
|
|
| 350 | - * Indicates whether the event is public accessible via the publication URL or not
|
|
| 351 | - * @param id
|
|
| 352 | - * The id of the new event
|
|
| 353 | - * @param venue
|
|
| 354 | - * The name of the venue of the new event
|
|
| 355 | - * @return The new event
|
|
| 356 | - */
|
|
| 357 | - Event addEvent(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, UUID id);
|
|
| 358 | -
|
|
| 359 | - /**
|
|
| 360 | - * Updates a sailing event with the name <code>eventName</code>, the venue<code>venue</code> and the regattas with
|
|
| 361 | - * the names in <code>regattaNames</code> and updates it in the database.
|
|
| 362 | - * @param eventName
|
|
| 363 | - * The name of the event to update
|
|
| 364 | - * @param startDate
|
|
| 365 | - * The start date of the event
|
|
| 366 | - * @param endDate
|
|
| 367 | - * The end date of the event
|
|
| 368 | - * @param venueName
|
|
| 369 | - * The name of the venue of the event
|
|
| 370 | - * @param isPublic
|
|
| 371 | - * Indicates whether the event is public accessible via the publication URL or not
|
|
| 372 | - * @return The new event
|
|
| 373 | - */
|
|
| 374 | - void updateEvent(UUID id, String eventName, String eventDescription, TimePoint startDate, TimePoint endDate,
|
|
| 375 | - String venueName, boolean isPublic, Iterable<UUID> leaderboardGroupIds, URL officialWebsiteURL, URL sailorsInfoWebsiteURL,
|
|
| 376 | - Iterable<ImageDescriptor> images, Iterable<VideoDescriptor> videos);
|
|
| 377 | -
|
|
| 378 | - /**
|
|
| 379 | - * Renames a sailing event. If a sailing event by the name <code>oldName</code> does not exist in {@link #getEvents()},
|
|
| 380 | - * or if a event with the name <code>newName</code> already exists, an {@link IllegalArgumentException} is thrown.
|
|
| 381 | - * If the method completes normally, the rename has been successful, and the event previously obtained by calling
|
|
| 382 | - * {@link #getEventByName(String) getEventByName(oldName)} can now be obtained by calling
|
|
| 383 | - * {@link #getEventByName(String) getEventByName(newName)}.
|
|
| 384 | - */
|
|
| 385 | - void renameEvent(UUID id, String newEventName);
|
|
| 386 | -
|
|
| 387 | - void removeEvent(UUID id);
|
|
| 388 | -
|
|
| 389 | -
|
|
| 390 | - /**
|
|
| 391 | - * @return a thread-safe copy of the events (or the exception that occurred trying to obtain the events; arranged in
|
|
| 392 | - * a {@link Util.Pair}) of from all sailing server instances currently known by the service; it's safe for
|
|
| 393 | - * callers to iterate over the iterable returned, and no risk of a {@link ConcurrentModificationException}
|
|
| 394 | - * exists
|
|
| 395 | - */
|
|
| 396 | - Map<RemoteSailingServerReference, Util.Pair<Iterable<EventBase>, Exception>> getPublicEventsOfAllSailingServers();
|
|
| 397 | -
|
|
| 398 | - RemoteSailingServerReference addRemoteSailingServerReference(String name, URL url);
|
|
| 399 | -
|
|
| 400 | - void removeRemoteSailingServerReference(String name);
|
|
| 401 | -
|
|
| 402 | -
|
|
| 403 | - CourseArea[] addCourseAreas(UUID eventId, String[] courseAreaNames, UUID[] courseAreaIds);
|
|
| 404 | -
|
|
| 405 | - com.sap.sailing.domain.base.DomainFactory getBaseDomainFactory();
|
|
| 406 | -
|
|
| 407 | - CourseArea getCourseArea(Serializable courseAreaId);
|
|
| 408 | -
|
|
| 409 | - /**
|
|
| 410 | - * Adds the specified mediaTrack to the in-memory media library.
|
|
| 411 | - * Important note: Only if mediaTrack.dbId != null the mediaTrack will be persisted in the the database.
|
|
| 412 | - * @param mediaTrack
|
|
| 413 | - */
|
|
| 414 | - void mediaTrackAdded(MediaTrack mediaTrack);
|
|
| 415 | -
|
|
| 416 | - /**
|
|
| 417 | - * Calling mediaTrackAdded for every entry in the specified collection.
|
|
| 418 | - * @param mediaTracks
|
|
| 419 | - */
|
|
| 420 | - void mediaTracksAdded(Collection<MediaTrack> mediaTracks);
|
|
| 421 | -
|
|
| 422 | - void mediaTrackTitleChanged(MediaTrack mediaTrack);
|
|
| 423 | -
|
|
| 424 | - void mediaTrackUrlChanged(MediaTrack mediaTrack);
|
|
| 425 | -
|
|
| 426 | - void mediaTrackStartTimeChanged(MediaTrack mediaTrack);
|
|
| 427 | -
|
|
| 428 | - void mediaTrackDurationChanged(MediaTrack mediaTrack);
|
|
| 429 | -
|
|
| 430 | - void mediaTrackAssignedRacesChanged(MediaTrack mediaTrack);
|
|
| 431 | -
|
|
| 432 | - void mediaTrackDeleted(MediaTrack mediaTrack);
|
|
| 433 | -
|
|
| 434 | - /**
|
|
| 435 | - * In contrast to mediaTracksAdded, this method takes mediaTracks with a given dbId.
|
|
| 436 | - * Checks if the track already exists in the library and the database and adds/stores it
|
|
| 437 | - * accordingly. If a track already exists and override, its properties are checked for changes
|
|
| 438 | - * @param mediaTrack
|
|
| 439 | - * @param override If true, track properties (title, url, start time, duration, not mime type!) will be
|
|
| 440 | - * overwritten with the values from the track to be imported.
|
|
| 441 | - */
|
|
| 442 | - void mediaTracksImported(Collection<MediaTrack> mediaTracksToImport, boolean override);
|
|
| 443 | -
|
|
| 444 | - Collection<MediaTrack> getMediaTracksForRace(RegattaAndRaceIdentifier regattaAndRaceIdentifier);
|
|
| 445 | -
|
|
| 446 | - Collection<MediaTrack> getMediaTracksInTimeRange(RegattaAndRaceIdentifier regattaAndRaceIdentifier);
|
|
| 447 | -
|
|
| 448 | - Collection<MediaTrack> getAllMediaTracks();
|
|
| 449 | -
|
|
| 450 | - void reloadRaceLog(String leaderboardName, String raceColumnName, String fleetName);
|
|
| 451 | -
|
|
| 452 | - RaceLog getRaceLog(String leaderboardName, String raceColumnName, String fleetName);
|
|
| 453 | -
|
|
| 454 | - /**
|
|
| 455 | - * @param rankingMetricConstructor TODO
|
|
| 456 | - * @return a pair with the found or created regatta, and a boolean that tells whether the regatta was created during
|
|
| 457 | - * the call
|
|
| 458 | - */
|
|
| 459 | - Util.Pair<Regatta, Boolean> getOrCreateRegattaWithoutReplication(String fullRegattaName, String boatClassName,
|
|
| 460 | - TimePoint startDate, TimePoint endDate, Serializable id,
|
|
| 461 | - Iterable<? extends Series> series, boolean persistent, ScoringScheme scoringScheme,
|
|
| 462 | - Serializable defaultCourseAreaId, boolean useStartTimeInference, RankingMetricConstructor rankingMetricConstructor);
|
|
| 463 | -
|
|
| 464 | - /**
|
|
| 465 | - * @return map where keys are the toString() representation of the {@link RaceDefinition#getId() IDs} of races passed to
|
|
| 466 | - * {@link #setRegattaForRace(Regatta, RaceDefinition)}. It helps remember the connection between races and regattas.
|
|
| 467 | - */
|
|
| 468 | - ConcurrentHashMap<String, Regatta> getPersistentRegattasForRaceIDs();
|
|
| 469 | -
|
|
| 470 | - Event createEventWithoutReplication(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venue,
|
|
| 471 | - boolean isPublic, UUID id, URL officialWebsiteURL, URL sailorsInfoWebsiteURL, Iterable<ImageDescriptor> images,
|
|
| 472 | - Iterable<VideoDescriptor> videos);
|
|
| 473 | -
|
|
| 474 | - void setRegattaForRace(Regatta regatta, String raceIdAsString);
|
|
| 475 | -
|
|
| 476 | - CourseArea[] addCourseAreasWithoutReplication(UUID eventId, UUID[] courseAreaIds, String[] courseAreaNames);
|
|
| 477 | -
|
|
| 478 | - CourseArea[] removeCourseAreaWithoutReplication(UUID eventId, UUID[] courseAreaIds);
|
|
| 479 | -
|
|
| 480 | - /**
|
|
| 481 | - * Returns a mobile device's configuration.
|
|
| 482 | - * @param identifier of the client (may include event)
|
|
| 483 | - * @return the {@link DeviceConfiguration}
|
|
| 484 | - */
|
|
| 485 | - DeviceConfiguration getDeviceConfiguration(DeviceConfigurationIdentifier identifier);
|
|
| 486 | -
|
|
| 487 | - /**
|
|
| 488 | - * Adds a device configuration.
|
|
| 489 | - * @param matcher defining for which the configuration applies.
|
|
| 490 | - * @param configuration of the device.
|
|
| 491 | - */
|
|
| 492 | - void createOrUpdateDeviceConfiguration(DeviceConfigurationMatcher matcher, DeviceConfiguration configuration);
|
|
| 493 | -
|
|
| 494 | - /**
|
|
| 495 | - * Removes a configuration by its matching object.
|
|
| 496 | - * @param matcher
|
|
| 497 | - */
|
|
| 498 | - void removeDeviceConfiguration(DeviceConfigurationMatcher matcher);
|
|
| 499 | -
|
|
| 500 | - /**
|
|
| 501 | - * Returns all configurations and their matching objects.
|
|
| 502 | - * @return the {@link DeviceConfiguration}s.
|
|
| 503 | - */
|
|
| 504 | - Map<DeviceConfigurationMatcher, DeviceConfiguration> getAllDeviceConfigurations();
|
|
| 505 | -
|
|
| 506 | - /**
|
|
| 507 | - * Forces a new start time on the RaceLog identified by the passed parameters.
|
|
| 508 | - * @param leaderboardName name of the RaceLog's leaderboard.
|
|
| 509 | - * @param raceColumnName name of the RaceLog's column
|
|
| 510 | - * @param fleetName name of the RaceLog's fleet
|
|
| 511 | - * @param authorName name of the {@link AbstractLogEventAuthor} the {@link RaceLogStartTimeEvent} will be created with
|
|
| 512 | - * @param authorPriority priority of the author.
|
|
| 513 | - * @param passId Pass identifier of the new start time event.
|
|
| 514 | - * @param logicalTimePoint logical {@link TimePoint} of the new event.
|
|
| 515 | - * @param startTime the new Start-Time
|
|
| 516 | - * @return
|
|
| 517 | - */
|
|
| 518 | - TimePoint setStartTimeAndProcedure(String leaderboardName, String raceColumnName, String fleetName, String authorName,
|
|
| 519 | - int authorPriority, int passId, TimePoint logicalTimePoint, TimePoint startTime, RacingProcedureType racingProcedure);
|
|
| 520 | -
|
|
| 521 | - /**
|
|
| 522 | - * Gets the start time, pass identifier and racing procedure for the queried race. Start time might be <code>null</code>.
|
|
| 523 | - */
|
|
| 524 | - Util.Triple<TimePoint, Integer, RacingProcedureType> getStartTimeAndProcedure(String leaderboardName, String raceColumnName, String fleetName);
|
|
| 525 | -
|
|
| 526 | - MongoObjectFactory getMongoObjectFactory();
|
|
| 527 | -
|
|
| 528 | - DomainObjectFactory getDomainObjectFactory();
|
|
| 529 | -
|
|
| 530 | - WindStore getWindStore();
|
|
| 531 | -
|
|
| 532 | - PolarDataService getPolarDataService();
|
|
| 533 | -
|
|
| 534 | - SimulationService getSimulationService();
|
|
| 535 | -
|
|
| 536 | - GPSFixStore getGPSFixStore();
|
|
| 537 | -
|
|
| 538 | - RaceTracker getRaceTrackerById(Object id);
|
|
| 539 | -
|
|
| 540 | - AbstractLogEventAuthor getServerAuthor();
|
|
| 541 | -
|
|
| 542 | - CompetitorStore getCompetitorStore();
|
|
| 543 | -
|
|
| 544 | - TypeBasedServiceFinderFactory getTypeBasedServiceFinderFactory();
|
|
| 545 | -
|
|
| 546 | - /**
|
|
| 547 | - * This lock exists to allow only one master data import at a time to avoid situation where multiple Imports
|
|
| 548 | - * override each other in unpredictable fashion
|
|
| 549 | - */
|
|
| 550 | - DataImportLockWithProgress getDataImportLock();
|
|
| 551 | -
|
|
| 552 | - DataImportProgress createOrUpdateDataImportProgressWithReplication(UUID importOperationId,
|
|
| 553 | - double overallProgressPct,
|
|
| 554 | - String subProgressName, double subProgressPct);
|
|
| 555 | -
|
|
| 556 | - DataImportProgress createOrUpdateDataImportProgressWithoutReplication(UUID importOperationId,
|
|
| 557 | - double overallProgressPct,
|
|
| 558 | - String subProgressName, double subProgressPct);
|
|
| 559 | -
|
|
| 560 | - void setDataImportFailedWithReplication(UUID importOperationId, String errorMessage);
|
|
| 561 | -
|
|
| 562 | - void setDataImportFailedWithoutReplication(UUID importOperationId, String errorMessage);
|
|
| 563 | -
|
|
| 564 | - void setDataImportDeleteProgressFromMapTimerWithReplication(UUID importOperationId);
|
|
| 565 | -
|
|
| 566 | - void setDataImportDeleteProgressFromMapTimerWithoutReplication(UUID importOperationId);
|
|
| 567 | -
|
|
| 568 | - /**
|
|
| 569 | - * For the reference to a remote sailing server, updates its events cache and returns the event list
|
|
| 570 | - * or, if fetching the event list from the remote server did fail, the exception for which it failed.
|
|
| 571 | - */
|
|
| 572 | - Util.Pair<Iterable<EventBase>, Exception> updateRemoteServerEventCacheSynchronously(RemoteSailingServerReference ref);
|
|
| 573 | -
|
|
| 574 | - /**
|
|
| 575 | - * Searches the content of this server, not that of any remote servers referenced by any {@link RemoteSailingServerReference}s.
|
|
| 576 | - */
|
|
| 577 | - @Override
|
|
| 578 | - Result<LeaderboardSearchResult> search(KeywordQuery query);
|
|
| 579 | -
|
|
| 580 | - /**
|
|
| 581 | - * Searches a specific remote server whose reference has the {@link RemoteSailingServerReference#getName() name}
|
|
| 582 | - * <code>remoteServerReferenceName</code>. If a remote server reference with that name is not known,
|
|
| 583 | - * <code>null</code> is returned. Otherwise, a non-<code>null</code> and possibly empty search result set is
|
|
| 584 | - * returned.
|
|
| 585 | - */
|
|
| 586 | - Result<LeaderboardSearchResultBase> searchRemotely(String remoteServerReferenceName, KeywordQuery query);
|
|
| 587 | -
|
|
| 588 | - /**
|
|
| 589 | - * Gets the configuration of the local sailing server instances.
|
|
| 590 | - */
|
|
| 591 | - SailingServerConfiguration getSailingServerConfiguration();
|
|
| 592 | -
|
|
| 593 | - void updateServerConfiguration(SailingServerConfiguration serverConfiguration);
|
|
| 594 | -
|
|
| 595 | - /**
|
|
| 596 | - * References to remote servers may be dead or alive. This is internally determined by regularly polling those
|
|
| 597 | - * servers for their events list. If the events list cannot be successfully retrieved, the server is considered "dead."
|
|
| 598 | - * This method returns the "live" server references.
|
|
| 599 | - */
|
|
| 600 | - Iterable<RemoteSailingServerReference> getLiveRemoteServerReferences();
|
|
| 601 | -
|
|
| 602 | - RemoteSailingServerReference getRemoteServerReferenceByName(String remoteServerReferenceName);
|
|
| 603 | -
|
|
| 604 | - void addRegattaWithoutReplication(Regatta regatta);
|
|
| 605 | -
|
|
| 606 | - void addEventWithoutReplication(Event event);
|
|
| 607 | -
|
|
| 608 | - /**
|
|
| 609 | - * Adds the leaderboard group to this service; if the group has an overall leaderboard, the overall leaderboard
|
|
| 610 | - * is added to this service as well. For both, the group and the overall leaderboard, any previously existing
|
|
| 611 | - * objects by the same name of that type will be replaced.
|
|
| 612 | - */
|
|
| 613 | - void addLeaderboardGroupWithoutReplication(LeaderboardGroup leaderboardGroup);
|
|
| 614 | -
|
|
| 615 | - /**
|
|
| 616 | - * @return {@code null} if no service can be found in the OSGi registry
|
|
| 617 | - */
|
|
| 618 | - FileStorageManagementService getFileStorageManagementService();
|
|
| 619 | -
|
|
| 620 | - ClassLoader getCombinedMasterDataClassLoader();
|
|
| 621 | -
|
|
| 622 | - Iterable<Competitor> getCompetitorInOrderOfWindwardDistanceTraveledFarthestFirst(TrackedRace trackedRace, TimePoint timePoint);
|
|
| 623 | -}
|
|
| 1 | +package com.sap.sailing.server; |
|
| 2 | + |
|
| 3 | +import java.io.IOException; |
|
| 4 | +import java.io.Serializable; |
|
| 5 | +import java.net.MalformedURLException; |
|
| 6 | +import java.net.SocketException; |
|
| 7 | +import java.net.URI; |
|
| 8 | +import java.net.URL; |
|
| 9 | +import java.util.Collection; |
|
| 10 | +import java.util.ConcurrentModificationException; |
|
| 11 | +import java.util.List; |
|
| 12 | +import java.util.Map; |
|
| 13 | +import java.util.UUID; |
|
| 14 | +import java.util.concurrent.ConcurrentHashMap; |
|
| 15 | + |
|
| 16 | +import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; |
|
| 17 | +import com.sap.sailing.domain.abstractlog.race.RaceLog; |
|
| 18 | +import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent; |
|
| 19 | +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver; |
|
| 20 | +import com.sap.sailing.domain.base.Competitor; |
|
| 21 | +import com.sap.sailing.domain.base.CompetitorStore; |
|
| 22 | +import com.sap.sailing.domain.base.CourseArea; |
|
| 23 | +import com.sap.sailing.domain.base.DomainFactory; |
|
| 24 | +import com.sap.sailing.domain.base.Event; |
|
| 25 | +import com.sap.sailing.domain.base.EventBase; |
|
| 26 | +import com.sap.sailing.domain.base.Fleet; |
|
| 27 | +import com.sap.sailing.domain.base.LeaderboardSearchResult; |
|
| 28 | +import com.sap.sailing.domain.base.LeaderboardSearchResultBase; |
|
| 29 | +import com.sap.sailing.domain.base.Mark; |
|
| 30 | +import com.sap.sailing.domain.base.RaceColumn; |
|
| 31 | +import com.sap.sailing.domain.base.RaceDefinition; |
|
| 32 | +import com.sap.sailing.domain.base.Regatta; |
|
| 33 | +import com.sap.sailing.domain.base.RegattaRegistry; |
|
| 34 | +import com.sap.sailing.domain.base.RemoteSailingServerReference; |
|
| 35 | +import com.sap.sailing.domain.base.SailingServerConfiguration; |
|
| 36 | +import com.sap.sailing.domain.base.Series; |
|
| 37 | +import com.sap.sailing.domain.base.configuration.DeviceConfiguration; |
|
| 38 | +import com.sap.sailing.domain.base.configuration.DeviceConfigurationIdentifier; |
|
| 39 | +import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher; |
|
| 40 | +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; |
|
| 41 | +import com.sap.sailing.domain.common.DataImportProgress; |
|
| 42 | +import com.sap.sailing.domain.common.Position; |
|
| 43 | +import com.sap.sailing.domain.common.RaceFetcher; |
|
| 44 | +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; |
|
| 45 | +import com.sap.sailing.domain.common.RegattaFetcher; |
|
| 46 | +import com.sap.sailing.domain.common.RegattaIdentifier; |
|
| 47 | +import com.sap.sailing.domain.common.RegattaName; |
|
| 48 | +import com.sap.sailing.domain.common.ScoringSchemeType; |
|
| 49 | +import com.sap.sailing.domain.common.media.MediaTrack; |
|
| 50 | +import com.sap.sailing.domain.common.racelog.RacingProcedureType; |
|
| 51 | +import com.sap.sailing.domain.leaderboard.EventResolver; |
|
| 52 | +import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; |
|
| 53 | +import com.sap.sailing.domain.leaderboard.Leaderboard; |
|
| 54 | +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; |
|
| 55 | +import com.sap.sailing.domain.leaderboard.LeaderboardGroupResolver; |
|
| 56 | +import com.sap.sailing.domain.leaderboard.LeaderboardRegistry; |
|
| 57 | +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; |
|
| 58 | +import com.sap.sailing.domain.leaderboard.ScoringScheme; |
|
| 59 | +import com.sap.sailing.domain.persistence.DomainObjectFactory; |
|
| 60 | +import com.sap.sailing.domain.persistence.MongoObjectFactory; |
|
| 61 | +import com.sap.sailing.domain.polars.PolarDataService; |
|
| 62 | +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; |
|
| 63 | +import com.sap.sailing.domain.ranking.RankingMetricConstructor; |
|
| 64 | +import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike; |
|
| 65 | +import com.sap.sailing.domain.tracking.DynamicTrackedRace; |
|
| 66 | +import com.sap.sailing.domain.tracking.RaceListener; |
|
| 67 | +import com.sap.sailing.domain.tracking.RaceTracker; |
|
| 68 | +import com.sap.sailing.domain.tracking.TrackedRace; |
|
| 69 | +import com.sap.sailing.domain.tracking.TrackedRegatta; |
|
| 70 | +import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; |
|
| 71 | +import com.sap.sailing.domain.tracking.TrackerManager; |
|
| 72 | +import com.sap.sailing.domain.tracking.WindStore; |
|
| 73 | +import com.sap.sailing.server.masterdata.DataImportLockWithProgress; |
|
| 74 | +import com.sap.sailing.server.simulation.SimulationService; |
|
| 75 | +import com.sap.sse.common.TimePoint; |
|
| 76 | +import com.sap.sse.common.TypeBasedServiceFinderFactory; |
|
| 77 | +import com.sap.sse.common.Util; |
|
| 78 | +import com.sap.sse.common.Util.Triple; |
|
| 79 | +import com.sap.sse.common.search.KeywordQuery; |
|
| 80 | +import com.sap.sse.common.search.Result; |
|
| 81 | +import com.sap.sse.common.search.Searchable; |
|
| 82 | +import com.sap.sse.filestorage.FileStorageManagementService; |
|
| 83 | +import com.sap.sse.replication.impl.ReplicableWithObjectInputStream; |
|
| 84 | +import com.sap.sse.shared.media.ImageDescriptor; |
|
| 85 | +import com.sap.sse.shared.media.VideoDescriptor; |
|
| 86 | + |
|
| 87 | +/** |
|
| 88 | + * An OSGi service that can be used to track boat races using a TracTrac connector that pushes live GPS boat location, |
|
| 89 | + * waypoint, coarse and mark passing data. |
|
| 90 | + * <p> |
|
| 91 | + * |
|
| 92 | + * If a race/regatta is already being tracked, another {@link #addTracTracRace(URL, URI, URI, WindStore, long)} or |
|
| 93 | + * {@link #addRegatta(URL, URI, URI, WindStore, long)} call will have no effect, even if a different {@link WindStore} |
|
| 94 | + * is requested. |
|
| 95 | + * <p> |
|
| 96 | + * |
|
| 97 | + * When the tracking of a race/regatta is {@link #stopTracking(Regatta, RaceDefinition) stopped}, the next time it's |
|
| 98 | + * started to be tracked, a new {@link TrackedRace} at least will be constructed. This also means that when a |
|
| 99 | + * {@link TrackedRegatta} exists that still holds other {@link TrackedRace}s, the no longer tracked {@link TrackedRace} |
|
| 100 | + * will be removed from the {@link TrackedRegatta}. corresponding information is removed also from the |
|
| 101 | + * {@link DomainFactory}'s caches to ensure that clean, fresh data is received should another tracking request be issued |
|
| 102 | + * later. |
|
| 103 | + * <p> |
|
| 104 | + * |
|
| 105 | + * During receiving the initial load for a replication in {@link #initiallyFillFromInternal(java.io.ObjectInputStream)}, |
|
| 106 | + * tracked regattas read from the stream are observed (see {@link RaceListener}) by this object for automatic updates to |
|
| 107 | + * the default leaderboard and for automatic linking to leaderboard columns. It is assumed that no explicit replication |
|
| 108 | + * of these operations will happen based on the changes performed on the replication master. |
|
| 109 | + * |
|
| 110 | + * @author Axel Uhl (d043530) |
|
| 111 | + * |
|
| 112 | + */ |
|
| 113 | +public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetcher, RegattaRegistry, RaceFetcher, |
|
| 114 | + LeaderboardRegistry, EventResolver, LeaderboardGroupResolver, TrackerManager, Searchable<LeaderboardSearchResult, KeywordQuery>, |
|
| 115 | + ReplicableWithObjectInputStream<RacingEventService, RacingEventServiceOperation<?>>, RaceLogResolver { |
|
| 116 | + @Override |
|
| 117 | + Regatta getRegatta(RegattaName regattaName); |
|
| 118 | + |
|
| 119 | + @Override |
|
| 120 | + RaceDefinition getRace(RegattaAndRaceIdentifier raceIdentifier); |
|
| 121 | + |
|
| 122 | + DynamicTrackedRace getTrackedRace(Regatta regatta, RaceDefinition race); |
|
| 123 | + |
|
| 124 | + DynamicTrackedRace getTrackedRace(RegattaAndRaceIdentifier raceIdentifier); |
|
| 125 | + |
|
| 126 | + /** |
|
| 127 | + * Obtains an unmodifiable map of the leaderboard configured in this service keyed by their names. |
|
| 128 | + */ |
|
| 129 | + Map<String, Leaderboard> getLeaderboards(); |
|
| 130 | + |
|
| 131 | + /** |
|
| 132 | + * @return a leaderboard whose {@link Leaderboard#getName()} method returns the value of the <code>name</code> |
|
| 133 | + * parameter, or <code>null</code> if no such leaderboard is known to this service |
|
| 134 | + */ |
|
| 135 | + Leaderboard getLeaderboardByName(String name); |
|
| 136 | + |
|
| 137 | + /** |
|
| 138 | + * Looks at the mark tracks in the tracked races attached to the <code>leaderboard</code>. If it doesn't find a |
|
| 139 | + * track for the <code>mark</code> requested there which has fixes before and after <code>timePoint</code> (to |
|
| 140 | + * ensure that no track cropping has taken place, removing the fixes for the interesting time period), looks in the |
|
| 141 | + * leaderboard's regatta log and the specific <code>raceLog</code> (if provided) or all race logs attached to the |
|
| 142 | + * leaderboard (if not provided) for device mappings for the mark and tries to load fixes from the |
|
| 143 | + * {@link GPSFixStore}. The latter is only necessary if the mark isn't found in any tracked race with fixes |
|
| 144 | + * surrounding <code>timePoint</code> because should there be a tracked race in the leaderboard that has the mark |
|
| 145 | + * then it will also have received the fixes from the {@link GPSFixStore} through the regatta log mapping. |
|
| 146 | + * <p> |
|
| 147 | + * |
|
| 148 | + * @return the position obtained by interpolation but never extrapolation from the track identified as described |
|
| 149 | + * above |
|
| 150 | + */ |
|
| 151 | + Position getMarkPosition(Mark mark, LeaderboardThatHasRegattaLike leaderboard, TimePoint timePoint, RaceLog raceLog); |
|
| 152 | + |
|
| 153 | + /** |
|
| 154 | + * Stops tracking all races of the regatta specified. This will also stop tracking wind for all races of this regatta. |
|
| 155 | + * See {@link #stopTrackingWind(Regatta, RaceDefinition)}. If there were multiple calls to |
|
| 156 | + * {@link #addTracTracRace(URL, URI, URI, WindStore, long)} with an equal combination of URLs/URIs, the {@link TracTracRaceTracker} |
|
| 157 | + * already tracking the race was re-used. The trackers will be stopped by this call regardless of how many calls |
|
| 158 | + * were made that ensured they were tracking. |
|
| 159 | + */ |
|
| 160 | + void stopTracking(Regatta regatta) throws MalformedURLException, IOException, InterruptedException; |
|
| 161 | + |
|
| 162 | + /** |
|
| 163 | + * Removes <code>race</code> and any corresponding {@link #getTrackedRace(Regatta, RaceDefinition) tracked race} |
|
| 164 | + * from this service. If it was the last {@link RaceDefinition} in its {@link Regatta} and the regatta |
|
| 165 | + * {@link Regatta#isPersistent() is not stored persistently}, the <code>regatta</code> is removed as well and will no |
|
| 166 | + * longer be returned by {@link #getAllRegattas()}. The wind tracking is stopped for <code>race</code>. |
|
| 167 | + * <p> |
|
| 168 | + * |
|
| 169 | + * Any {@link RaceTracker} for which <code>race</race> is the last race tracked that is still reachable |
|
| 170 | + * from {@link #getAllRegattas()} will be {@link RaceTracker#stop(boolean) stopped}. |
|
| 171 | + * |
|
| 172 | + * The <code>race</code> will be also removed from all leaderboards containing a column that has <code>race</code>'s |
|
| 173 | + * {@link #getTrackedRace(Regatta, RaceDefinition) corresponding} {@link TrackedRace} as its |
|
| 174 | + * {@link RaceColumn#getTrackedRace(Fleet)}. |
|
| 175 | + * |
|
| 176 | + * @param regatta |
|
| 177 | + * the regatta from which to remove the race |
|
| 178 | + * @param race |
|
| 179 | + * the race to remove |
|
| 180 | + */ |
|
| 181 | + void removeRace(Regatta regatta, RaceDefinition race) throws MalformedURLException, IOException,InterruptedException; |
|
| 182 | + |
|
| 183 | + /** |
|
| 184 | + * @param port |
|
| 185 | + * the UDP port on which to listen for incoming messages from Expedition clients |
|
| 186 | + * @param correctByDeclination |
|
| 187 | + * An optional service to convert the wind bearings (which the receiver may |
|
| 188 | + * believe to be true bearings) from magnetic to true bearings. |
|
| 189 | + * @throws SocketException |
|
| 190 | + * thrown, e.g., in case there is already another listener on the port requested |
|
| 191 | + */ |
|
| 192 | + void startTrackingWind(Regatta regatta, RaceDefinition race, boolean correctByDeclination) throws Exception; |
|
| 193 | + |
|
| 194 | + void stopTrackingWind(Regatta regatta, RaceDefinition race) throws SocketException, IOException; |
|
| 195 | + |
|
| 196 | + /** |
|
| 197 | + * The {@link Triple#getC() third component} of the triples returned is a wind tracker-specific |
|
| 198 | + * comment where a wind tracker may provide information such as its type name or, if applicable, |
|
| 199 | + * connectivity information such as the network port on which it receives wind information. |
|
| 200 | + */ |
|
| 201 | + Iterable<Util.Triple<Regatta, RaceDefinition, String>> getWindTrackedRaces(); |
|
| 202 | + |
|
| 203 | + /** |
|
| 204 | + * Creates a new leaderboard with the <code>name</code> specified. |
|
| 205 | + * @param discardThresholds |
|
| 206 | + * Tells the thresholds from which on a next higher number of worst races will be discarded per |
|
| 207 | + * competitor. Example: <code>[3, 6]</code> means that starting from three races the single worst race |
|
| 208 | + * will be discarded; starting from six races, the two worst races per competitor are discarded. |
|
| 209 | + * |
|
| 210 | + * @return the leaderboard created |
|
| 211 | + */ |
|
| 212 | + FlexibleLeaderboard addFlexibleLeaderboard(String leaderboardName, String leaderboardDisplayName, int[] discardThresholds, ScoringScheme scoringScheme, Serializable courseAreaId); |
|
| 213 | + |
|
| 214 | + RegattaLeaderboard addRegattaLeaderboard(RegattaIdentifier regattaIdentifier, String leaderboardDisplayName, int[] discardThresholds); |
|
| 215 | + |
|
| 216 | + void removeLeaderboard(String leaderboardName); |
|
| 217 | + |
|
| 218 | + /** |
|
| 219 | + * Renames a leaderboard. If a leaderboard by the name <code>oldName</code> does not exist in {@link #getLeaderboards()}, |
|
| 220 | + * or if a leaderboard with the name <code>newName</code> already exists, an {@link IllegalArgumentException} is thrown. |
|
| 221 | + * If the method completes normally, the rename has been successful, and the leaderboard previously obtained by calling |
|
| 222 | + * {@link #getLeaderboardByName(String) getLeaderboardByName(oldName)} can now be obtained by calling |
|
| 223 | + * {@link #getLeaderboardByName(String) getLeaderboardByName(newName)}. |
|
| 224 | + */ |
|
| 225 | + void renameLeaderboard(String oldName, String newName); |
|
| 226 | + |
|
| 227 | + RaceColumn addColumnToLeaderboard(String columnName, String leaderboardName, boolean medalRace); |
|
| 228 | + |
|
| 229 | + void moveLeaderboardColumnUp(String leaderboardName, String columnName); |
|
| 230 | + |
|
| 231 | + void moveLeaderboardColumnDown(String leaderboardName, String columnName); |
|
| 232 | + |
|
| 233 | + void removeLeaderboardColumn(String leaderboardName, String columnName); |
|
| 234 | + |
|
| 235 | + void renameLeaderboardColumn(String leaderboardName, String oldColumnName, String newColumnName); |
|
| 236 | + |
|
| 237 | + /** |
|
| 238 | + * @see RaceColumn#setFactor(Double) |
|
| 239 | + */ |
|
| 240 | + void updateLeaderboardColumnFactor(String leaderboardName, String columnName, Double factor); |
|
| 241 | + |
|
| 242 | + /** |
|
| 243 | + * Updates the leaderboard data in the persistent store |
|
| 244 | + */ |
|
| 245 | + void updateStoredLeaderboard(Leaderboard leaderboard); |
|
| 246 | + |
|
| 247 | + void updateStoredRegatta(Regatta regatta); |
|
| 248 | + |
|
| 249 | + void stopTrackingAndRemove(Regatta regatta) throws MalformedURLException, IOException, InterruptedException; |
|
| 250 | + |
|
| 251 | + /** |
|
| 252 | + * Removes the regatta as well as all regatta leaderboards for that regatta |
|
| 253 | + */ |
|
| 254 | + void removeRegatta(Regatta regatta) throws MalformedURLException, IOException, InterruptedException; |
|
| 255 | + |
|
| 256 | + /** |
|
| 257 | + * Removes the given series |
|
| 258 | + */ |
|
| 259 | + void removeSeries(Series series) throws MalformedURLException, IOException, InterruptedException; |
|
| 260 | + |
|
| 261 | + DynamicTrackedRace getExistingTrackedRace(RegattaAndRaceIdentifier raceIdentifier); |
|
| 262 | + |
|
| 263 | + /** |
|
| 264 | + * Obtains an unmodifiable map of the leaderboard groups configured in this service keyed by their names. |
|
| 265 | + */ |
|
| 266 | + Map<String, LeaderboardGroup> getLeaderboardGroups(); |
|
| 267 | + |
|
| 268 | + /** |
|
| 269 | + * Creates a new group with the name <code>groupName</code>, the description <code>desciption</code> and the |
|
| 270 | + * leaderboards with the names in <code>leaderboardNames</code> and saves it in the database. |
|
| 271 | + * @param id TODO |
|
| 272 | + * @param groupName |
|
| 273 | + * The name of the new group |
|
| 274 | + * @param description |
|
| 275 | + * The description of the new group |
|
| 276 | + * @param displayName TODO |
|
| 277 | + * @param displayGroupsInReverseOrder TODO |
|
| 278 | + * @param leaderboardNames |
|
| 279 | + * The names of the leaderboards, which should be contained by the new group.<br /> |
|
| 280 | + * If there isn't a leaderboard with one of these names an {@link IllegalArgumentException} is thrown. |
|
| 281 | + * @return The new leaderboard group |
|
| 282 | + */ |
|
| 283 | + LeaderboardGroup addLeaderboardGroup(UUID id, String groupName, String description, |
|
| 284 | + String displayName, boolean displayGroupsInReverseOrder, List<String> leaderboardNames, int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType); |
|
| 285 | + |
|
| 286 | + /** |
|
| 287 | + * Removes the group with the name <code>groupName</code> from the service and the database. |
|
| 288 | + * @param groupName The name of the group which shall be removed. |
|
| 289 | + */ |
|
| 290 | + void removeLeaderboardGroup(String groupName); |
|
| 291 | + |
|
| 292 | + /** |
|
| 293 | + * Renames the group with the name <code>oldName</code> to the <code>newName</code>.<br /> |
|
| 294 | + * If there's no group with the name <code>oldName</code> or there's already a group with the name |
|
| 295 | + * <code>newName</code> a {@link IllegalArgumentException} is thrown. |
|
| 296 | + * |
|
| 297 | + * @param oldName The old name of the group |
|
| 298 | + * @param newName The new name of the group |
|
| 299 | + */ |
|
| 300 | + void renameLeaderboardGroup(String oldName, String newName); |
|
| 301 | + |
|
| 302 | + /** |
|
| 303 | + * Updates the group data in the persistant store. |
|
| 304 | + */ |
|
| 305 | + void updateStoredLeaderboardGroup(LeaderboardGroup leaderboardGroup); |
|
| 306 | + |
|
| 307 | + DynamicTrackedRace createTrackedRace(RegattaAndRaceIdentifier raceIdentifier, WindStore windStore, GPSFixStore gpsFixStore, |
|
| 308 | + long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, boolean useMarkPassingCalculator); |
|
| 309 | + |
|
| 310 | + Regatta getOrCreateDefaultRegatta(String name, String boatClassName, Serializable id); |
|
| 311 | + |
|
| 312 | + /** |
|
| 313 | + * @param series the series must not have any {@link RaceColumn}s yet |
|
| 314 | + */ |
|
| 315 | + Regatta createRegatta(String regattaName, String boatClassName, TimePoint startDate, TimePoint endDate, Serializable id, Iterable<? extends Series> series, |
|
| 316 | + boolean persistent, ScoringScheme scoringScheme, Serializable defaultCourseAreaId, |
|
| 317 | + boolean useStartTimeInference, RankingMetricConstructor rankingMetricConstructor); |
|
| 318 | + |
|
| 319 | + Regatta updateRegatta(RegattaIdentifier regattaIdentifier, TimePoint startDate, TimePoint endDate, Serializable newDefaultCourseAreaId, RegattaConfiguration regattaConfiguration, Iterable<? extends Series> series, boolean useStartTimeInference); |
|
| 320 | + |
|
| 321 | + /** |
|
| 322 | + * Adds <code>raceDefinition</code> to the {@link Regatta} such that it will appear in {@link Regatta#getAllRaces()} |
|
| 323 | + * and {@link Regatta#getRaceByName(String)}. |
|
| 324 | + * |
|
| 325 | + * @param addToRegatta identifier of an regatta that must exist already |
|
| 326 | + */ |
|
| 327 | + void addRace(RegattaIdentifier addToRegatta, RaceDefinition raceDefinition); |
|
| 328 | + |
|
| 329 | + void updateLeaderboardGroup(String oldName, String newName, String description, String displayName, |
|
| 330 | + List<String> leaderboardNames, int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType); |
|
| 331 | + |
|
| 332 | + /** |
|
| 333 | + * @return a thread-safe copy of the events currently known by the service; it's safe for callers to iterate over |
|
| 334 | + * the iterable returned, and no risk of a {@link ConcurrentModificationException} exists |
|
| 335 | + */ |
|
| 336 | + Iterable<Event> getAllEvents(); |
|
| 337 | + |
|
| 338 | + /** |
|
| 339 | + * Creates a new event with the name <code>eventName</code>, the venue <code>venue</code> and the regattas with the |
|
| 340 | + * names in <code>regattaNames</code>, saves it in the database and replicates it. Use for TESTING only! |
|
| 341 | + * |
|
| 342 | + * @param eventName |
|
| 343 | + * The name of the new event |
|
| 344 | + * @param eventDescription TODO |
|
| 345 | + * @param startDate |
|
| 346 | + * The start date of the event |
|
| 347 | + * @param endDate |
|
| 348 | + * The end date of the event |
|
| 349 | + * @param isPublic |
|
| 350 | + * Indicates whether the event is public accessible via the publication URL or not |
|
| 351 | + * @param id |
|
| 352 | + * The id of the new event |
|
| 353 | + * @param venue |
|
| 354 | + * The name of the venue of the new event |
|
| 355 | + * @return The new event |
|
| 356 | + */ |
|
| 357 | + Event addEvent(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, UUID id); |
|
| 358 | + |
|
| 359 | + /** |
|
| 360 | + * Updates a sailing event with the name <code>eventName</code>, the venue<code>venue</code> and the regattas with |
|
| 361 | + * the names in <code>regattaNames</code> and updates it in the database. |
|
| 362 | + * @param eventName |
|
| 363 | + * The name of the event to update |
|
| 364 | + * @param startDate |
|
| 365 | + * The start date of the event |
|
| 366 | + * @param endDate |
|
| 367 | + * The end date of the event |
|
| 368 | + * @param venueName |
|
| 369 | + * The name of the venue of the event |
|
| 370 | + * @param isPublic |
|
| 371 | + * Indicates whether the event is public accessible via the publication URL or not |
|
| 372 | + * @return The new event |
|
| 373 | + */ |
|
| 374 | + void updateEvent(UUID id, String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, |
|
| 375 | + String venueName, boolean isPublic, Iterable<UUID> leaderboardGroupIds, URL officialWebsiteURL, URL sailorsInfoWebsiteURL, |
|
| 376 | + Iterable<ImageDescriptor> images, Iterable<VideoDescriptor> videos); |
|
| 377 | + |
|
| 378 | + /** |
|
| 379 | + * Renames a sailing event. If a sailing event by the name <code>oldName</code> does not exist in {@link #getEvents()}, |
|
| 380 | + * or if a event with the name <code>newName</code> already exists, an {@link IllegalArgumentException} is thrown. |
|
| 381 | + * If the method completes normally, the rename has been successful, and the event previously obtained by calling |
|
| 382 | + * {@link #getEventByName(String) getEventByName(oldName)} can now be obtained by calling |
|
| 383 | + * {@link #getEventByName(String) getEventByName(newName)}. |
|
| 384 | + */ |
|
| 385 | + void renameEvent(UUID id, String newEventName); |
|
| 386 | + |
|
| 387 | + void removeEvent(UUID id); |
|
| 388 | + |
|
| 389 | + |
|
| 390 | + /** |
|
| 391 | + * @return a thread-safe copy of the events (or the exception that occurred trying to obtain the events; arranged in |
|
| 392 | + * a {@link Util.Pair}) of from all sailing server instances currently known by the service; it's safe for |
|
| 393 | + * callers to iterate over the iterable returned, and no risk of a {@link ConcurrentModificationException} |
|
| 394 | + * exists |
|
| 395 | + */ |
|
| 396 | + Map<RemoteSailingServerReference, Util.Pair<Iterable<EventBase>, Exception>> getPublicEventsOfAllSailingServers(); |
|
| 397 | + |
|
| 398 | + RemoteSailingServerReference addRemoteSailingServerReference(String name, URL url); |
|
| 399 | + |
|
| 400 | + void removeRemoteSailingServerReference(String name); |
|
| 401 | + |
|
| 402 | + |
|
| 403 | + CourseArea[] addCourseAreas(UUID eventId, String[] courseAreaNames, UUID[] courseAreaIds); |
|
| 404 | + |
|
| 405 | + com.sap.sailing.domain.base.DomainFactory getBaseDomainFactory(); |
|
| 406 | + |
|
| 407 | + CourseArea getCourseArea(Serializable courseAreaId); |
|
| 408 | + |
|
| 409 | + /** |
|
| 410 | + * Adds the specified mediaTrack to the in-memory media library. |
|
| 411 | + * Important note: Only if mediaTrack.dbId != null the mediaTrack will be persisted in the the database. |
|
| 412 | + * @param mediaTrack |
|
| 413 | + */ |
|
| 414 | + void mediaTrackAdded(MediaTrack mediaTrack); |
|
| 415 | + |
|
| 416 | + /** |
|
| 417 | + * Calling mediaTrackAdded for every entry in the specified collection. |
|
| 418 | + * @param mediaTracks |
|
| 419 | + */ |
|
| 420 | + void mediaTracksAdded(Collection<MediaTrack> mediaTracks); |
|
| 421 | + |
|
| 422 | + void mediaTrackTitleChanged(MediaTrack mediaTrack); |
|
| 423 | + |
|
| 424 | + void mediaTrackUrlChanged(MediaTrack mediaTrack); |
|
| 425 | + |
|
| 426 | + void mediaTrackStartTimeChanged(MediaTrack mediaTrack); |
|
| 427 | + |
|
| 428 | + void mediaTrackDurationChanged(MediaTrack mediaTrack); |
|
| 429 | + |
|
| 430 | + void mediaTrackAssignedRacesChanged(MediaTrack mediaTrack); |
|
| 431 | + |
|
| 432 | + void mediaTrackDeleted(MediaTrack mediaTrack); |
|
| 433 | + |
|
| 434 | + /** |
|
| 435 | + * In contrast to mediaTracksAdded, this method takes mediaTracks with a given dbId. |
|
| 436 | + * Checks if the track already exists in the library and the database and adds/stores it |
|
| 437 | + * accordingly. If a track already exists and override, its properties are checked for changes |
|
| 438 | + * @param mediaTrack |
|
| 439 | + * @param override If true, track properties (title, url, start time, duration, not mime type!) will be |
|
| 440 | + * overwritten with the values from the track to be imported. |
|
| 441 | + */ |
|
| 442 | + void mediaTracksImported(Collection<MediaTrack> mediaTracksToImport, boolean override); |
|
| 443 | + |
|
| 444 | + Collection<MediaTrack> getMediaTracksForRace(RegattaAndRaceIdentifier regattaAndRaceIdentifier); |
|
| 445 | + |
|
| 446 | + Collection<MediaTrack> getMediaTracksInTimeRange(RegattaAndRaceIdentifier regattaAndRaceIdentifier); |
|
| 447 | + |
|
| 448 | + Collection<MediaTrack> getAllMediaTracks(); |
|
| 449 | + |
|
| 450 | + void reloadRaceLog(String leaderboardName, String raceColumnName, String fleetName); |
|
| 451 | + |
|
| 452 | + RaceLog getRaceLog(String leaderboardName, String raceColumnName, String fleetName); |
|
| 453 | + |
|
| 454 | + /** |
|
| 455 | + * @param rankingMetricConstructor TODO |
|
| 456 | + * @return a pair with the found or created regatta, and a boolean that tells whether the regatta was created during |
|
| 457 | + * the call |
|
| 458 | + */ |
|
| 459 | + Util.Pair<Regatta, Boolean> getOrCreateRegattaWithoutReplication(String fullRegattaName, String boatClassName, |
|
| 460 | + TimePoint startDate, TimePoint endDate, Serializable id, |
|
| 461 | + Iterable<? extends Series> series, boolean persistent, ScoringScheme scoringScheme, |
|
| 462 | + Serializable defaultCourseAreaId, boolean useStartTimeInference, RankingMetricConstructor rankingMetricConstructor); |
|
| 463 | + |
|
| 464 | + /** |
|
| 465 | + * @return map where keys are the toString() representation of the {@link RaceDefinition#getId() IDs} of races passed to |
|
| 466 | + * {@link #setRegattaForRace(Regatta, RaceDefinition)}. It helps remember the connection between races and regattas. |
|
| 467 | + */ |
|
| 468 | + ConcurrentHashMap<String, Regatta> getPersistentRegattasForRaceIDs(); |
|
| 469 | + |
|
| 470 | + Event createEventWithoutReplication(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venue, |
|
| 471 | + boolean isPublic, UUID id, URL officialWebsiteURL, URL sailorsInfoWebsiteURL, Iterable<ImageDescriptor> images, |
|
| 472 | + Iterable<VideoDescriptor> videos); |
|
| 473 | + |
|
| 474 | + void setRegattaForRace(Regatta regatta, String raceIdAsString); |
|
| 475 | + |
|
| 476 | + CourseArea[] addCourseAreasWithoutReplication(UUID eventId, UUID[] courseAreaIds, String[] courseAreaNames); |
|
| 477 | + |
|
| 478 | + CourseArea[] removeCourseAreaWithoutReplication(UUID eventId, UUID[] courseAreaIds); |
|
| 479 | + |
|
| 480 | + /** |
|
| 481 | + * Returns a mobile device's configuration. |
|
| 482 | + * @param identifier of the client (may include event) |
|
| 483 | + * @return the {@link DeviceConfiguration} |
|
| 484 | + */ |
|
| 485 | + DeviceConfiguration getDeviceConfiguration(DeviceConfigurationIdentifier identifier); |
|
| 486 | + |
|
| 487 | + /** |
|
| 488 | + * Adds a device configuration. |
|
| 489 | + * @param matcher defining for which the configuration applies. |
|
| 490 | + * @param configuration of the device. |
|
| 491 | + */ |
|
| 492 | + void createOrUpdateDeviceConfiguration(DeviceConfigurationMatcher matcher, DeviceConfiguration configuration); |
|
| 493 | + |
|
| 494 | + /** |
|
| 495 | + * Removes a configuration by its matching object. |
|
| 496 | + * @param matcher |
|
| 497 | + */ |
|
| 498 | + void removeDeviceConfiguration(DeviceConfigurationMatcher matcher); |
|
| 499 | + |
|
| 500 | + /** |
|
| 501 | + * Returns all configurations and their matching objects. |
|
| 502 | + * @return the {@link DeviceConfiguration}s. |
|
| 503 | + */ |
|
| 504 | + Map<DeviceConfigurationMatcher, DeviceConfiguration> getAllDeviceConfigurations(); |
|
| 505 | + |
|
| 506 | + /** |
|
| 507 | + * Forces a new start time on the RaceLog identified by the passed parameters. |
|
| 508 | + * @param leaderboardName name of the RaceLog's leaderboard. |
|
| 509 | + * @param raceColumnName name of the RaceLog's column |
|
| 510 | + * @param fleetName name of the RaceLog's fleet |
|
| 511 | + * @param authorName name of the {@link AbstractLogEventAuthor} the {@link RaceLogStartTimeEvent} will be created with |
|
| 512 | + * @param authorPriority priority of the author. |
|
| 513 | + * @param passId Pass identifier of the new start time event. |
|
| 514 | + * @param logicalTimePoint logical {@link TimePoint} of the new event. |
|
| 515 | + * @param startTime the new Start-Time |
|
| 516 | + * @return |
|
| 517 | + */ |
|
| 518 | + TimePoint setStartTimeAndProcedure(String leaderboardName, String raceColumnName, String fleetName, String authorName, |
|
| 519 | + int authorPriority, int passId, TimePoint logicalTimePoint, TimePoint startTime, RacingProcedureType racingProcedure); |
|
| 520 | + |
|
| 521 | + /** |
|
| 522 | + * Gets the start time, pass identifier and racing procedure for the queried race. Start time might be <code>null</code>. |
|
| 523 | + */ |
|
| 524 | + Util.Triple<TimePoint, Integer, RacingProcedureType> getStartTimeAndProcedure(String leaderboardName, String raceColumnName, String fleetName); |
|
| 525 | + |
|
| 526 | + MongoObjectFactory getMongoObjectFactory(); |
|
| 527 | + |
|
| 528 | + DomainObjectFactory getDomainObjectFactory(); |
|
| 529 | + |
|
| 530 | + WindStore getWindStore(); |
|
| 531 | + |
|
| 532 | + PolarDataService getPolarDataService(); |
|
| 533 | + |
|
| 534 | + SimulationService getSimulationService(); |
|
| 535 | + |
|
| 536 | + GPSFixStore getGPSFixStore(); |
|
| 537 | + |
|
| 538 | + RaceTracker getRaceTrackerById(Object id); |
|
| 539 | + |
|
| 540 | + AbstractLogEventAuthor getServerAuthor(); |
|
| 541 | + |
|
| 542 | + CompetitorStore getCompetitorStore(); |
|
| 543 | + |
|
| 544 | + TypeBasedServiceFinderFactory getTypeBasedServiceFinderFactory(); |
|
| 545 | + |
|
| 546 | + /** |
|
| 547 | + * This lock exists to allow only one master data import at a time to avoid situation where multiple Imports |
|
| 548 | + * override each other in unpredictable fashion |
|
| 549 | + */ |
|
| 550 | + DataImportLockWithProgress getDataImportLock(); |
|
| 551 | + |
|
| 552 | + DataImportProgress createOrUpdateDataImportProgressWithReplication(UUID importOperationId, |
|
| 553 | + double overallProgressPct, |
|
| 554 | + String subProgressName, double subProgressPct); |
|
| 555 | + |
|
| 556 | + DataImportProgress createOrUpdateDataImportProgressWithoutReplication(UUID importOperationId, |
|
| 557 | + double overallProgressPct, |
|
| 558 | + String subProgressName, double subProgressPct); |
|
| 559 | + |
|
| 560 | + void setDataImportFailedWithReplication(UUID importOperationId, String errorMessage); |
|
| 561 | + |
|
| 562 | + void setDataImportFailedWithoutReplication(UUID importOperationId, String errorMessage); |
|
| 563 | + |
|
| 564 | + void setDataImportDeleteProgressFromMapTimerWithReplication(UUID importOperationId); |
|
| 565 | + |
|
| 566 | + void setDataImportDeleteProgressFromMapTimerWithoutReplication(UUID importOperationId); |
|
| 567 | + |
|
| 568 | + /** |
|
| 569 | + * For the reference to a remote sailing server, updates its events cache and returns the event list |
|
| 570 | + * or, if fetching the event list from the remote server did fail, the exception for which it failed. |
|
| 571 | + */ |
|
| 572 | + Util.Pair<Iterable<EventBase>, Exception> updateRemoteServerEventCacheSynchronously(RemoteSailingServerReference ref); |
|
| 573 | + |
|
| 574 | + /** |
|
| 575 | + * Searches the content of this server, not that of any remote servers referenced by any {@link RemoteSailingServerReference}s. |
|
| 576 | + */ |
|
| 577 | + @Override |
|
| 578 | + Result<LeaderboardSearchResult> search(KeywordQuery query); |
|
| 579 | + |
|
| 580 | + /** |
|
| 581 | + * Searches a specific remote server whose reference has the {@link RemoteSailingServerReference#getName() name} |
|
| 582 | + * <code>remoteServerReferenceName</code>. If a remote server reference with that name is not known, |
|
| 583 | + * <code>null</code> is returned. Otherwise, a non-<code>null</code> and possibly empty search result set is |
|
| 584 | + * returned. |
|
| 585 | + */ |
|
| 586 | + Result<LeaderboardSearchResultBase> searchRemotely(String remoteServerReferenceName, KeywordQuery query); |
|
| 587 | + |
|
| 588 | + /** |
|
| 589 | + * Gets the configuration of the local sailing server instances. |
|
| 590 | + */ |
|
| 591 | + SailingServerConfiguration getSailingServerConfiguration(); |
|
| 592 | + |
|
| 593 | + void updateServerConfiguration(SailingServerConfiguration serverConfiguration); |
|
| 594 | + |
|
| 595 | + /** |
|
| 596 | + * References to remote servers may be dead or alive. This is internally determined by regularly polling those |
|
| 597 | + * servers for their events list. If the events list cannot be successfully retrieved, the server is considered "dead." |
|
| 598 | + * This method returns the "live" server references. |
|
| 599 | + */ |
|
| 600 | + Iterable<RemoteSailingServerReference> getLiveRemoteServerReferences(); |
|
| 601 | + |
|
| 602 | + RemoteSailingServerReference getRemoteServerReferenceByName(String remoteServerReferenceName); |
|
| 603 | + |
|
| 604 | + void addRegattaWithoutReplication(Regatta regatta); |
|
| 605 | + |
|
| 606 | + void addEventWithoutReplication(Event event); |
|
| 607 | + |
|
| 608 | + /** |
|
| 609 | + * Adds the leaderboard group to this service; if the group has an overall leaderboard, the overall leaderboard |
|
| 610 | + * is added to this service as well. For both, the group and the overall leaderboard, any previously existing |
|
| 611 | + * objects by the same name of that type will be replaced. |
|
| 612 | + */ |
|
| 613 | + void addLeaderboardGroupWithoutReplication(LeaderboardGroup leaderboardGroup); |
|
| 614 | + |
|
| 615 | + /** |
|
| 616 | + * @return {@code null} if no service can be found in the OSGi registry |
|
| 617 | + */ |
|
| 618 | + FileStorageManagementService getFileStorageManagementService(); |
|
| 619 | + |
|
| 620 | + ClassLoader getCombinedMasterDataClassLoader(); |
|
| 621 | + |
|
| 622 | + Iterable<Competitor> getCompetitorInOrderOfWindwardDistanceTraveledFarthestFirst(TrackedRace trackedRace, TimePoint timePoint); |
|
| 623 | +} |
java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java
| ... | ... | @@ -1,3344 +1,3344 @@ |
| 1 | -package com.sap.sailing.server.impl;
|
|
| 2 | -
|
|
| 3 | -import java.io.BufferedReader;
|
|
| 4 | -import java.io.IOException;
|
|
| 5 | -import java.io.InputStream;
|
|
| 6 | -import java.io.InputStreamReader;
|
|
| 7 | -import java.io.ObjectInputStream;
|
|
| 8 | -import java.io.ObjectOutputStream;
|
|
| 9 | -import java.io.Serializable;
|
|
| 10 | -import java.net.MalformedURLException;
|
|
| 11 | -import java.net.SocketException;
|
|
| 12 | -import java.net.URL;
|
|
| 13 | -import java.net.URLConnection;
|
|
| 14 | -import java.net.URLEncoder;
|
|
| 15 | -import java.util.ArrayList;
|
|
| 16 | -import java.util.Arrays;
|
|
| 17 | -import java.util.Collection;
|
|
| 18 | -import java.util.Collections;
|
|
| 19 | -import java.util.Comparator;
|
|
| 20 | -import java.util.HashMap;
|
|
| 21 | -import java.util.HashSet;
|
|
| 22 | -import java.util.Iterator;
|
|
| 23 | -import java.util.LinkedHashMap;
|
|
| 24 | -import java.util.List;
|
|
| 25 | -import java.util.Map;
|
|
| 26 | -import java.util.Map.Entry;
|
|
| 27 | -import java.util.Set;
|
|
| 28 | -import java.util.UUID;
|
|
| 29 | -import java.util.concurrent.ConcurrentHashMap;
|
|
| 30 | -import java.util.concurrent.Executor;
|
|
| 31 | -import java.util.concurrent.Executors;
|
|
| 32 | -import java.util.concurrent.LinkedBlockingQueue;
|
|
| 33 | -import java.util.concurrent.ScheduledExecutorService;
|
|
| 34 | -import java.util.concurrent.ScheduledFuture;
|
|
| 35 | -import java.util.concurrent.ThreadPoolExecutor;
|
|
| 36 | -import java.util.concurrent.TimeUnit;
|
|
| 37 | -import java.util.function.Function;
|
|
| 38 | -import java.util.logging.Level;
|
|
| 39 | -import java.util.logging.Logger;
|
|
| 40 | -
|
|
| 41 | -import org.json.simple.JSONArray;
|
|
| 42 | -import org.json.simple.JSONObject;
|
|
| 43 | -import org.json.simple.parser.JSONParser;
|
|
| 44 | -import org.json.simple.parser.ParseException;
|
|
| 45 | -import org.osgi.framework.BundleContext;
|
|
| 46 | -import org.osgi.framework.ServiceReference;
|
|
| 47 | -import org.osgi.util.tracker.ServiceTracker;
|
|
| 48 | -
|
|
| 49 | -import com.sap.sailing.domain.abstractlog.AbstractLog;
|
|
| 50 | -import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
|
|
| 51 | -import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
|
|
| 52 | -import com.sap.sailing.domain.abstractlog.race.RaceLog;
|
|
| 53 | -import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
|
|
| 54 | -import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor;
|
|
| 55 | -import com.sap.sailing.domain.abstractlog.race.SimpleRaceLogIdentifier;
|
|
| 56 | -import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
|
|
| 57 | -import com.sap.sailing.domain.abstractlog.race.state.RaceState;
|
|
| 58 | -import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState;
|
|
| 59 | -import com.sap.sailing.domain.abstractlog.race.state.impl.RaceStateImpl;
|
|
| 60 | -import com.sap.sailing.domain.abstractlog.race.state.impl.ReadonlyRaceStateImpl;
|
|
| 61 | -import com.sap.sailing.domain.base.Competitor;
|
|
| 62 | -import com.sap.sailing.domain.base.CompetitorStore;
|
|
| 63 | -import com.sap.sailing.domain.base.CompetitorStore.CompetitorUpdateListener;
|
|
| 64 | -import com.sap.sailing.domain.base.ControlPoint;
|
|
| 65 | -import com.sap.sailing.domain.base.CourseArea;
|
|
| 66 | -import com.sap.sailing.domain.base.DomainFactory;
|
|
| 67 | -import com.sap.sailing.domain.base.Event;
|
|
| 68 | -import com.sap.sailing.domain.base.EventBase;
|
|
| 69 | -import com.sap.sailing.domain.base.EventFetcher;
|
|
| 70 | -import com.sap.sailing.domain.base.Fleet;
|
|
| 71 | -import com.sap.sailing.domain.base.LeaderboardSearchResult;
|
|
| 72 | -import com.sap.sailing.domain.base.LeaderboardSearchResultBase;
|
|
| 73 | -import com.sap.sailing.domain.base.Mark;
|
|
| 74 | -import com.sap.sailing.domain.base.RaceColumn;
|
|
| 75 | -import com.sap.sailing.domain.base.RaceColumnInSeries;
|
|
| 76 | -import com.sap.sailing.domain.base.RaceDefinition;
|
|
| 77 | -import com.sap.sailing.domain.base.Regatta;
|
|
| 78 | -import com.sap.sailing.domain.base.RegattaListener;
|
|
| 79 | -import com.sap.sailing.domain.base.RemoteSailingServerReference;
|
|
| 80 | -import com.sap.sailing.domain.base.SailingServerConfiguration;
|
|
| 81 | -import com.sap.sailing.domain.base.Series;
|
|
| 82 | -import com.sap.sailing.domain.base.Sideline;
|
|
| 83 | -import com.sap.sailing.domain.base.Waypoint;
|
|
| 84 | -import com.sap.sailing.domain.base.configuration.DeviceConfiguration;
|
|
| 85 | -import com.sap.sailing.domain.base.configuration.DeviceConfigurationIdentifier;
|
|
| 86 | -import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher;
|
|
| 87 | -import com.sap.sailing.domain.base.configuration.RegattaConfiguration;
|
|
| 88 | -import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationMapImpl;
|
|
| 89 | -import com.sap.sailing.domain.base.impl.DynamicCompetitor;
|
|
| 90 | -import com.sap.sailing.domain.base.impl.EventImpl;
|
|
| 91 | -import com.sap.sailing.domain.base.impl.RegattaImpl;
|
|
| 92 | -import com.sap.sailing.domain.base.impl.RemoteSailingServerReferenceImpl;
|
|
| 93 | -import com.sap.sailing.domain.common.DataImportProgress;
|
|
| 94 | -import com.sap.sailing.domain.common.Distance;
|
|
| 95 | -import com.sap.sailing.domain.common.Position;
|
|
| 96 | -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
|
| 97 | -import com.sap.sailing.domain.common.RegattaIdentifier;
|
|
| 98 | -import com.sap.sailing.domain.common.RegattaName;
|
|
| 99 | -import com.sap.sailing.domain.common.Renamable;
|
|
| 100 | -import com.sap.sailing.domain.common.ScoringSchemeType;
|
|
| 101 | -import com.sap.sailing.domain.common.TrackedRaceStatusEnum;
|
|
| 102 | -import com.sap.sailing.domain.common.Wind;
|
|
| 103 | -import com.sap.sailing.domain.common.WindSource;
|
|
| 104 | -import com.sap.sailing.domain.common.dto.FleetDTO;
|
|
| 105 | -import com.sap.sailing.domain.common.dto.RegattaCreationParametersDTO;
|
|
| 106 | -import com.sap.sailing.domain.common.dto.SeriesCreationParametersDTO;
|
|
| 107 | -import com.sap.sailing.domain.common.impl.DataImportProgressImpl;
|
|
| 108 | -import com.sap.sailing.domain.common.media.MediaTrack;
|
|
| 109 | -import com.sap.sailing.domain.common.racelog.RacingProcedureType;
|
|
| 110 | -import com.sap.sailing.domain.common.tracking.GPSFix;
|
|
| 111 | -import com.sap.sailing.domain.common.tracking.GPSFixMoving;
|
|
| 112 | -import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
|
|
| 113 | -import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn;
|
|
| 114 | -import com.sap.sailing.domain.leaderboard.Leaderboard;
|
|
| 115 | -import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
|
|
| 116 | -import com.sap.sailing.domain.leaderboard.LeaderboardRegistry;
|
|
| 117 | -import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
|
|
| 118 | -import com.sap.sailing.domain.leaderboard.ScoringScheme;
|
|
| 119 | -import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl;
|
|
| 120 | -import com.sap.sailing.domain.leaderboard.impl.LeaderboardGroupImpl;
|
|
| 121 | -import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl;
|
|
| 122 | -import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl;
|
|
| 123 | -import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard;
|
|
| 124 | -import com.sap.sailing.domain.persistence.DomainObjectFactory;
|
|
| 125 | -import com.sap.sailing.domain.persistence.MongoObjectFactory;
|
|
| 126 | -import com.sap.sailing.domain.persistence.MongoRaceLogStoreFactory;
|
|
| 127 | -import com.sap.sailing.domain.persistence.MongoRegattaLogStoreFactory;
|
|
| 128 | -import com.sap.sailing.domain.persistence.MongoWindStore;
|
|
| 129 | -import com.sap.sailing.domain.persistence.MongoWindStoreFactory;
|
|
| 130 | -import com.sap.sailing.domain.persistence.PersistenceFactory;
|
|
| 131 | -import com.sap.sailing.domain.persistence.media.MediaDB;
|
|
| 132 | -import com.sap.sailing.domain.persistence.media.MediaDBFactory;
|
|
| 133 | -import com.sap.sailing.domain.persistence.racelog.tracking.MongoGPSFixStoreFactory;
|
|
| 134 | -import com.sap.sailing.domain.polars.PolarDataService;
|
|
| 135 | -import com.sap.sailing.domain.racelog.RaceLogIdentifier;
|
|
| 136 | -import com.sap.sailing.domain.racelog.RaceLogStore;
|
|
| 137 | -import com.sap.sailing.domain.racelog.tracking.GPSFixStore;
|
|
| 138 | -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
|
|
| 139 | -import com.sap.sailing.domain.ranking.RankingMetric.CompetitorRankingInfo;
|
|
| 140 | -import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo;
|
|
| 141 | -import com.sap.sailing.domain.ranking.RankingMetricConstructor;
|
|
| 142 | -import com.sap.sailing.domain.regattalike.HasRegattaLike;
|
|
| 143 | -import com.sap.sailing.domain.regattalike.IsRegattaLike;
|
|
| 144 | -import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike;
|
|
| 145 | -import com.sap.sailing.domain.regattalog.RegattaLogStore;
|
|
| 146 | -import com.sap.sailing.domain.tracking.DynamicTrackedRace;
|
|
| 147 | -import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
|
|
| 148 | -import com.sap.sailing.domain.tracking.GPSFixTrack;
|
|
| 149 | -import com.sap.sailing.domain.tracking.MarkPassing;
|
|
| 150 | -import com.sap.sailing.domain.tracking.RaceChangeListener;
|
|
| 151 | -import com.sap.sailing.domain.tracking.RaceHandle;
|
|
| 152 | -import com.sap.sailing.domain.tracking.RaceListener;
|
|
| 153 | -import com.sap.sailing.domain.tracking.RaceTracker;
|
|
| 154 | -import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
|
|
| 155 | -import com.sap.sailing.domain.tracking.TrackedRace;
|
|
| 156 | -import com.sap.sailing.domain.tracking.TrackedRaceStatus;
|
|
| 157 | -import com.sap.sailing.domain.tracking.TrackedRegatta;
|
|
| 158 | -import com.sap.sailing.domain.tracking.WindStore;
|
|
| 159 | -import com.sap.sailing.domain.tracking.WindTracker;
|
|
| 160 | -import com.sap.sailing.domain.tracking.WindTrackerFactory;
|
|
| 161 | -import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
|
|
| 162 | -import com.sap.sailing.domain.tracking.impl.DynamicGPSFixTrackImpl;
|
|
| 163 | -import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl;
|
|
| 164 | -import com.sap.sailing.domain.tracking.impl.TrackedRaceImpl;
|
|
| 165 | -import com.sap.sailing.expeditionconnector.ExpeditionWindTrackerFactory;
|
|
| 166 | -import com.sap.sailing.server.RacingEventService;
|
|
| 167 | -import com.sap.sailing.server.Replicator;
|
|
| 168 | -import com.sap.sailing.server.gateway.deserialization.impl.CourseAreaJsonDeserializer;
|
|
| 169 | -import com.sap.sailing.server.gateway.deserialization.impl.EventBaseJsonDeserializer;
|
|
| 170 | -import com.sap.sailing.server.gateway.deserialization.impl.LeaderboardGroupBaseJsonDeserializer;
|
|
| 171 | -import com.sap.sailing.server.gateway.deserialization.impl.LeaderboardSearchResultBaseJsonDeserializer;
|
|
| 172 | -import com.sap.sailing.server.gateway.deserialization.impl.VenueJsonDeserializer;
|
|
| 173 | -import com.sap.sailing.server.masterdata.DataImportLockWithProgress;
|
|
| 174 | -import com.sap.sailing.server.operationaltransformation.AddCourseAreas;
|
|
| 175 | -import com.sap.sailing.server.operationaltransformation.AddDefaultRegatta;
|
|
| 176 | -import com.sap.sailing.server.operationaltransformation.AddMediaTrackOperation;
|
|
| 177 | -import com.sap.sailing.server.operationaltransformation.AddRaceDefinition;
|
|
| 178 | -import com.sap.sailing.server.operationaltransformation.AddSpecificRegatta;
|
|
| 179 | -import com.sap.sailing.server.operationaltransformation.ConnectTrackedRaceToLeaderboardColumn;
|
|
| 180 | -import com.sap.sailing.server.operationaltransformation.CreateEvent;
|
|
| 181 | -import com.sap.sailing.server.operationaltransformation.CreateOrUpdateDataImportProgress;
|
|
| 182 | -import com.sap.sailing.server.operationaltransformation.CreateOrUpdateDeviceConfiguration;
|
|
| 183 | -import com.sap.sailing.server.operationaltransformation.CreateTrackedRace;
|
|
| 184 | -import com.sap.sailing.server.operationaltransformation.DataImportFailed;
|
|
| 185 | -import com.sap.sailing.server.operationaltransformation.RecordCompetitorGPSFix;
|
|
| 186 | -import com.sap.sailing.server.operationaltransformation.RecordMarkGPSFix;
|
|
| 187 | -import com.sap.sailing.server.operationaltransformation.RecordMarkGPSFixForExistingTrack;
|
|
| 188 | -import com.sap.sailing.server.operationaltransformation.RecordMarkGPSFixForNewMarkTrack;
|
|
| 189 | -import com.sap.sailing.server.operationaltransformation.RecordWindFix;
|
|
| 190 | -import com.sap.sailing.server.operationaltransformation.RemoveDeviceConfiguration;
|
|
| 191 | -import com.sap.sailing.server.operationaltransformation.RemoveEvent;
|
|
| 192 | -import com.sap.sailing.server.operationaltransformation.RemoveMediaTrackOperation;
|
|
| 193 | -import com.sap.sailing.server.operationaltransformation.RemoveWindFix;
|
|
| 194 | -import com.sap.sailing.server.operationaltransformation.RenameEvent;
|
|
| 195 | -import com.sap.sailing.server.operationaltransformation.SetDataImportDeleteProgressFromMapTimer;
|
|
| 196 | -import com.sap.sailing.server.operationaltransformation.TrackRegatta;
|
|
| 197 | -import com.sap.sailing.server.operationaltransformation.UpdateCompetitor;
|
|
| 198 | -import com.sap.sailing.server.operationaltransformation.UpdateEndOfTracking;
|
|
| 199 | -import com.sap.sailing.server.operationaltransformation.UpdateMarkPassings;
|
|
| 200 | -import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackDurationOperation;
|
|
| 201 | -import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackRacesOperation;
|
|
| 202 | -import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackStartTimeOperation;
|
|
| 203 | -import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackTitleOperation;
|
|
| 204 | -import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackUrlOperation;
|
|
| 205 | -import com.sap.sailing.server.operationaltransformation.UpdateRaceDelayToLive;
|
|
| 206 | -import com.sap.sailing.server.operationaltransformation.UpdateStartOfTracking;
|
|
| 207 | -import com.sap.sailing.server.operationaltransformation.UpdateStartTimeReceived;
|
|
| 208 | -import com.sap.sailing.server.operationaltransformation.UpdateTrackedRaceStatus;
|
|
| 209 | -import com.sap.sailing.server.operationaltransformation.UpdateWindAveragingTime;
|
|
| 210 | -import com.sap.sailing.server.operationaltransformation.UpdateWindSourcesToExclude;
|
|
| 211 | -import com.sap.sailing.server.simulation.SimulationService;
|
|
| 212 | -import com.sap.sailing.server.simulation.SimulationServiceFactory;
|
|
| 213 | -import com.sap.sse.ServerInfo;
|
|
| 214 | -import com.sap.sse.common.TimePoint;
|
|
| 215 | -import com.sap.sse.common.TypeBasedServiceFinderFactory;
|
|
| 216 | -import com.sap.sse.common.Util;
|
|
| 217 | -import com.sap.sse.common.Util.Pair;
|
|
| 218 | -import com.sap.sse.common.Util.Triple;
|
|
| 219 | -import com.sap.sse.common.impl.MillisecondsTimePoint;
|
|
| 220 | -import com.sap.sse.common.media.ImageDescriptor;
|
|
| 221 | -import com.sap.sse.common.media.VideoDescriptor;
|
|
| 222 | -import com.sap.sse.common.search.KeywordQuery;
|
|
| 223 | -import com.sap.sse.common.search.Result;
|
|
| 224 | -import com.sap.sse.common.search.ResultImpl;
|
|
| 225 | -import com.sap.sse.concurrent.LockUtil;
|
|
| 226 | -import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
|
|
| 227 | -import com.sap.sse.filestorage.FileStorageManagementService;
|
|
| 228 | -import com.sap.sse.replication.OperationExecutionListener;
|
|
| 229 | -import com.sap.sse.replication.OperationWithResult;
|
|
| 230 | -import com.sap.sse.replication.ReplicationMasterDescriptor;
|
|
| 231 | -import com.sap.sse.replication.impl.OperationWithResultWithIdWrapper;
|
|
| 232 | -import com.sap.sse.util.ClearStateTestSupport;
|
|
| 233 | -import com.sap.sse.util.JoinedClassLoader;
|
|
| 234 | -import com.sap.sse.util.impl.ThreadFactoryWithPriority;
|
|
| 235 | -
|
|
| 236 | -public class RacingEventServiceImpl implements RacingEventService, ClearStateTestSupport, RegattaListener,
|
|
| 237 | - LeaderboardRegistry, Replicator, EventFetcher {
|
|
| 238 | - private static final Logger logger = Logger.getLogger(RacingEventServiceImpl.class.getName());
|
|
| 239 | -
|
|
| 240 | - /**
|
|
| 241 | - * A scheduler for the periodic checks of the paramURL documents for the advent of {@link ControlPoint}s with static
|
|
| 242 | - * position information otherwise not available through <code>MarkPassingReceiver</code>'s events.
|
|
| 243 | - */
|
|
| 244 | - private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, new ThreadFactoryWithPriority(Thread.NORM_PRIORITY, /* daemon */ true));
|
|
| 245 | -
|
|
| 246 | - private final com.sap.sailing.domain.base.DomainFactory baseDomainFactory;
|
|
| 247 | -
|
|
| 248 | - /**
|
|
| 249 | - * Holds the {@link Event} objects for those event registered with this service. Note that there may be
|
|
| 250 | - * {@link Event} objects that exist outside this service for events not (yet) registered here.
|
|
| 251 | - */
|
|
| 252 | - private final ConcurrentHashMap<Serializable, Event> eventsById;
|
|
| 253 | -
|
|
| 254 | - private final RemoteSailingServerSet remoteSailingServerSet;
|
|
| 255 | -
|
|
| 256 | - /**
|
|
| 257 | - * Holds the {@link Regatta} objects for those races registered with this service. Note that there may be
|
|
| 258 | - * {@link Regatta} objects that exist outside this service for regattas not (yet) registered here.
|
|
| 259 | - */
|
|
| 260 | - protected final ConcurrentHashMap<String, Regatta> regattasByName;
|
|
| 261 | -
|
|
| 262 | - private final NamedReentrantReadWriteLock regattasByNameLock;
|
|
| 263 | -
|
|
| 264 | - private final ConcurrentHashMap<RaceDefinition, CourseChangeReplicator> courseListeners;
|
|
| 265 | -
|
|
| 266 | - protected final ConcurrentHashMap<Regatta, Set<RaceTracker>> raceTrackersByRegatta;
|
|
| 267 | -
|
|
| 268 | - /**
|
|
| 269 | - * Although {@link #raceTrackersByRegatta} is a concurrent hash map, entering sets as values needs to be
|
|
| 270 | - * synchronized using this lock's write lock to avoid two value sets overwriting each other.
|
|
| 271 | - */
|
|
| 272 | - private final NamedReentrantReadWriteLock raceTrackersByRegattaLock;
|
|
| 273 | -
|
|
| 274 | - /**
|
|
| 275 | - * Remembers the trackers by paramURL/liveURI/storedURI to avoid duplication
|
|
| 276 | - */
|
|
| 277 | - protected final ConcurrentHashMap<Object, RaceTracker> raceTrackersByID;
|
|
| 278 | -
|
|
| 279 | - /**
|
|
| 280 | - * {@link #addRace(RegattaIdentifier, RaceTrackingConnectivityParameters, long)} will check
|
|
| 281 | - * {@link #raceTrackersByID} for the presence of a tracker and won't create a new tracker if one for the
|
|
| 282 | - * connectivity parameters' ID already exists. This check and creation and addition to {@link #raceTrackersByID}
|
|
| 283 | - * requires locking in the face of concurrent calls to
|
|
| 284 | - * {@link #addRace(RegattaIdentifier, RaceTrackingConnectivityParameters, long)}. Using <code>synchronized</code> is
|
|
| 285 | - * not ideal due to its coarse-grained locking style which allows for little concurrency. Instead, this map is used
|
|
| 286 | - * to keep locks for any ID that any invocation of the
|
|
| 287 | - * {@link #addRace(RegattaIdentifier, RaceTrackingConnectivityParameters, long)} method is currently working on.
|
|
| 288 | - * Fetching or creating and putting a lock to this map happens in ({@link #getOrCreateRaceTrackersByIdLock}) which
|
|
| 289 | - * takes care of managing concurrent access to this concurrent map. When done, the
|
|
| 290 | - * {@link #addRace(RegattaIdentifier, RaceTrackingConnectivityParameters, long)} method cleans up by removing the
|
|
| 291 | - * lock again from this map, again using a synchronized method (
|
|
| 292 | - * {@link #unlockRaceTrackersById(Object, NamedReentrantReadWriteLock)}).
|
|
| 293 | - */
|
|
| 294 | - private final ConcurrentHashMap<Object, NamedReentrantReadWriteLock> raceTrackersByIDLocks;
|
|
| 295 | -
|
|
| 296 | - /**
|
|
| 297 | - * Leaderboards managed by this racing event service
|
|
| 298 | - */
|
|
| 299 | - private final ConcurrentHashMap<String, Leaderboard> leaderboardsByName;
|
|
| 300 | -
|
|
| 301 | - /**
|
|
| 302 | - * {@link #leaderboardsByName} is already a concurrent hash map; however, when renaming a leaderboard, this shall
|
|
| 303 | - * happen as an atomic transaction, not interruptible by other write accesses on the same map because otherwise
|
|
| 304 | - * assumptions made during the rename process wouldn't hold. See, in particular,
|
|
| 305 | - * {@link #renameLeaderboard(String, String)}.
|
|
| 306 | - */
|
|
| 307 | - private final NamedReentrantReadWriteLock leaderboardsByNameLock;
|
|
| 308 | -
|
|
| 309 | - private final ConcurrentHashMap<String, LeaderboardGroup> leaderboardGroupsByName;
|
|
| 310 | -
|
|
| 311 | - private final ConcurrentHashMap<UUID, LeaderboardGroup> leaderboardGroupsByID;
|
|
| 312 | -
|
|
| 313 | - /**
|
|
| 314 | - * See {@link #leaderboardsByNameLock}
|
|
| 315 | - */
|
|
| 316 | - private final NamedReentrantReadWriteLock leaderboardGroupsByNameLock;
|
|
| 317 | -
|
|
| 318 | - private final CompetitorStore competitorStore;
|
|
| 319 | -
|
|
| 320 | - /**
|
|
| 321 | - * A set based on a concurrent hash map, therefore being thread safe
|
|
| 322 | - */
|
|
| 323 | - private Set<DynamicTrackedRegatta> regattasObservedForDefaultLeaderboard = Collections
|
|
| 324 | - .newSetFromMap(new ConcurrentHashMap<DynamicTrackedRegatta, Boolean>());
|
|
| 325 | -
|
|
| 326 | - private final MongoObjectFactory mongoObjectFactory;
|
|
| 327 | -
|
|
| 328 | - private final DomainObjectFactory domainObjectFactory;
|
|
| 329 | -
|
|
| 330 | - private final ConcurrentHashMap<Regatta, DynamicTrackedRegatta> regattaTrackingCache;
|
|
| 331 | -
|
|
| 332 | - /**
|
|
| 333 | - * Protects write access transactions that do a previous read to {@link #regattaTrackingCache}; read-only access is
|
|
| 334 | - * already synchronized by using a concurrent hash map for {@link #regattaTrackingCache}.
|
|
| 335 | - */
|
|
| 336 | - private final NamedReentrantReadWriteLock regattaTrackingCacheLock;
|
|
| 337 | -
|
|
| 338 | - private final ConcurrentHashMap<OperationExecutionListener<RacingEventService>, OperationExecutionListener<RacingEventService>> operationExecutionListeners;
|
|
| 339 | -
|
|
| 340 | - /**
|
|
| 341 | - * Keys are the toString() representation of the {@link RaceDefinition#getId() IDs} of races passed to
|
|
| 342 | - * {@link #setRegattaForRace(Regatta, RaceDefinition)}.
|
|
| 343 | - */
|
|
| 344 | - private final ConcurrentHashMap<String, Regatta> persistentRegattasForRaceIDs;
|
|
| 345 | -
|
|
| 346 | - private final RaceLogReplicator raceLogReplicator;
|
|
| 347 | - private final RegattaLogReplicator regattaLogReplicator;
|
|
| 348 | -
|
|
| 349 | - private final RaceLogScoringReplicator raceLogScoringReplicator;
|
|
| 350 | -
|
|
| 351 | - private final MediaDB mediaDB;
|
|
| 352 | -
|
|
| 353 | - private final MediaLibrary mediaLibrary;
|
|
| 354 | -
|
|
| 355 | - /**
|
|
| 356 | - * Currently valid pairs of {@link DeviceConfigurationMatcher}s and {@link DeviceConfiguration}s. The contents of
|
|
| 357 | - * this map is persisted and replicated. See {@link DeviceConfigurationMapImpl}.
|
|
| 358 | - */
|
|
| 359 | - protected final DeviceConfigurationMapImpl configurationMap;
|
|
| 360 | -
|
|
| 361 | - private final WindStore windStore;
|
|
| 362 | - private final GPSFixStore gpsFixStore;
|
|
| 363 | -
|
|
| 364 | - /**
|
|
| 365 | - * This author should be used for server generated race log events
|
|
| 366 | - */
|
|
| 367 | - private final AbstractLogEventAuthor raceLogEventAuthorForServer = new LogEventAuthorImpl(
|
|
| 368 | - RacingEventService.class.getName(), 0);
|
|
| 369 | -
|
|
| 370 | - private PolarDataService polarDataService;
|
|
| 371 | -
|
|
| 372 | - private final SimulationService simulationService;
|
|
| 373 | -
|
|
| 374 | - /**
|
|
| 375 | - * Allow only one master data import at a time to avoid situation where multiple Imports override each other in
|
|
| 376 | - * unpredictable fashion
|
|
| 377 | - */
|
|
| 378 | - private final DataImportLockWithProgress dataImportLock;
|
|
| 379 | -
|
|
| 380 | - /**
|
|
| 381 | - * If this service runs in the context of an OSGi environment, the activator should {@link #setBundleContext set the
|
|
| 382 | - * bundle context} on this object so that service lookups become possible.
|
|
| 383 | - */
|
|
| 384 | - private BundleContext bundleContext;
|
|
| 385 | -
|
|
| 386 | - private TypeBasedServiceFinderFactory serviceFinderFactory;
|
|
| 387 | -
|
|
| 388 | - /**
|
|
| 389 | - * The master from which this replicable is currently replicating, or <code>null</code> if this replicable is not
|
|
| 390 | - * currently replicated from any master.
|
|
| 391 | - */
|
|
| 392 | - private ReplicationMasterDescriptor replicatingFromMaster;
|
|
| 393 | -
|
|
| 394 | - private Set<OperationWithResultWithIdWrapper<?, ?>> operationsSentToMasterForReplication;
|
|
| 395 | -
|
|
| 396 | - private ThreadLocal<Boolean> currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster = ThreadLocal
|
|
| 397 | - .withInitial(() -> false);
|
|
| 398 | -
|
|
| 399 | - private final Set<ClassLoader> masterDataClassLoaders = new HashSet<ClassLoader>();
|
|
| 400 | -
|
|
| 401 | - private final JoinedClassLoader joinedClassLoader;
|
|
| 402 | -
|
|
| 403 | - private SailingServerConfiguration sailingServerConfiguration;
|
|
| 404 | -
|
|
| 405 | - /**
|
|
| 406 | - * Providing the constructor parameters for a new {@link RacingEventServiceImpl} instance is a bit tricky
|
|
| 407 | - * in some cases because containment and initialization order of some types is fairly tightly coupled.
|
|
| 408 | - * There is a dependency of many such objects on an instance of {@link RaceLogResolver} which is implemented
|
|
| 409 | - * by {@link RacingEventServiceImpl}. However, therefore, this instance only becomes available in the
|
|
| 410 | - * innermost constructor.
|
|
| 411 | - *
|
|
| 412 | - * @author Axel Uhl (d043530)
|
|
| 413 | - *
|
|
| 414 | - */
|
|
| 415 | - public static interface ConstructorParameters {
|
|
| 416 | - DomainObjectFactory getDomainObjectFactory();
|
|
| 417 | - MongoObjectFactory getMongoObjectFactory();
|
|
| 418 | - com.sap.sailing.domain.base.DomainFactory getBaseDomainFactory();
|
|
| 419 | - CompetitorStore getCompetitorStore();
|
|
| 420 | - }
|
|
| 421 | -
|
|
| 422 | - /**
|
|
| 423 | - * Constructs a {@link DomainFactory base domain factory} that uses this object's {@link #competitorStore competitor
|
|
| 424 | - * store} for competitor management. This base domain factory is then also used for the construction of the
|
|
| 425 | - * {@link DomainObjectFactory}. This constructor variant initially clears the persistent competitor collection,
|
|
| 426 | - * hence removes all previously persistent competitors. This is the default for testing and for backward
|
|
| 427 | - * compatibility with prior releases that did not support a persistent competitor collection.
|
|
| 428 | - */
|
|
| 429 | - public RacingEventServiceImpl() {
|
|
| 430 | - this(true, null);
|
|
| 431 | - }
|
|
| 432 | -
|
|
| 433 | - public RacingEventServiceImpl(WindStore windStore, GPSFixStore gpsFixStore,
|
|
| 434 | - TypeBasedServiceFinderFactory serviceFinderFactory) {
|
|
| 435 | - this(true, windStore, gpsFixStore, serviceFinderFactory);
|
|
| 436 | - }
|
|
| 437 | -
|
|
| 438 | - void setBundleContext(BundleContext bundleContext) {
|
|
| 439 | - this.bundleContext = bundleContext;
|
|
| 440 | - }
|
|
| 441 | -
|
|
| 442 | - /**
|
|
| 443 | - * Like {@link #RacingEventServiceImpl()}, but allows callers to specify that the persistent competitor collection
|
|
| 444 | - * be cleared before the service starts.
|
|
| 445 | - *
|
|
| 446 | - * @param clearPersistentCompetitorStore
|
|
| 447 | - * if <code>true</code>, the {@link PersistentCompetitorStore} is created empty, with the corresponding
|
|
| 448 | - * database collection cleared as well. Use with caution! When used with <code>false</code>, competitors
|
|
| 449 | - * created and stored during previous service executions will initially be loaded.
|
|
| 450 | - */
|
|
| 451 | - public RacingEventServiceImpl(boolean clearPersistentCompetitorStore, final TypeBasedServiceFinderFactory serviceFinderFactory) {
|
|
| 452 | - this((final RaceLogResolver raceLogResolver)-> {
|
|
| 453 | - return new ConstructorParameters() {
|
|
| 454 | - private final MongoObjectFactory mongoObjectFactory = PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(serviceFinderFactory);
|
|
| 455 | - private final PersistentCompetitorStore competitorStore = new PersistentCompetitorStore(
|
|
| 456 | - PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(serviceFinderFactory),
|
|
| 457 | - clearPersistentCompetitorStore, serviceFinderFactory, raceLogResolver);
|
|
| 458 | -
|
|
| 459 | - @Override public DomainObjectFactory getDomainObjectFactory() { return competitorStore.getDomainObjectFactory(); }
|
|
| 460 | - @Override public MongoObjectFactory getMongoObjectFactory() { return mongoObjectFactory; }
|
|
| 461 | - @Override public DomainFactory getBaseDomainFactory() { return competitorStore.getBaseDomainFactory(); }
|
|
| 462 | - @Override public CompetitorStore getCompetitorStore() { return competitorStore; }
|
|
| 463 | - };
|
|
| 464 | - }, MediaDBFactory.INSTANCE.getDefaultMediaDB(), null, null, serviceFinderFactory);
|
|
| 465 | - }
|
|
| 466 | -
|
|
| 467 | - private RacingEventServiceImpl(final boolean clearPersistentCompetitorStore, WindStore windStore,
|
|
| 468 | - GPSFixStore gpsFixStore, final TypeBasedServiceFinderFactory serviceFinderFactory) {
|
|
| 469 | - this((final RaceLogResolver raceLogResolver)-> {
|
|
| 470 | - return new ConstructorParameters() {
|
|
| 471 | - private final MongoObjectFactory mongoObjectFactory = PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(serviceFinderFactory);
|
|
| 472 | - private final PersistentCompetitorStore competitorStore = new PersistentCompetitorStore(
|
|
| 473 | - mongoObjectFactory,
|
|
| 474 | - clearPersistentCompetitorStore, serviceFinderFactory, raceLogResolver);
|
|
| 475 | -
|
|
| 476 | - @Override public DomainObjectFactory getDomainObjectFactory() { return competitorStore.getDomainObjectFactory(); }
|
|
| 477 | - @Override public MongoObjectFactory getMongoObjectFactory() { return mongoObjectFactory; }
|
|
| 478 | - @Override public DomainFactory getBaseDomainFactory() { return competitorStore.getBaseDomainFactory(); }
|
|
| 479 | - @Override public CompetitorStore getCompetitorStore() { return competitorStore; }
|
|
| 480 | - };
|
|
| 481 | - }, MediaDBFactory.INSTANCE.getDefaultMediaDB(), windStore, gpsFixStore, serviceFinderFactory);
|
|
| 482 | - }
|
|
| 483 | -
|
|
| 484 | - /**
|
|
| 485 | - * Uses the default factories for the tracking adapters and the {@link DomainFactory base domain factory} of the
|
|
| 486 | - * {@link PersistenceFactory#getDefaultDomainObjectFactory() default domain object factory}. This constructor should
|
|
| 487 | - * be used for testing because it provides a transient {@link CompetitorStore} as required for competitor
|
|
| 488 | - * persistence.
|
|
| 489 | - */
|
|
| 490 | - public RacingEventServiceImpl(Function<RaceLogResolver, DomainObjectFactory> domainObjectFactoryProvider,
|
|
| 491 | - final MongoObjectFactory mongoObjectFactory, MediaDB mediaDB, WindStore windStore, GPSFixStore gpsFixStore) {
|
|
| 492 | - this((final RaceLogResolver raceLogResolver)-> {
|
|
| 493 | - return new ConstructorParameters() {
|
|
| 494 | - private final DomainObjectFactory domainObjectFactory = domainObjectFactoryProvider.apply(raceLogResolver);
|
|
| 495 | -
|
|
| 496 | - @Override public DomainObjectFactory getDomainObjectFactory() { return domainObjectFactory; }
|
|
| 497 | - @Override public MongoObjectFactory getMongoObjectFactory() { return mongoObjectFactory; }
|
|
| 498 | - @Override public DomainFactory getBaseDomainFactory() { return domainObjectFactory.getBaseDomainFactory(); }
|
|
| 499 | - @Override public CompetitorStore getCompetitorStore() { return getBaseDomainFactory().getCompetitorStore(); }
|
|
| 500 | - };
|
|
| 501 | - }, mediaDB, windStore, gpsFixStore, null);
|
|
| 502 | - }
|
|
| 503 | -
|
|
| 504 | - public RacingEventServiceImpl(final DomainObjectFactory domainObjectFactory, MongoObjectFactory mongoObjectFactory,
|
|
| 505 | - MediaDB mediaDB, WindStore windStore, GPSFixStore gpsFixStore) {
|
|
| 506 | - this((final RaceLogResolver raceLogResolver)-> {
|
|
| 507 | - return new ConstructorParameters() {
|
|
| 508 | - @Override public DomainObjectFactory getDomainObjectFactory() { return domainObjectFactory; }
|
|
| 509 | - @Override public MongoObjectFactory getMongoObjectFactory() { return mongoObjectFactory; }
|
|
| 510 | - @Override public DomainFactory getBaseDomainFactory() { return domainObjectFactory.getBaseDomainFactory(); }
|
|
| 511 | - @Override public CompetitorStore getCompetitorStore() { return getBaseDomainFactory().getCompetitorStore(); }
|
|
| 512 | - };
|
|
| 513 | - }, mediaDB, windStore, gpsFixStore, null);
|
|
| 514 | - }
|
|
| 515 | -
|
|
| 516 | - /**
|
|
| 517 | - * @param windStore
|
|
| 518 | - * if <code>null</code>, a default {@link MongoWindStore} will be used, based on the persistence set-up
|
|
| 519 | - * of this service
|
|
| 520 | - * @param serviceFinderFactory
|
|
| 521 | - * used to find the services handling specific types of tracking devices, such as the persistent storage
|
|
| 522 | - * of {@link DeviceIdentifier}s of specific device types or the managing of the device-to-competitor
|
|
| 523 | - * associations per race tracked.
|
|
| 524 | - */
|
|
| 525 | - public RacingEventServiceImpl(Function<RaceLogResolver, ConstructorParameters> constructorParametersProvider, MediaDB mediaDb,
|
|
| 526 | - WindStore windStore, GPSFixStore gpsFixStore, TypeBasedServiceFinderFactory serviceFinderFactory) {
|
|
| 527 | - logger.info("Created " + this);
|
|
| 528 | - final ConstructorParameters constructorParameters = constructorParametersProvider.apply(this);
|
|
| 529 | - this.domainObjectFactory = constructorParameters.getDomainObjectFactory();
|
|
| 530 | - this.masterDataClassLoaders.add(this.getClass().getClassLoader());
|
|
| 531 | - joinedClassLoader = new JoinedClassLoader(masterDataClassLoaders);
|
|
| 532 | - this.operationsSentToMasterForReplication = new HashSet<>();
|
|
| 533 | - this.baseDomainFactory = constructorParameters.getBaseDomainFactory();
|
|
| 534 | - this.mongoObjectFactory = constructorParameters.getMongoObjectFactory();
|
|
| 535 | - this.mediaDB = mediaDb;
|
|
| 536 | - this.competitorStore = constructorParameters.getCompetitorStore();
|
|
| 537 | - if (windStore == null) {
|
|
| 538 | - try {
|
|
| 539 | - windStore = MongoWindStoreFactory.INSTANCE.getMongoWindStore(mongoObjectFactory, domainObjectFactory);
|
|
| 540 | - } catch (Exception e) {
|
|
| 541 | - throw new RuntimeException(e);
|
|
| 542 | - }
|
|
| 543 | - }
|
|
| 544 | - this.competitorStore.addCompetitorUpdateListener(new CompetitorUpdateListener() {
|
|
| 545 | - @Override
|
|
| 546 | - public void competitorUpdated(Competitor competitor) {
|
|
| 547 | - replicate(new UpdateCompetitor(competitor.getId().toString(), competitor.getName(), competitor
|
|
| 548 | - .getColor(), competitor.getEmail(), competitor.getBoat().getSailID(), competitor.getTeam().getNationality(),
|
|
| 549 | - competitor.getTeam().getImage(), competitor.getFlagImage(),
|
|
| 550 | - competitor.getTimeOnTimeFactor(), competitor.getTimeOnDistanceAllowancePerNauticalMile()));
|
|
| 551 | - }
|
|
| 552 | - });
|
|
| 553 | - this.windStore = windStore;
|
|
| 554 | - this.dataImportLock = new DataImportLockWithProgress();
|
|
| 555 | -
|
|
| 556 | - remoteSailingServerSet = new RemoteSailingServerSet(scheduler);
|
|
| 557 | - regattasByName = new ConcurrentHashMap<String, Regatta>();
|
|
| 558 | - regattasByNameLock = new NamedReentrantReadWriteLock("regattasByName for " + this, /* fair */false);
|
|
| 559 | - eventsById = new ConcurrentHashMap<Serializable, Event>();
|
|
| 560 | - regattaTrackingCache = new ConcurrentHashMap<>();
|
|
| 561 | - regattaTrackingCacheLock = new NamedReentrantReadWriteLock("regattaTrackingCache for " + this, /* fair */false);
|
|
| 562 | - raceTrackersByRegatta = new ConcurrentHashMap<>();
|
|
| 563 | - raceTrackersByRegattaLock = new NamedReentrantReadWriteLock("raceTrackersByRegatta for " + this, /* fair */
|
|
| 564 | - false);
|
|
| 565 | - raceTrackersByID = new ConcurrentHashMap<>();
|
|
| 566 | - raceTrackersByIDLocks = new ConcurrentHashMap<>();
|
|
| 567 | - leaderboardGroupsByName = new ConcurrentHashMap<>();
|
|
| 568 | - leaderboardGroupsByID = new ConcurrentHashMap<>();
|
|
| 569 | - leaderboardGroupsByNameLock = new NamedReentrantReadWriteLock("leaderboardGroupsByName for " + this, /* fair */
|
|
| 570 | - false);
|
|
| 571 | - leaderboardsByName = new ConcurrentHashMap<String, Leaderboard>();
|
|
| 572 | - leaderboardsByNameLock = new NamedReentrantReadWriteLock("leaderboardsByName for " + this, /* fair */false);
|
|
| 573 | - operationExecutionListeners = new ConcurrentHashMap<>();
|
|
| 574 | - courseListeners = new ConcurrentHashMap<>();
|
|
| 575 | - persistentRegattasForRaceIDs = new ConcurrentHashMap<>();
|
|
| 576 | - final int SIMULATION_THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors()/3, 1);
|
|
| 577 | - Executor simulatorExecutor = new ThreadPoolExecutor(/* corePoolSize */SIMULATION_THREAD_POOL_SIZE,
|
|
| 578 | - /* maximumPoolSize */SIMULATION_THREAD_POOL_SIZE,
|
|
| 579 | - /* keepAliveTime */60, TimeUnit.SECONDS,
|
|
| 580 | - /* workQueue */new LinkedBlockingQueue<Runnable>());
|
|
| 581 | - // TODO: initialize smart-future-cache for simulation-results and add to simulation-service
|
|
| 582 | - simulationService = SimulationServiceFactory.INSTANCE.getService(simulatorExecutor, this);
|
|
| 583 | - this.raceLogReplicator = new RaceLogReplicator(this);
|
|
| 584 | - this.regattaLogReplicator = new RegattaLogReplicator(this);
|
|
| 585 | - this.raceLogScoringReplicator = new RaceLogScoringReplicator(this);
|
|
| 586 | - this.mediaLibrary = new MediaLibrary();
|
|
| 587 | - if (gpsFixStore == null) {
|
|
| 588 | - try {
|
|
| 589 | - gpsFixStore = MongoGPSFixStoreFactory.INSTANCE.getMongoGPSFixStore(mongoObjectFactory,
|
|
| 590 | - domainObjectFactory, serviceFinderFactory);
|
|
| 591 | - } catch (Exception e) {
|
|
| 592 | - e.printStackTrace();
|
|
| 593 | - throw new RuntimeException(e);
|
|
| 594 | - }
|
|
| 595 | - }
|
|
| 596 | - this.gpsFixStore = gpsFixStore;
|
|
| 597 | - this.configurationMap = new DeviceConfigurationMapImpl();
|
|
| 598 | - this.serviceFinderFactory = serviceFinderFactory;
|
|
| 599 | -
|
|
| 600 | - sailingServerConfiguration = domainObjectFactory.loadServerConfiguration();
|
|
| 601 | - final Iterable<Pair<Event, Boolean>> loadedEventsWithRequireStoreFlag = loadStoredEvents();
|
|
| 602 | - loadStoredRegattas();
|
|
| 603 | - loadRaceIDToRegattaAssociations();
|
|
| 604 | - loadStoredLeaderboardsAndGroups();
|
|
| 605 | - loadLinksFromEventsToLeaderboardGroups();
|
|
| 606 | - loadMediaLibary();
|
|
| 607 | - loadStoredDeviceConfigurations();
|
|
| 608 | - loadAllRemoteSailingServersAndSchedulePeriodicEventCacheRefresh();
|
|
| 609 | -
|
|
| 610 | - // Stores all events which run through a data migration
|
|
| 611 | - // Remark: must be called after loadLinksFromEventsToLeaderboardGroups(), otherwise would loose the Event -> LeaderboardGroup relation
|
|
| 612 | - for (Pair<Event, Boolean> eventAndRequireStoreFlag : loadedEventsWithRequireStoreFlag) {
|
|
| 613 | - if (eventAndRequireStoreFlag.getB()) {
|
|
| 614 | - mongoObjectFactory.storeEvent(eventAndRequireStoreFlag.getA());
|
|
| 615 | - }
|
|
| 616 | - }
|
|
| 617 | - }
|
|
| 618 | -
|
|
| 619 | - @Override
|
|
| 620 | - public boolean isCurrentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster() {
|
|
| 621 | - return currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster.get();
|
|
| 622 | - }
|
|
| 623 | -
|
|
| 624 | - @Override
|
|
| 625 | - public void setCurrentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster(
|
|
| 626 | - boolean currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster) {
|
|
| 627 | - this.currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster
|
|
| 628 | - .set(currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster);
|
|
| 629 | - }
|
|
| 630 | -
|
|
| 631 | - @Override
|
|
| 632 | - public PolarDataService getPolarDataService() {
|
|
| 633 | - return polarDataService;
|
|
| 634 | - }
|
|
| 635 | -
|
|
| 636 | - @Override
|
|
| 637 | - public SimulationService getSimulationService() {
|
|
| 638 | - return simulationService;
|
|
| 639 | - }
|
|
| 640 | -
|
|
| 641 | - @Override
|
|
| 642 | - public void clearState() throws Exception {
|
|
| 643 | - for (String leaderboardGroupName : new ArrayList<>(this.leaderboardGroupsByName.keySet())) {
|
|
| 644 | - removeLeaderboardGroup(leaderboardGroupName);
|
|
| 645 | - }
|
|
| 646 | - for (String leaderboardName : new ArrayList<>(this.leaderboardsByName.keySet())) {
|
|
| 647 | - removeLeaderboard(leaderboardName);
|
|
| 648 | - }
|
|
| 649 | - for (Regatta regatta : new ArrayList<>(this.regattasByName.values())) {
|
|
| 650 | - stopTracking(regatta);
|
|
| 651 | - removeRegatta(regatta);
|
|
| 652 | - }
|
|
| 653 | - for (Event event : new ArrayList<>(this.eventsById.values())) {
|
|
| 654 | - removeEvent(event.getId());
|
|
| 655 | - }
|
|
| 656 | - for (MediaTrack mediaTrack : this.mediaLibrary.allTracks()) {
|
|
| 657 | - mediaTrackDeleted(mediaTrack);
|
|
| 658 | - }
|
|
| 659 | - // TODO clear user store? See bug 2430.
|
|
| 660 | - this.competitorStore.clear();
|
|
| 661 | - }
|
|
| 662 | -
|
|
| 663 | - @Override
|
|
| 664 | - public com.sap.sailing.domain.base.DomainFactory getBaseDomainFactory() {
|
|
| 665 | - return baseDomainFactory;
|
|
| 666 | - }
|
|
| 667 | -
|
|
| 668 | - @Override
|
|
| 669 | - public MongoObjectFactory getMongoObjectFactory() {
|
|
| 670 | - return mongoObjectFactory;
|
|
| 671 | - }
|
|
| 672 | -
|
|
| 673 | - @Override
|
|
| 674 | - public DomainObjectFactory getDomainObjectFactory() {
|
|
| 675 | - return domainObjectFactory;
|
|
| 676 | - }
|
|
| 677 | -
|
|
| 678 | - private void loadRaceIDToRegattaAssociations() {
|
|
| 679 | - persistentRegattasForRaceIDs.putAll(domainObjectFactory.loadRaceIDToRegattaAssociations(this));
|
|
| 680 | - }
|
|
| 681 | -
|
|
| 682 | - private void loadStoredRegattas() {
|
|
| 683 | - LockUtil.lockForWrite(regattasByNameLock);
|
|
| 684 | - try {
|
|
| 685 | - for (Regatta regatta : domainObjectFactory.loadAllRegattas(this)) {
|
|
| 686 | - logger.info("putting regatta " + regatta.getName() + " (" + regatta.hashCode()
|
|
| 687 | - + ") into regattasByName");
|
|
| 688 | - regattasByName.put(regatta.getName(), regatta);
|
|
| 689 | - regatta.addRegattaListener(this);
|
|
| 690 | - onRegattaLikeAdded(regatta);
|
|
| 691 | - regatta.addRaceColumnListener(raceLogReplicator);
|
|
| 692 | - regatta.addRaceColumnListener(raceLogScoringReplicator);
|
|
| 693 | - }
|
|
| 694 | - } finally {
|
|
| 695 | - LockUtil.unlockAfterWrite(regattasByNameLock);
|
|
| 696 | - }
|
|
| 697 | - }
|
|
| 698 | -
|
|
| 699 | - private Iterable<Pair<Event, Boolean>> loadStoredEvents() {
|
|
| 700 | - Iterable<Pair<Event, Boolean>> loadedEventsWithRequireStoreFlag = domainObjectFactory.loadAllEvents();
|
|
| 701 | - for (Pair<Event, Boolean> eventAndFlag : loadedEventsWithRequireStoreFlag) {
|
|
| 702 | - Event event = eventAndFlag.getA();
|
|
| 703 | - if (event.getId() != null)
|
|
| 704 | - eventsById.put(event.getId(), event);
|
|
| 705 | - }
|
|
| 706 | - return loadedEventsWithRequireStoreFlag;
|
|
| 707 | - }
|
|
| 708 | -
|
|
| 709 | - private void loadLinksFromEventsToLeaderboardGroups() {
|
|
| 710 | - domainObjectFactory.loadLeaderboardGroupLinksForEvents(/* eventResolver */this, /* leaderboardGroupResolver */
|
|
| 711 | - this);
|
|
| 712 | - }
|
|
| 713 | -
|
|
| 714 | - private void loadAllRemoteSailingServersAndSchedulePeriodicEventCacheRefresh() {
|
|
| 715 | - for (RemoteSailingServerReference sailingServer : domainObjectFactory.loadAllRemoteSailingServerReferences()) {
|
|
| 716 | - remoteSailingServerSet.add(sailingServer);
|
|
| 717 | - }
|
|
| 718 | - }
|
|
| 719 | -
|
|
| 720 | - /**
|
|
| 721 | - * Collects media track references from the configured sources (mongo DB by default, ftp folder yet to be
|
|
| 722 | - * implemented). The method is expected to be called initially blocking the API until finished.
|
|
| 723 | - *
|
|
| 724 | - * Subsequent calls (assumed to be triggered from the admin console or in scheduled intervals) don't need to block.
|
|
| 725 | - * In that case, the API will simply serve the current state.
|
|
| 726 | - *
|
|
| 727 | - */
|
|
| 728 | - private void loadMediaLibary() {
|
|
| 729 | - Collection<MediaTrack> allDbMediaTracks = mediaDB.loadAllMediaTracks();
|
|
| 730 | - mediaTracksAdded(allDbMediaTracks);
|
|
| 731 | - }
|
|
| 732 | -
|
|
| 733 | - private void loadStoredDeviceConfigurations() {
|
|
| 734 | - for (Entry<DeviceConfigurationMatcher, DeviceConfiguration> entry : domainObjectFactory
|
|
| 735 | - .loadAllDeviceConfigurations()) {
|
|
| 736 | - configurationMap.put(entry.getKey(), entry.getValue());
|
|
| 737 | - }
|
|
| 738 | - }
|
|
| 739 | -
|
|
| 740 | - @Override
|
|
| 741 | - public void addLeaderboard(Leaderboard leaderboard) {
|
|
| 742 | - LockUtil.lockForWrite(leaderboardsByNameLock);
|
|
| 743 | - try {
|
|
| 744 | - leaderboardsByName.put(leaderboard.getName(), leaderboard);
|
|
| 745 | - } finally {
|
|
| 746 | - LockUtil.unlockAfterWrite(leaderboardsByNameLock);
|
|
| 747 | - }
|
|
| 748 | - // RaceColumns of RegattaLeaderboards are tracked via its Regatta!
|
|
| 749 | - if (leaderboard instanceof FlexibleLeaderboard) {
|
|
| 750 | - onRegattaLikeAdded(((FlexibleLeaderboard) leaderboard).getRegattaLike());
|
|
| 751 | - leaderboard.addRaceColumnListener(raceLogReplicator);
|
|
| 752 | - leaderboard.addRaceColumnListener(raceLogScoringReplicator);
|
|
| 753 | - }
|
|
| 754 | - }
|
|
| 755 | -
|
|
| 756 | - private void loadStoredLeaderboardsAndGroups() {
|
|
| 757 | - logger.info("loading stored leaderboards and groups");
|
|
| 758 | - // Loading all leaderboard groups and the contained leaderboards
|
|
| 759 | - for (LeaderboardGroup leaderboardGroup : domainObjectFactory.getAllLeaderboardGroups(this, this)) {
|
|
| 760 | - logger.info("loaded leaderboard group " + leaderboardGroup.getName() + " into " + this);
|
|
| 761 | - LockUtil.lockForWrite(leaderboardGroupsByNameLock);
|
|
| 762 | - try {
|
|
| 763 | - leaderboardGroupsByName.put(leaderboardGroup.getName(), leaderboardGroup);
|
|
| 764 | - leaderboardGroupsByID.put(leaderboardGroup.getId(), leaderboardGroup);
|
|
| 765 | - } finally {
|
|
| 766 | - LockUtil.unlockAfterWrite(leaderboardGroupsByNameLock);
|
|
| 767 | - }
|
|
| 768 | - }
|
|
| 769 | - // Loading the remaining leaderboards
|
|
| 770 | - domainObjectFactory.getLeaderboardsNotInGroup(this, this);
|
|
| 771 | - logger.info("done with loading stored leaderboards and groups");
|
|
| 772 | - }
|
|
| 773 | -
|
|
| 774 | - @Override
|
|
| 775 | - public FlexibleLeaderboard addFlexibleLeaderboard(String leaderboardName, String leaderboardDisplayName,
|
|
| 776 | - int[] discardThresholds, ScoringScheme scoringScheme, Serializable courseAreaId) {
|
|
| 777 | - logger.info("adding flexible leaderboard " + leaderboardName);
|
|
| 778 | - CourseArea courseArea = getCourseArea(courseAreaId);
|
|
| 779 | - FlexibleLeaderboard result = new FlexibleLeaderboardImpl(getRaceLogStore(), getRegattaLogStore(),
|
|
| 780 | - leaderboardName, new ThresholdBasedResultDiscardingRuleImpl(discardThresholds), scoringScheme,
|
|
| 781 | - courseArea);
|
|
| 782 | - result.setDisplayName(leaderboardDisplayName);
|
|
| 783 | - if (getLeaderboardByName(leaderboardName) != null) {
|
|
| 784 | - throw new IllegalArgumentException("Leaderboard with name " + leaderboardName + " already exists");
|
|
| 785 | - }
|
|
| 786 | - addLeaderboard(result);
|
|
| 787 | - mongoObjectFactory.storeLeaderboard(result);
|
|
| 788 | - return result;
|
|
| 789 | - }
|
|
| 790 | -
|
|
| 791 | - @Override
|
|
| 792 | - public CourseArea getCourseArea(Serializable courseAreaId) {
|
|
| 793 | - for (Event event : getAllEvents()) {
|
|
| 794 | - for (CourseArea courseArea : event.getVenue().getCourseAreas()) {
|
|
| 795 | - if (courseArea.getId().equals(courseAreaId)) {
|
|
| 796 | - return courseArea;
|
|
| 797 | - }
|
|
| 798 | - }
|
|
| 799 | - }
|
|
| 800 | - return null;
|
|
| 801 | - }
|
|
| 802 | -
|
|
| 803 | - @Override
|
|
| 804 | - public RegattaLeaderboard addRegattaLeaderboard(RegattaIdentifier regattaIdentifier, String leaderboardDisplayName,
|
|
| 805 | - int[] discardThresholds) {
|
|
| 806 | - Regatta regatta = getRegatta(regattaIdentifier);
|
|
| 807 | - logger.info("adding regatta leaderboard for regatta "
|
|
| 808 | - + (regatta == null ? "null" : (regatta.getName() + " (" + regatta.hashCode() + ")")) + " to " + this);
|
|
| 809 | - RegattaLeaderboard result = null;
|
|
| 810 | - if (regatta != null) {
|
|
| 811 | - result = new RegattaLeaderboardImpl(regatta, new ThresholdBasedResultDiscardingRuleImpl(discardThresholds));
|
|
| 812 | - result.setDisplayName(leaderboardDisplayName);
|
|
| 813 | - if (getLeaderboardByName(result.getName()) != null) {
|
|
| 814 | - throw new IllegalArgumentException("Leaderboard with name " + result.getName() + " already exists in "
|
|
| 815 | - + this);
|
|
| 816 | - }
|
|
| 817 | - addLeaderboard(result);
|
|
| 818 | - mongoObjectFactory.storeLeaderboard(result);
|
|
| 819 | - } else {
|
|
| 820 | - logger.warning("Cannot find regatta " + regattaIdentifier
|
|
| 821 | - + ". Hence, cannot create regatta leaderboard for it.");
|
|
| 822 | - }
|
|
| 823 | - return result;
|
|
| 824 | - }
|
|
| 825 | -
|
|
| 826 | - @Override
|
|
| 827 | - public RaceColumn addColumnToLeaderboard(String columnName, String leaderboardName, boolean medalRace) {
|
|
| 828 | - Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
|
|
| 829 | - if (leaderboard != null) {
|
|
| 830 | - if (leaderboard instanceof FlexibleLeaderboard) {
|
|
| 831 | - // uses the default fleet as the single fleet for the new column
|
|
| 832 | - RaceColumn result = ((FlexibleLeaderboard) leaderboard).addRaceColumn(columnName, medalRace);
|
|
| 833 | - updateStoredLeaderboard((FlexibleLeaderboard) leaderboard);
|
|
| 834 | - return result;
|
|
| 835 | - } else {
|
|
| 836 | - throw new IllegalArgumentException("Leaderboard named " + leaderboardName
|
|
| 837 | - + " is not a FlexibleLeaderboard");
|
|
| 838 | - }
|
|
| 839 | - } else {
|
|
| 840 | - throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found");
|
|
| 841 | - }
|
|
| 842 | - }
|
|
| 843 | -
|
|
| 844 | - @Override
|
|
| 845 | - public void moveLeaderboardColumnUp(String leaderboardName, String columnName) {
|
|
| 846 | - Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
|
|
| 847 | - if (leaderboard != null && leaderboard instanceof FlexibleLeaderboard) {
|
|
| 848 | - ((FlexibleLeaderboard) leaderboard).moveRaceColumnUp(columnName);
|
|
| 849 | - updateStoredLeaderboard((FlexibleLeaderboard) leaderboard);
|
|
| 850 | - } else {
|
|
| 851 | - throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found");
|
|
| 852 | - }
|
|
| 853 | - }
|
|
| 854 | -
|
|
| 855 | - @Override
|
|
| 856 | - public void moveLeaderboardColumnDown(String leaderboardName, String columnName) {
|
|
| 857 | - Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
|
|
| 858 | - if (leaderboard != null && leaderboard instanceof FlexibleLeaderboard) {
|
|
| 859 | - ((FlexibleLeaderboard) leaderboard).moveRaceColumnDown(columnName);
|
|
| 860 | - updateStoredLeaderboard((FlexibleLeaderboard) leaderboard);
|
|
| 861 | - } else {
|
|
| 862 | - throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found");
|
|
| 863 | - }
|
|
| 864 | - }
|
|
| 865 | -
|
|
| 866 | - @Override
|
|
| 867 | - public void removeLeaderboardColumn(String leaderboardName, String columnName) {
|
|
| 868 | - Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
|
|
| 869 | - if (leaderboard == null) {
|
|
| 870 | - throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found");
|
|
| 871 | - } else if (!(leaderboard instanceof FlexibleLeaderboard)) {
|
|
| 872 | - throw new IllegalArgumentException("Columns cannot be removed from Leaderboard named " + leaderboardName);
|
|
| 873 | - } else {
|
|
| 874 | - ((FlexibleLeaderboard) leaderboard).removeRaceColumn(columnName);
|
|
| 875 | - updateStoredLeaderboard((FlexibleLeaderboard) leaderboard);
|
|
| 876 | - }
|
|
| 877 | - }
|
|
| 878 | -
|
|
| 879 | - @Override
|
|
| 880 | - public void renameLeaderboardColumn(String leaderboardName, String oldColumnName, String newColumnName) {
|
|
| 881 | - Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
|
|
| 882 | - if (leaderboard != null) {
|
|
| 883 | - final RaceColumn raceColumn = leaderboard.getRaceColumnByName(oldColumnName);
|
|
| 884 | - if (raceColumn instanceof FlexibleRaceColumn) {
|
|
| 885 | - // remove race log under old identifier; the race log identifier changes
|
|
| 886 | - for (Fleet fleet : raceColumn.getFleets()) {
|
|
| 887 | - getMongoObjectFactory().removeRaceLog(raceColumn.getRaceLogIdentifier(fleet));
|
|
| 888 | - }
|
|
| 889 | - ((FlexibleRaceColumn) raceColumn).setName(newColumnName);
|
|
| 890 | - // store the race logs again under the new identifiers
|
|
| 891 | - storeRaceLogs(raceColumn);
|
|
| 892 | - updateStoredLeaderboard(leaderboard);
|
|
| 893 | - } else {
|
|
| 894 | - throw new IllegalArgumentException("Race column " + oldColumnName + " cannot be renamed");
|
|
| 895 | - }
|
|
| 896 | - } else {
|
|
| 897 | - throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found");
|
|
| 898 | - }
|
|
| 899 | - }
|
|
| 900 | -
|
|
| 901 | - /**
|
|
| 902 | - * When a race column is renamed, its race log identifiers change. Therefore, the race logs need to be stored under
|
|
| 903 | - * the new identifier again to be consistent with the in-memory image again.
|
|
| 904 | - */
|
|
| 905 | - private void storeRaceLogs(RaceColumn raceColumn) {
|
|
| 906 | - for (Fleet fleet : raceColumn.getFleets()) {
|
|
| 907 | - RaceLogIdentifier identifier = raceColumn.getRaceLogIdentifier(fleet);
|
|
| 908 | - RaceLogEventVisitor storeVisitor = MongoRaceLogStoreFactory.INSTANCE.getMongoRaceLogStoreVisitor(
|
|
| 909 | - identifier, getMongoObjectFactory());
|
|
| 910 | - RaceLog raceLog = raceColumn.getRaceLog(fleet);
|
|
| 911 | - raceLog.lockForRead();
|
|
| 912 | - try {
|
|
| 913 | - for (RaceLogEvent e : raceLog.getRawFixes()) {
|
|
| 914 | - e.accept(storeVisitor);
|
|
| 915 | - }
|
|
| 916 | - } finally {
|
|
| 917 | - raceLog.unlockAfterRead();
|
|
| 918 | - }
|
|
| 919 | - }
|
|
| 920 | - }
|
|
| 921 | -
|
|
| 922 | - @Override
|
|
| 923 | - public void updateLeaderboardColumnFactor(String leaderboardName, String columnName, Double factor) {
|
|
| 924 | - Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
|
|
| 925 | - if (leaderboard != null) {
|
|
| 926 | - final RaceColumn raceColumn = leaderboard.getRaceColumnByName(columnName);
|
|
| 927 | - if (raceColumn != null) {
|
|
| 928 | - raceColumn.setFactor(factor);
|
|
| 929 | - updateStoredLeaderboard(leaderboard);
|
|
| 930 | - } else {
|
|
| 931 | - throw new IllegalArgumentException("Race column " + columnName + " not found in leaderboard "
|
|
| 932 | - + leaderboardName);
|
|
| 933 | - }
|
|
| 934 | - } else {
|
|
| 935 | - throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found");
|
|
| 936 | - }
|
|
| 937 | - }
|
|
| 938 | -
|
|
| 939 | - @Override
|
|
| 940 | - public void renameLeaderboard(String oldName, String newName) {
|
|
| 941 | - final Leaderboard toRename = leaderboardsByName.get(oldName);
|
|
| 942 | - LockUtil.lockForWrite(leaderboardsByNameLock);
|
|
| 943 | - try {
|
|
| 944 | - if (toRename == null) {
|
|
| 945 | - throw new IllegalArgumentException("No leaderboard with name " + oldName + " found");
|
|
| 946 | - }
|
|
| 947 | - if (leaderboardsByName.containsKey(newName)) {
|
|
| 948 | - throw new IllegalArgumentException("Leaderboard with name " + newName + " already exists");
|
|
| 949 | - }
|
|
| 950 | - if (toRename instanceof Renamable) {
|
|
| 951 | - ((Renamable) toRename).setName(newName);
|
|
| 952 | - leaderboardsByName.remove(oldName);
|
|
| 953 | - leaderboardsByName.put(newName, toRename);
|
|
| 954 | - } else {
|
|
| 955 | - throw new IllegalArgumentException("Leaderboard with name " + newName + " is of type "
|
|
| 956 | - + toRename.getClass().getSimpleName() + " and therefore cannot be renamed");
|
|
| 957 | - }
|
|
| 958 | - } finally {
|
|
| 959 | - LockUtil.unlockAfterWrite(leaderboardsByNameLock);
|
|
| 960 | - }
|
|
| 961 | - // don't need the lock anymore to update DB
|
|
| 962 | - if (toRename instanceof Renamable) {
|
|
| 963 | - mongoObjectFactory.renameLeaderboard(oldName, newName);
|
|
| 964 | - syncGroupsAfterLeaderboardChange(toRename, true);
|
|
| 965 | - }
|
|
| 966 | - }
|
|
| 967 | -
|
|
| 968 | - @Override
|
|
| 969 | - public void updateStoredLeaderboard(Leaderboard leaderboard) {
|
|
| 970 | - getMongoObjectFactory().storeLeaderboard(leaderboard);
|
|
| 971 | - syncGroupsAfterLeaderboardChange(leaderboard, true);
|
|
| 972 | - }
|
|
| 973 | -
|
|
| 974 | - @Override
|
|
| 975 | - public void updateStoredRegatta(Regatta regatta) {
|
|
| 976 | - if (regatta.isPersistent()) {
|
|
| 977 | - mongoObjectFactory.storeRegatta(regatta);
|
|
| 978 | - }
|
|
| 979 | - }
|
|
| 980 | -
|
|
| 981 | - /**
|
|
| 982 | - * Checks all groups, if they contain a leaderboard with the name of the <code>updatedLeaderboard</code> and
|
|
| 983 | - * replaces the one in the group with the updated one.<br />
|
|
| 984 | - * This synchronizes things like the RaceIdentifier in the leaderboard columns.
|
|
| 985 | - */
|
|
| 986 | - private void syncGroupsAfterLeaderboardChange(Leaderboard updatedLeaderboard, boolean doDatabaseUpdate) {
|
|
| 987 | - boolean groupNeedsUpdate = false;
|
|
| 988 | - for (LeaderboardGroup leaderboardGroup : leaderboardGroupsByName.values()) {
|
|
| 989 | - for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
|
|
| 990 | - if (leaderboard == updatedLeaderboard) {
|
|
| 991 | - int index = leaderboardGroup.getIndexOf(leaderboard);
|
|
| 992 | - leaderboardGroup.removeLeaderboard(leaderboard);
|
|
| 993 | - leaderboardGroup.addLeaderboardAt(updatedLeaderboard, index);
|
|
| 994 | - groupNeedsUpdate = true;
|
|
| 995 | - // TODO we assume that the leaderboard names are unique, so we can break the inner loop here
|
|
| 996 | - break;
|
|
| 997 | - }
|
|
| 998 | - }
|
|
| 999 | -
|
|
| 1000 | - if (doDatabaseUpdate && groupNeedsUpdate) {
|
|
| 1001 | - mongoObjectFactory.storeLeaderboardGroup(leaderboardGroup);
|
|
| 1002 | - }
|
|
| 1003 | - groupNeedsUpdate = false;
|
|
| 1004 | - }
|
|
| 1005 | - }
|
|
| 1006 | -
|
|
| 1007 | - @Override
|
|
| 1008 | - public void removeLeaderboard(String leaderboardName) {
|
|
| 1009 | - Leaderboard leaderboard = removeLeaderboardFromLeaderboardsByName(leaderboardName);
|
|
| 1010 | - if (leaderboard != null) {
|
|
| 1011 | - leaderboard.removeRaceColumnListener(raceLogReplicator);
|
|
| 1012 | - leaderboard.removeRaceColumnListener(raceLogScoringReplicator);
|
|
| 1013 | - mongoObjectFactory.removeLeaderboard(leaderboardName);
|
|
| 1014 | - syncGroupsAfterLeaderboardRemove(leaderboardName, true);
|
|
| 1015 | - if (leaderboard instanceof FlexibleLeaderboard) {
|
|
| 1016 | - onRegattaLikeRemoved(((FlexibleLeaderboard) leaderboard).getRegattaLike());
|
|
| 1017 | - }
|
|
| 1018 | - leaderboard.destroy();
|
|
| 1019 | - }
|
|
| 1020 | - }
|
|
| 1021 | -
|
|
| 1022 | - private Leaderboard removeLeaderboardFromLeaderboardsByName(String leaderboardName) {
|
|
| 1023 | - LockUtil.lockForWrite(leaderboardsByNameLock);
|
|
| 1024 | - try {
|
|
| 1025 | - return leaderboardsByName.remove(leaderboardName);
|
|
| 1026 | - } finally {
|
|
| 1027 | - LockUtil.unlockAfterWrite(leaderboardsByNameLock);
|
|
| 1028 | - }
|
|
| 1029 | - }
|
|
| 1030 | -
|
|
| 1031 | - /**
|
|
| 1032 | - * Checks all groups, if they contain a leaderboard with the <code>removedLeaderboardName</code> and removes it from
|
|
| 1033 | - * the group.
|
|
| 1034 | - *
|
|
| 1035 | - * @param removedLeaderboardName
|
|
| 1036 | - */
|
|
| 1037 | - private void syncGroupsAfterLeaderboardRemove(String removedLeaderboardName, boolean doDatabaseUpdate) {
|
|
| 1038 | - boolean groupNeedsUpdate = false;
|
|
| 1039 | - for (LeaderboardGroup leaderboardGroup : leaderboardGroupsByName.values()) {
|
|
| 1040 | - for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
|
|
| 1041 | - if (leaderboard.getName().equals(removedLeaderboardName)) {
|
|
| 1042 | - leaderboardGroup.removeLeaderboard(leaderboard);
|
|
| 1043 | - groupNeedsUpdate = true;
|
|
| 1044 | - // TODO we assume that the leaderboard names are unique, so we can break the inner loop here
|
|
| 1045 | - break;
|
|
| 1046 | - }
|
|
| 1047 | - }
|
|
| 1048 | -
|
|
| 1049 | - if (doDatabaseUpdate && groupNeedsUpdate) {
|
|
| 1050 | - mongoObjectFactory.storeLeaderboardGroup(leaderboardGroup);
|
|
| 1051 | - }
|
|
| 1052 | - groupNeedsUpdate = false;
|
|
| 1053 | - }
|
|
| 1054 | - }
|
|
| 1055 | -
|
|
| 1056 | - @Override
|
|
| 1057 | - public Leaderboard getLeaderboardByName(String name) {
|
|
| 1058 | - return leaderboardsByName.get(name);
|
|
| 1059 | - }
|
|
| 1060 | -
|
|
| 1061 | - @Override
|
|
| 1062 | - public Position getMarkPosition(Mark mark, LeaderboardThatHasRegattaLike leaderboard, TimePoint timePoint, RaceLog raceLog) {
|
|
| 1063 | - GPSFixTrack<Mark, GPSFix> track = null;
|
|
| 1064 | - // If no spanning track is found, the fix closest to the time point requested is used instead
|
|
| 1065 | - GPSFix nonSpanningFallback = null;
|
|
| 1066 | - for (TrackedRace trackedRace : leaderboard.getTrackedRaces()) {
|
|
| 1067 | - GPSFixTrack<Mark, GPSFix> trackCandidate = trackedRace.getTrack(mark);
|
|
| 1068 | - if (trackCandidate != null) {
|
|
| 1069 | - if (spansTimePoint(trackCandidate, timePoint)) {
|
|
| 1070 | - track = trackCandidate;
|
|
| 1071 | - break;
|
|
| 1072 | - } else {
|
|
| 1073 | - nonSpanningFallback = improveTimewiseClosestFix(nonSpanningFallback, trackCandidate, timePoint);
|
|
| 1074 | - }
|
|
| 1075 | - }
|
|
| 1076 | - }
|
|
| 1077 | - if (track == null) { // no spanning track found in any tracked race, or no tracked races found
|
|
| 1078 | - // try to load from store
|
|
| 1079 | - DynamicGPSFixTrackImpl<Mark> loadedTrack = new DynamicGPSFixTrackImpl<Mark>(mark, 0);
|
|
| 1080 | - track = loadedTrack;
|
|
| 1081 | - Set<AbstractLog<?, ?>> logs = new HashSet<>();
|
|
| 1082 | - logs.add(leaderboard.getRegattaLike().getRegattaLog());
|
|
| 1083 | - if (raceLog == null) { // no race log explicitly provided --> use all race logs
|
|
| 1084 | - for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
|
|
| 1085 | - for (Fleet fleet : raceColumn.getFleets()) {
|
|
| 1086 | - logs.add(raceColumn.getRaceLog(fleet));
|
|
| 1087 | - }
|
|
| 1088 | - }
|
|
| 1089 | - } else {
|
|
| 1090 | - logs.add(raceLog);
|
|
| 1091 | - }
|
|
| 1092 | - for (AbstractLog<?, ?> log : logs) {
|
|
| 1093 | - try {
|
|
| 1094 | - getGPSFixStore().loadMarkTrack(loadedTrack, log, mark);
|
|
| 1095 | - } catch (Exception e) {
|
|
| 1096 | - logger.log(Level.WARNING, "Couldn't load mark track for mark " + mark + " from log " + log, e);
|
|
| 1097 | - }
|
|
| 1098 | - }
|
|
| 1099 | - }
|
|
| 1100 | - Position result = track.getEstimatedPosition(timePoint, /* extrapolate */ false);
|
|
| 1101 | - if (result == null) {
|
|
| 1102 | - result = nonSpanningFallback == null ? null : nonSpanningFallback.getPosition();
|
|
| 1103 | - }
|
|
| 1104 | - return result;
|
|
| 1105 | - }
|
|
| 1106 | -
|
|
| 1107 | - private GPSFix improveTimewiseClosestFix(GPSFix nonSpanningFallback, GPSFixTrack<Mark, GPSFix> track, final TimePoint timePoint) {
|
|
| 1108 | - GPSFix lastAtOrBefore = track.getLastFixAtOrBefore(timePoint);
|
|
| 1109 | - GPSFix firstAtOrAfter = track.getFirstFixAtOrAfter(timePoint);
|
|
| 1110 | - // find the fix closes to timePoint, sorting null values to the end and fixes near timePoint to the beginning
|
|
| 1111 | - final List<GPSFix> list = Arrays.asList(nonSpanningFallback, lastAtOrBefore, firstAtOrAfter);
|
|
| 1112 | - list.sort(new Comparator<GPSFix>() {
|
|
| 1113 | - @Override
|
|
| 1114 | - public int compare(GPSFix o1, GPSFix o2) {
|
|
| 1115 | - final int result;
|
|
| 1116 | - if (o1 == null) {
|
|
| 1117 | - if (o2 == null) {
|
|
| 1118 | - result = 0;
|
|
| 1119 | - } else {
|
|
| 1120 | - result = 1;
|
|
| 1121 | - }
|
|
| 1122 | - } else if (o2 == null) {
|
|
| 1123 | - result = -1;
|
|
| 1124 | - } else {
|
|
| 1125 | - result = new Long(Math.abs(o1.getTimePoint().until(timePoint).asMillis())).compareTo(
|
|
| 1126 | - Math.abs(o2.getTimePoint().until(timePoint).asMillis()));
|
|
| 1127 | - }
|
|
| 1128 | - return result;
|
|
| 1129 | - }
|
|
| 1130 | - });
|
|
| 1131 | - return list.get(0);
|
|
| 1132 | - }
|
|
| 1133 | -
|
|
| 1134 | - private boolean spansTimePoint(GPSFixTrack<Mark, GPSFix> track, TimePoint timePoint) {
|
|
| 1135 | - return track.getLastFixAtOrBefore(timePoint) != null && track.getFirstFixAtOrAfter(timePoint) != null;
|
|
| 1136 | - }
|
|
| 1137 | -
|
|
| 1138 | - @Override
|
|
| 1139 | - public Map<String, Leaderboard> getLeaderboards() {
|
|
| 1140 | - return Collections.unmodifiableMap(new HashMap<String, Leaderboard>(leaderboardsByName));
|
|
| 1141 | - }
|
|
| 1142 | -
|
|
| 1143 | - @Override
|
|
| 1144 | - public SailingServerConfiguration getSailingServerConfiguration() {
|
|
| 1145 | - return sailingServerConfiguration;
|
|
| 1146 | - }
|
|
| 1147 | -
|
|
| 1148 | - @Override
|
|
| 1149 | - public void updateServerConfiguration(SailingServerConfiguration serverConfiguration) {
|
|
| 1150 | - this.sailingServerConfiguration = serverConfiguration;
|
|
| 1151 | - mongoObjectFactory.storeServerConfiguration(serverConfiguration);
|
|
| 1152 | - }
|
|
| 1153 | -
|
|
| 1154 | - @Override
|
|
| 1155 | - public Map<RemoteSailingServerReference, com.sap.sse.common.Util.Pair<Iterable<EventBase>, Exception>> getPublicEventsOfAllSailingServers() {
|
|
| 1156 | - return remoteSailingServerSet.getCachedEventsForRemoteSailingServers(); // FIXME should probably add our own
|
|
| 1157 | - // stuff here... Is it enough to pass on
|
|
| 1158 | - // the remote reference URL to the
|
|
| 1159 | - // client for leaderboard group URL
|
|
| 1160 | - // construction?
|
|
| 1161 | - }
|
|
| 1162 | -
|
|
| 1163 | - @Override
|
|
| 1164 | - public RemoteSailingServerReference addRemoteSailingServerReference(String name, URL url) {
|
|
| 1165 | - RemoteSailingServerReference result = new RemoteSailingServerReferenceImpl(name, url);
|
|
| 1166 | - remoteSailingServerSet.add(result);
|
|
| 1167 | - mongoObjectFactory.storeSailingServer(result);
|
|
| 1168 | - return result;
|
|
| 1169 | - }
|
|
| 1170 | -
|
|
| 1171 | - @Override
|
|
| 1172 | - public Iterable<RemoteSailingServerReference> getLiveRemoteServerReferences() {
|
|
| 1173 | - return remoteSailingServerSet.getLiveRemoteServerReferences();
|
|
| 1174 | - }
|
|
| 1175 | -
|
|
| 1176 | - @Override
|
|
| 1177 | - public RemoteSailingServerReference getRemoteServerReferenceByName(String remoteServerReferenceName) {
|
|
| 1178 | - return remoteSailingServerSet.getServerReferenceByName(remoteServerReferenceName);
|
|
| 1179 | - }
|
|
| 1180 | -
|
|
| 1181 | - @Override
|
|
| 1182 | - public com.sap.sse.common.Util.Pair<Iterable<EventBase>, Exception> updateRemoteServerEventCacheSynchronously(
|
|
| 1183 | - RemoteSailingServerReference ref) {
|
|
| 1184 | - return remoteSailingServerSet.getEventsOrException(ref);
|
|
| 1185 | - }
|
|
| 1186 | -
|
|
| 1187 | - @Override
|
|
| 1188 | - public void removeRemoteSailingServerReference(String name) {
|
|
| 1189 | - remoteSailingServerSet.remove(name);
|
|
| 1190 | - mongoObjectFactory.removeSailingServer(name);
|
|
| 1191 | - }
|
|
| 1192 | -
|
|
| 1193 | - @Override
|
|
| 1194 | - public Iterable<Event> getAllEvents() {
|
|
| 1195 | - return Collections.unmodifiableCollection(new ArrayList<Event>(eventsById.values()));
|
|
| 1196 | - }
|
|
| 1197 | -
|
|
| 1198 | - @Override
|
|
| 1199 | - public Event getEvent(Serializable id) {
|
|
| 1200 | - return id == null ? null : eventsById.get(id);
|
|
| 1201 | - }
|
|
| 1202 | -
|
|
| 1203 | - @Override
|
|
| 1204 | - public Iterable<Regatta> getAllRegattas() {
|
|
| 1205 | - return Collections.unmodifiableCollection(new ArrayList<Regatta>(regattasByName.values()));
|
|
| 1206 | - }
|
|
| 1207 | -
|
|
| 1208 | - @Override
|
|
| 1209 | - public boolean isRaceBeingTracked(Regatta regattaContext, RaceDefinition r) {
|
|
| 1210 | - Set<RaceTracker> trackers = raceTrackersByRegatta.get(regattaContext);
|
|
| 1211 | - if (trackers != null) {
|
|
| 1212 | - for (RaceTracker tracker : trackers) {
|
|
| 1213 | - final Set<RaceDefinition> races = tracker.getRaces();
|
|
| 1214 | - if (races != null && races.contains(r)) {
|
|
| 1215 | - return true;
|
|
| 1216 | - }
|
|
| 1217 | - }
|
|
| 1218 | - }
|
|
| 1219 | - return false;
|
|
| 1220 | - }
|
|
| 1221 | -
|
|
| 1222 | - @Override
|
|
| 1223 | - public Regatta getRegattaByName(String name) {
|
|
| 1224 | - return name == null ? null : regattasByName.get(name);
|
|
| 1225 | - }
|
|
| 1226 | -
|
|
| 1227 | - @Override
|
|
| 1228 | - public Regatta getOrCreateDefaultRegatta(String name, String boatClassName, Serializable id) {
|
|
| 1229 | - Regatta result = regattasByName.get(name);
|
|
| 1230 | - if (result == null) {
|
|
| 1231 | - result = new RegattaImpl(getRaceLogStore(), getRegattaLogStore(), name, getBaseDomainFactory()
|
|
| 1232 | - .getOrCreateBoatClass(boatClassName), /* startDate */null, /* endDate */null, this,
|
|
| 1233 | - getBaseDomainFactory().createScoringScheme(ScoringSchemeType.LOW_POINT), id, null);
|
|
| 1234 | - logger.info("Created default regatta " + result.getName() + " (" + hashCode() + ") on " + this);
|
|
| 1235 | - onRegattaLikeAdded(result);
|
|
| 1236 | - cacheAndReplicateDefaultRegatta(result);
|
|
| 1237 | - }
|
|
| 1238 | - return result;
|
|
| 1239 | - }
|
|
| 1240 | -
|
|
| 1241 | - private void onRegattaLikeAdded(IsRegattaLike isRegattaLike) {
|
|
| 1242 | - isRegattaLike.addListener(regattaLogReplicator);
|
|
| 1243 | - }
|
|
| 1244 | -
|
|
| 1245 | - private void onRegattaLikeRemoved(IsRegattaLike isRegattaLike) {
|
|
| 1246 | - isRegattaLike.removeListener(regattaLogReplicator);
|
|
| 1247 | - getRegattaLogStore().removeRegattaLog(isRegattaLike.getRegattaLikeIdentifier());
|
|
| 1248 | - }
|
|
| 1249 | -
|
|
| 1250 | - @Override
|
|
| 1251 | - public Regatta createRegatta(String fullRegattaName, String boatClassName, TimePoint startDate, TimePoint endDate,
|
|
| 1252 | - Serializable id, Iterable<? extends Series> series, boolean persistent, ScoringScheme scoringScheme,
|
|
| 1253 | - Serializable defaultCourseAreaId, boolean useStartTimeInference, RankingMetricConstructor rankingMetricConstructor) {
|
|
| 1254 | - com.sap.sse.common.Util.Pair<Regatta, Boolean> regattaWithCreatedFlag = getOrCreateRegattaWithoutReplication(
|
|
| 1255 | - fullRegattaName, boatClassName, startDate, endDate, id, series, persistent, scoringScheme,
|
|
| 1256 | - defaultCourseAreaId, useStartTimeInference, rankingMetricConstructor);
|
|
| 1257 | - Regatta regatta = regattaWithCreatedFlag.getA();
|
|
| 1258 | - if (regattaWithCreatedFlag.getB()) {
|
|
| 1259 | - onRegattaLikeAdded(regatta);
|
|
| 1260 | - replicateSpecificRegattaWithoutRaceColumns(regatta);
|
|
| 1261 | - }
|
|
| 1262 | - return regatta;
|
|
| 1263 | - }
|
|
| 1264 | -
|
|
| 1265 | - @Override
|
|
| 1266 | - public void addRegattaWithoutReplication(Regatta regatta) {
|
|
| 1267 | - UUID defaultCourseAreaId = null;
|
|
| 1268 | - if (regatta.getDefaultCourseArea() != null) {
|
|
| 1269 | - defaultCourseAreaId = regatta.getDefaultCourseArea().getId();
|
|
| 1270 | - }
|
|
| 1271 | - boolean wasAdded = addAndConnectRegatta(regatta.isPersistent(), defaultCourseAreaId, regatta);
|
|
| 1272 | - if (!wasAdded) {
|
|
| 1273 | - logger.info("Regatta with name " + regatta.getName() + " already existed, so it hasn't been added.");
|
|
| 1274 | - }
|
|
| 1275 | - }
|
|
| 1276 | -
|
|
| 1277 | - private RaceLogStore getRaceLogStore() {
|
|
| 1278 | - return MongoRaceLogStoreFactory.INSTANCE.getMongoRaceLogStore(mongoObjectFactory, domainObjectFactory);
|
|
| 1279 | - }
|
|
| 1280 | -
|
|
| 1281 | - private RegattaLogStore getRegattaLogStore() {
|
|
| 1282 | - return MongoRegattaLogStoreFactory.INSTANCE.getMongoRegattaLogStore(mongoObjectFactory, domainObjectFactory);
|
|
| 1283 | - }
|
|
| 1284 | -
|
|
| 1285 | - @Override
|
|
| 1286 | - public com.sap.sse.common.Util.Pair<Regatta, Boolean> getOrCreateRegattaWithoutReplication(String fullRegattaName,
|
|
| 1287 | - String boatClassName, TimePoint startDate, TimePoint endDate, Serializable id,
|
|
| 1288 | - Iterable<? extends Series> series, boolean persistent, ScoringScheme scoringScheme,
|
|
| 1289 | - Serializable defaultCourseAreaId, boolean useStartTimeInference, RankingMetricConstructor rankingMetricConstructor) {
|
|
| 1290 | - CourseArea courseArea = getCourseArea(defaultCourseAreaId);
|
|
| 1291 | - Regatta regatta = new RegattaImpl(getRaceLogStore(), getRegattaLogStore(), fullRegattaName,
|
|
| 1292 | - getBaseDomainFactory().getOrCreateBoatClass(boatClassName), startDate, endDate, series, persistent,
|
|
| 1293 | - scoringScheme, id, courseArea, useStartTimeInference, rankingMetricConstructor);
|
|
| 1294 | - boolean wasCreated = addAndConnectRegatta(persistent, defaultCourseAreaId, regatta);
|
|
| 1295 | - if (wasCreated) {
|
|
| 1296 | - logger.info("Created regatta " + regatta.getName() + " (" + hashCode() + ") on " + this);
|
|
| 1297 | - }
|
|
| 1298 | - return new com.sap.sse.common.Util.Pair<Regatta, Boolean>(regatta, wasCreated);
|
|
| 1299 | - }
|
|
| 1300 | -
|
|
| 1301 | - private boolean addAndConnectRegatta(boolean persistent, Serializable defaultCourseAreaId, Regatta regatta) {
|
|
| 1302 | - boolean wasCreated = false;
|
|
| 1303 | - // try a quick read protected by the concurrent hash map implementation
|
|
| 1304 | - if (!regattasByName.containsKey(regatta.getName())) {
|
|
| 1305 | - LockUtil.lockForWrite(regattasByNameLock);
|
|
| 1306 | - try {
|
|
| 1307 | - // check again, now that we hold the exclusive write lock
|
|
| 1308 | - if (!regattasByName.containsKey(regatta.getName())) {
|
|
| 1309 | - wasCreated = true;
|
|
| 1310 | - logger.info("putting regatta " + regatta.getName() + " (" + regatta.hashCode()
|
|
| 1311 | - + ") into regattasByName of " + this);
|
|
| 1312 | - regattasByName.put(regatta.getName(), regatta);
|
|
| 1313 | - regatta.addRegattaListener(this);
|
|
| 1314 | - regatta.addRaceColumnListener(raceLogReplicator);
|
|
| 1315 | - regatta.addRaceColumnListener(raceLogScoringReplicator);
|
|
| 1316 | - }
|
|
| 1317 | - } finally {
|
|
| 1318 | - LockUtil.unlockAfterWrite(regattasByNameLock);
|
|
| 1319 | - }
|
|
| 1320 | - }
|
|
| 1321 | - if (persistent) {
|
|
| 1322 | - updateStoredRegatta(regatta);
|
|
| 1323 | - }
|
|
| 1324 | - for (Event event : getAllEvents()) {
|
|
| 1325 | - for (CourseArea eventCourseArea : event.getVenue().getCourseAreas()) {
|
|
| 1326 | - if (defaultCourseAreaId != null && eventCourseArea.getId().equals(defaultCourseAreaId)) {
|
|
| 1327 | - event.addRegatta(regatta);
|
|
| 1328 | - }
|
|
| 1329 | - }
|
|
| 1330 | - }
|
|
| 1331 | - return wasCreated;
|
|
| 1332 | - }
|
|
| 1333 | -
|
|
| 1334 | - @Override
|
|
| 1335 | - public void addRace(RegattaIdentifier addToRegatta, RaceDefinition raceDefinition) {
|
|
| 1336 | - Regatta regatta = getRegatta(addToRegatta);
|
|
| 1337 | - regatta.addRace(raceDefinition); // will trigger the raceAdded operation because this service is listening on
|
|
| 1338 | - // all its regattas
|
|
| 1339 | - }
|
|
| 1340 | -
|
|
| 1341 | - /**
|
|
| 1342 | - * If the <code>regatta</code> {@link Regatta#isPersistent() is a persistent one}, the association of the race with
|
|
| 1343 | - * the regatta is remembered persistently so that {@link #getRememberedRegattaForRace(Serializable)} will provide
|
|
| 1344 | - * it.
|
|
| 1345 | - */
|
|
| 1346 | - @Override
|
|
| 1347 | - public void raceAdded(Regatta regatta, RaceDefinition raceDefinition) {
|
|
| 1348 | - if (regatta.isPersistent()) {
|
|
| 1349 | - setRegattaForRace(regatta, raceDefinition);
|
|
| 1350 | - }
|
|
| 1351 | - final CourseChangeReplicator listener = new CourseChangeReplicator(this, regatta, raceDefinition);
|
|
| 1352 | - courseListeners.put(raceDefinition, listener);
|
|
| 1353 | - raceDefinition.getCourse().addCourseListener(listener);
|
|
| 1354 | - replicate(new AddRaceDefinition(regatta.getRegattaIdentifier(), raceDefinition));
|
|
| 1355 | - }
|
|
| 1356 | -
|
|
| 1357 | - @Override
|
|
| 1358 | - public void raceRemoved(Regatta regatta, RaceDefinition raceDefinition) {
|
|
| 1359 | - raceDefinition.getCourse().removeCourseListener(courseListeners.remove(raceDefinition));
|
|
| 1360 | - }
|
|
| 1361 | -
|
|
| 1362 | - private NamedReentrantReadWriteLock lockRaceTrackersById(Object trackerId) {
|
|
| 1363 | - NamedReentrantReadWriteLock lock;
|
|
| 1364 | - synchronized (raceTrackersByIDLocks) {
|
|
| 1365 | - lock = raceTrackersByIDLocks.get(trackerId);
|
|
| 1366 | - if (lock == null) {
|
|
| 1367 | - lock = new NamedReentrantReadWriteLock("raceTrackersByIDLock for " + trackerId, /* fair */false);
|
|
| 1368 | - raceTrackersByIDLocks.put(trackerId, lock);
|
|
| 1369 | - }
|
|
| 1370 | - }
|
|
| 1371 | - LockUtil.lockForWrite(lock);
|
|
| 1372 | - return lock;
|
|
| 1373 | - }
|
|
| 1374 | -
|
|
| 1375 | - /**
|
|
| 1376 | - * @param lock
|
|
| 1377 | - * need to pass the lock obtained from {@link #lockRaceTrackersById(Object)} because a competing thread
|
|
| 1378 | - * may already have removed the lock from the {@link #raceTrackersByIDLocks} map
|
|
| 1379 | - */
|
|
| 1380 | - private void unlockRaceTrackersById(Object trackerId, NamedReentrantReadWriteLock lock) {
|
|
| 1381 | - LockUtil.unlockAfterWrite(lock);
|
|
| 1382 | - synchronized (raceTrackersByIDLocks) {
|
|
| 1383 | - raceTrackersByIDLocks.remove(trackerId);
|
|
| 1384 | - }
|
|
| 1385 | - }
|
|
| 1386 | -
|
|
| 1387 | - @Override
|
|
| 1388 | - public RaceHandle addRace(RegattaIdentifier regattaToAddTo, RaceTrackingConnectivityParameters params,
|
|
| 1389 | - long timeoutInMilliseconds) throws Exception {
|
|
| 1390 | - final Object trackerID = params.getTrackerID();
|
|
| 1391 | - NamedReentrantReadWriteLock raceTrackersByIdLock = lockRaceTrackersById(trackerID);
|
|
| 1392 | - try {
|
|
| 1393 | - RaceTracker tracker = raceTrackersByID.get(trackerID);
|
|
| 1394 | - if (tracker == null) {
|
|
| 1395 | - Regatta regatta = regattaToAddTo == null ? null : getRegatta(regattaToAddTo);
|
|
| 1396 | - if (regatta == null) {
|
|
| 1397 | - // create tracker and use an existing or create a default regatta
|
|
| 1398 | - tracker = params.createRaceTracker(this, windStore, gpsFixStore, /* raceLogResolver */ this);
|
|
| 1399 | - } else {
|
|
| 1400 | - // use the regatta selected by the RaceIdentifier regattaToAddTo
|
|
| 1401 | - tracker = params.createRaceTracker(regatta, this, windStore, gpsFixStore, /* raceLogResolver */ this);
|
|
| 1402 | - assert tracker.getRegatta() == regatta;
|
|
| 1403 | - }
|
|
| 1404 | - LockUtil.lockForWrite(raceTrackersByRegattaLock);
|
|
| 1405 | - try {
|
|
| 1406 | - raceTrackersByID.put(trackerID, tracker);
|
|
| 1407 | - Set<RaceTracker> trackers = raceTrackersByRegatta.get(tracker.getRegatta());
|
|
| 1408 | - if (trackers == null) {
|
|
| 1409 | - trackers = Collections.newSetFromMap(new ConcurrentHashMap<RaceTracker, Boolean>());
|
|
| 1410 | - raceTrackersByRegatta.put(tracker.getRegatta(), trackers);
|
|
| 1411 | - }
|
|
| 1412 | - trackers.add(tracker);
|
|
| 1413 | - } finally {
|
|
| 1414 | - LockUtil.unlockAfterWrite(raceTrackersByRegattaLock);
|
|
| 1415 | - }
|
|
| 1416 | - // TODO we assume here that the regatta name is unique which necessitates adding the boat class name to
|
|
| 1417 | - // it in RegattaImpl constructor
|
|
| 1418 | - String regattaName = tracker.getRegatta().getName();
|
|
| 1419 | - Regatta regattaWithName = regattasByName.get(regattaName);
|
|
| 1420 | - // TODO we assume here that the regatta name is unique which necessitates adding the boat class name to
|
|
| 1421 | - // it in RegattaImpl constructor
|
|
| 1422 | - if (regattaWithName != null) {
|
|
| 1423 | - if (regattaWithName != tracker.getRegatta()) {
|
|
| 1424 | - if (Util.isEmpty(regattaWithName.getAllRaces())) {
|
|
| 1425 | - // probably, tracker removed the last races from the old regatta and created a new one
|
|
| 1426 | - LockUtil.lockForWrite(regattasByNameLock);
|
|
| 1427 | - try {
|
|
| 1428 | - regattasByName.remove(regattaName);
|
|
| 1429 | - cacheAndReplicateDefaultRegatta(tracker.getRegatta());
|
|
| 1430 | - } finally {
|
|
| 1431 | - LockUtil.unlockAfterWrite(regattasByNameLock);
|
|
| 1432 | - }
|
|
| 1433 | - } else {
|
|
| 1434 | - throw new RuntimeException("Internal error. Two regatta objects with equal name "
|
|
| 1435 | - + regattaName);
|
|
| 1436 | - }
|
|
| 1437 | - }
|
|
| 1438 | - } else {
|
|
| 1439 | - cacheAndReplicateDefaultRegatta(tracker.getRegatta());
|
|
| 1440 | - }
|
|
| 1441 | - } else {
|
|
| 1442 | - logger.warning("Race tracker with ID "+trackerID+" already found; not tracking twice to avoid race duplication");
|
|
| 1443 | - WindStore existingTrackersWindStore = tracker.getWindStore();
|
|
| 1444 | - if (!existingTrackersWindStore.equals(windStore)) {
|
|
| 1445 | - logger.warning("Wind store mismatch. Requested wind store: " + windStore
|
|
| 1446 | - + ". Wind store in use by existing tracker: " + existingTrackersWindStore);
|
|
| 1447 | - }
|
|
| 1448 | - GPSFixStore existingTrackersGPSFixStore = tracker.getGPSFixStore();
|
|
| 1449 | - if (!existingTrackersGPSFixStore.equals(gpsFixStore)) {
|
|
| 1450 | - logger.warning("GPSFix store mismatch. Requested GPSFix store: " + gpsFixStore
|
|
| 1451 | - + ". GPSFix store in use by existing tracker: " + existingTrackersGPSFixStore);
|
|
| 1452 | - }
|
|
| 1453 | - }
|
|
| 1454 | - if (timeoutInMilliseconds != -1) {
|
|
| 1455 | - scheduleAbortTrackerAfterInitialTimeout(tracker, timeoutInMilliseconds);
|
|
| 1456 | - }
|
|
| 1457 | - return tracker.getRacesHandle();
|
|
| 1458 | - } finally {
|
|
| 1459 | - unlockRaceTrackersById(trackerID, raceTrackersByIdLock);
|
|
| 1460 | - }
|
|
| 1461 | - }
|
|
| 1462 | -
|
|
| 1463 | - /**
|
|
| 1464 | - * The regatta and all its contained {@link Regatta#getAllRaces() races} are replicated to all replicas.
|
|
| 1465 | - *
|
|
| 1466 | - * @param regatta
|
|
| 1467 | - * the series of this regatta must not have any {@link Series#getRaceColumns() race columns associated
|
|
| 1468 | - * (yet)}.
|
|
| 1469 | - */
|
|
| 1470 | - private void replicateSpecificRegattaWithoutRaceColumns(Regatta regatta) {
|
|
| 1471 | - Serializable courseAreaId = null;
|
|
| 1472 | - if (regatta.getDefaultCourseArea() != null) {
|
|
| 1473 | - courseAreaId = regatta.getDefaultCourseArea().getId();
|
|
| 1474 | - }
|
|
| 1475 | - replicate(new AddSpecificRegatta(regatta.getName(), regatta.getBoatClass() == null ? null : regatta
|
|
| 1476 | - .getBoatClass().getName(), regatta.getStartDate(), regatta.getEndDate(), regatta.getId(),
|
|
| 1477 | - getSeriesWithoutRaceColumnsConstructionParametersAsMap(regatta), regatta.isPersistent(),
|
|
| 1478 | - regatta.getScoringScheme(), courseAreaId, regatta.useStartTimeInference(), regatta.getRankingMetricType()));
|
|
| 1479 | - RegattaIdentifier regattaIdentifier = regatta.getRegattaIdentifier();
|
|
| 1480 | - for (RaceDefinition race : regatta.getAllRaces()) {
|
|
| 1481 | - replicate(new AddRaceDefinition(regattaIdentifier, race));
|
|
| 1482 | - }
|
|
| 1483 | - }
|
|
| 1484 | -
|
|
| 1485 | - private RegattaCreationParametersDTO getSeriesWithoutRaceColumnsConstructionParametersAsMap(Regatta regatta) {
|
|
| 1486 | - LinkedHashMap<String, SeriesCreationParametersDTO> result = new LinkedHashMap<String, SeriesCreationParametersDTO>();
|
|
| 1487 | - for (Series s : regatta.getSeries()) {
|
|
| 1488 | - assert Util.isEmpty(s.getRaceColumns());
|
|
| 1489 | - List<FleetDTO> fleetNamesAndOrdering = new ArrayList<FleetDTO>();
|
|
| 1490 | - for (Fleet f : s.getFleets()) {
|
|
| 1491 | - fleetNamesAndOrdering.add(getBaseDomainFactory().convertToFleetDTO(f));
|
|
| 1492 | - }
|
|
| 1493 | - result.put(
|
|
| 1494 | - s.getName(),
|
|
| 1495 | - new SeriesCreationParametersDTO(fleetNamesAndOrdering, s.isMedal(), s.isStartsWithZeroScore(), s
|
|
| 1496 | - .isFirstColumnIsNonDiscardableCarryForward(), s.getResultDiscardingRule() == null ? null
|
|
| 1497 | - : s.getResultDiscardingRule().getDiscardIndexResultsStartingWithHowManyRaces(), s
|
|
| 1498 | - .hasSplitFleetContiguousScoring()));
|
|
| 1499 | - }
|
|
| 1500 | - return new RegattaCreationParametersDTO(result);
|
|
| 1501 | - }
|
|
| 1502 | -
|
|
| 1503 | - /**
|
|
| 1504 | - * If <code>regatta</code> is not yet in {@link #regattasByName}, it is added, this service is
|
|
| 1505 | - * {@link Regatta#addRegattaListener(RegattaListener) added} as regatta listener, and the regatta and all its
|
|
| 1506 | - * contained {@link Regatta#getAllRaces() races} are replicated to all replica.
|
|
| 1507 | - */
|
|
| 1508 | - private void cacheAndReplicateDefaultRegatta(Regatta regatta) {
|
|
| 1509 | - // try a quick read first, protected by regattasByName being a concurrent hash set
|
|
| 1510 | - if (!regattasByName.containsKey(regatta.getName())) {
|
|
| 1511 | - // now we need to obtain exclusive write access; in between, some other thread may have added a regatta by
|
|
| 1512 | - // that
|
|
| 1513 | - // name, so we need to check again:
|
|
| 1514 | - LockUtil.lockForWrite(regattasByNameLock);
|
|
| 1515 | - try {
|
|
| 1516 | - if (!regattasByName.containsKey(regatta.getName())) {
|
|
| 1517 | - logger.info("putting regatta " + regatta.getName() + " (" + regatta.hashCode()
|
|
| 1518 | - + ") into regattasByName of " + this);
|
|
| 1519 | - regattasByName.put(regatta.getName(), regatta);
|
|
| 1520 | - regatta.addRegattaListener(this);
|
|
| 1521 | - regatta.addRaceColumnListener(raceLogReplicator);
|
|
| 1522 | - regatta.addRaceColumnListener(raceLogScoringReplicator);
|
|
| 1523 | -
|
|
| 1524 | - replicate(new AddDefaultRegatta(regatta.getName(), regatta.getBoatClass() == null ? null : regatta
|
|
| 1525 | - .getBoatClass().getName(), regatta.getStartDate(), regatta.getEndDate(), regatta.getId()));
|
|
| 1526 | - RegattaIdentifier regattaIdentifier = regatta.getRegattaIdentifier();
|
|
| 1527 | - for (RaceDefinition race : regatta.getAllRaces()) {
|
|
| 1528 | - replicate(new AddRaceDefinition(regattaIdentifier, race));
|
|
| 1529 | - }
|
|
| 1530 | - }
|
|
| 1531 | - } finally {
|
|
| 1532 | - LockUtil.unlockAfterWrite(regattasByNameLock);
|
|
| 1533 | - }
|
|
| 1534 | - }
|
|
| 1535 | - }
|
|
| 1536 | -
|
|
| 1537 | - @Override
|
|
| 1538 | - public DynamicTrackedRace createTrackedRace(RegattaAndRaceIdentifier raceIdentifier, WindStore windStore,
|
|
| 1539 | - GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind,
|
|
| 1540 | - long millisecondsOverWhichToAverageSpeed, boolean useMarkPassingCalculator) {
|
|
| 1541 | - DynamicTrackedRegatta trackedRegatta = getOrCreateTrackedRegatta(getRegatta(raceIdentifier));
|
|
| 1542 | - RaceDefinition race = getRace(raceIdentifier);
|
|
| 1543 | - return trackedRegatta.createTrackedRace(race, Collections.<Sideline> emptyList(), windStore, gpsFixStore,
|
|
| 1544 | - delayToLiveInMillis, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed,
|
|
| 1545 | - /* raceDefinitionSetToUpdate */null, useMarkPassingCalculator, /* raceLogResolver */ this);
|
|
| 1546 | - }
|
|
| 1547 | -
|
|
| 1548 | - private void ensureRegattaIsObservedForDefaultLeaderboardAndAutoLeaderboardLinking(
|
|
| 1549 | - DynamicTrackedRegatta trackedRegatta) {
|
|
| 1550 | - if (regattasObservedForDefaultLeaderboard.add(trackedRegatta)) {
|
|
| 1551 | - trackedRegatta.addRaceListener(new RaceAdditionListener());
|
|
| 1552 | - }
|
|
| 1553 | - }
|
|
| 1554 | -
|
|
| 1555 | - private void stopObservingRegattaForRedaultLeaderboardAndAutoLeaderboardLinking(DynamicTrackedRegatta trackedRegatta) {
|
|
| 1556 | - regattasObservedForDefaultLeaderboard.remove(trackedRegatta);
|
|
| 1557 | - }
|
|
| 1558 | -
|
|
| 1559 | - /**
|
|
| 1560 | - * A listener class used to ensure that when a tracked race is added to any {@link TrackedRegatta} managed by this
|
|
| 1561 | - * service, the service adds the tracked race to the default leaderboard and links it to the leaderboard columns
|
|
| 1562 | - * that were previously connected to it. Additionally, a {@link RaceChangeListener} is added to the
|
|
| 1563 | - * {@link TrackedRace} which is responsible for triggering the replication of all relevant changes to the tracked
|
|
| 1564 | - * race. When a tracked race is removed, the {@link TrackedRaceReplicator} that was added as listener to that
|
|
| 1565 | - * tracked race is removed again.
|
|
| 1566 | - *
|
|
| 1567 | - * A {@link PolarFixCacheUpdater} is added to every race so that polar fixes are aggregated when new GPS fixes
|
|
| 1568 | - * arrive.
|
|
| 1569 | - *
|
|
| 1570 | - * @author Axel Uhl (d043530)
|
|
| 1571 | - *
|
|
| 1572 | - */
|
|
| 1573 | - private class RaceAdditionListener implements RaceListener, Serializable {
|
|
| 1574 | - private static final long serialVersionUID = 1036955460477000265L;
|
|
| 1575 | -
|
|
| 1576 | - private final Map<TrackedRace, TrackedRaceReplicator> trackedRaceReplicators;
|
|
| 1577 | -
|
|
| 1578 | - private final Map<TrackedRace, PolarFixCacheUpdater> polarFixCacheUpdaters;
|
|
| 1579 | -
|
|
| 1580 | - public RaceAdditionListener() {
|
|
| 1581 | - this.trackedRaceReplicators = new HashMap<TrackedRace, TrackedRaceReplicator>();
|
|
| 1582 | - this.polarFixCacheUpdaters = new HashMap<TrackedRace, PolarFixCacheUpdater>();
|
|
| 1583 | - }
|
|
| 1584 | -
|
|
| 1585 | - @Override
|
|
| 1586 | - public void raceRemoved(TrackedRace trackedRace) {
|
|
| 1587 | - TrackedRaceReplicator trackedRaceReplicator = trackedRaceReplicators.remove(trackedRace);
|
|
| 1588 | - if (trackedRaceReplicator != null) {
|
|
| 1589 | - trackedRace.removeListener(trackedRaceReplicator);
|
|
| 1590 | - }
|
|
| 1591 | - PolarFixCacheUpdater polarFixCacheUpdater = polarFixCacheUpdaters.remove(trackedRace);
|
|
| 1592 | - if (polarFixCacheUpdater != null) {
|
|
| 1593 | - trackedRace.removeListener(polarFixCacheUpdater);
|
|
| 1594 | - }
|
|
| 1595 | - }
|
|
| 1596 | -
|
|
| 1597 | - @Override
|
|
| 1598 | - public void raceAdded(TrackedRace trackedRace) {
|
|
| 1599 | - // replicate the addition of the tracked race:
|
|
| 1600 | - CreateTrackedRace op = new CreateTrackedRace(trackedRace.getRaceIdentifier(), trackedRace.getWindStore(),
|
|
| 1601 | - trackedRace.getGPSFixStore(), trackedRace.getDelayToLiveInMillis(),
|
|
| 1602 | - trackedRace.getMillisecondsOverWhichToAverageWind(),
|
|
| 1603 | - trackedRace.getMillisecondsOverWhichToAverageSpeed());
|
|
| 1604 | - replicate(op);
|
|
| 1605 | - linkRaceToConfiguredLeaderboardColumns(trackedRace);
|
|
| 1606 | - TrackedRaceReplicator trackedRaceReplicator = new TrackedRaceReplicator(trackedRace);
|
|
| 1607 | - trackedRaceReplicators.put(trackedRace, trackedRaceReplicator);
|
|
| 1608 | - trackedRace.addListener(trackedRaceReplicator, /* fire wind already loaded */true, true);
|
|
| 1609 | -
|
|
| 1610 | - PolarFixCacheUpdater polarFixCacheUpdater = new PolarFixCacheUpdater(trackedRace);
|
|
| 1611 | - polarFixCacheUpdaters.put(trackedRace, polarFixCacheUpdater);
|
|
| 1612 | - trackedRace.addListener(polarFixCacheUpdater);
|
|
| 1613 | -
|
|
| 1614 | - if (polarDataService != null) {
|
|
| 1615 | - trackedRace.setPolarDataService(polarDataService);
|
|
| 1616 | - }
|
|
| 1617 | - }
|
|
| 1618 | - }
|
|
| 1619 | -
|
|
| 1620 | - private class PolarFixCacheUpdater extends AbstractRaceChangeListener {
|
|
| 1621 | -
|
|
| 1622 | - private final TrackedRace race;
|
|
| 1623 | -
|
|
| 1624 | - public PolarFixCacheUpdater(TrackedRace race) {
|
|
| 1625 | - this.race = race;
|
|
| 1626 | - }
|
|
| 1627 | -
|
|
| 1628 | - @Override
|
|
| 1629 | - public void competitorPositionChanged(GPSFixMoving fix, Competitor item) {
|
|
| 1630 | - if (polarDataService != null) {
|
|
| 1631 | - polarDataService.competitorPositionChanged(fix, item, race);
|
|
| 1632 | - }
|
|
| 1633 | - }
|
|
| 1634 | -
|
|
| 1635 | - @Override
|
|
| 1636 | - public void statusChanged(TrackedRaceStatus newStatus, TrackedRaceStatus oldStatus) {
|
|
| 1637 | - if (oldStatus.getStatus() == TrackedRaceStatusEnum.LOADING
|
|
| 1638 | - && newStatus.getStatus() != TrackedRaceStatusEnum.LOADING) {
|
|
| 1639 | - if (polarDataService != null) {
|
|
| 1640 | - polarDataService.raceFinishedLoading(race);
|
|
| 1641 | - }
|
|
| 1642 | - }
|
|
| 1643 | - }
|
|
| 1644 | -
|
|
| 1645 | - }
|
|
| 1646 | -
|
|
| 1647 | - private class TrackedRaceReplicator implements RaceChangeListener {
|
|
| 1648 | - private final TrackedRace trackedRace;
|
|
| 1649 | -
|
|
| 1650 | - public TrackedRaceReplicator(TrackedRace trackedRace) {
|
|
| 1651 | - this.trackedRace = trackedRace;
|
|
| 1652 | - }
|
|
| 1653 | -
|
|
| 1654 | - @Override
|
|
| 1655 | - public void windSourcesToExcludeChanged(Iterable<? extends WindSource> windSourcesToExclude) {
|
|
| 1656 | - replicate(new UpdateWindSourcesToExclude(getRaceIdentifier(), windSourcesToExclude));
|
|
| 1657 | - }
|
|
| 1658 | -
|
|
| 1659 | - @Override
|
|
| 1660 | - public void startOfTrackingChanged(TimePoint startOfTracking) {
|
|
| 1661 | - replicate(new UpdateStartOfTracking(getRaceIdentifier(), startOfTracking));
|
|
| 1662 | - }
|
|
| 1663 | -
|
|
| 1664 | - @Override
|
|
| 1665 | - public void endOfTrackingChanged(TimePoint endOfTracking) {
|
|
| 1666 | - replicate(new UpdateEndOfTracking(getRaceIdentifier(), endOfTracking));
|
|
| 1667 | - }
|
|
| 1668 | -
|
|
| 1669 | - @Override
|
|
| 1670 | - public void startTimeReceivedChanged(TimePoint startTimeReceived) {
|
|
| 1671 | - replicate(new UpdateStartTimeReceived(getRaceIdentifier(), startTimeReceived));
|
|
| 1672 | - }
|
|
| 1673 | -
|
|
| 1674 | - @Override
|
|
| 1675 | - public void startOfRaceChanged(TimePoint oldStartOfRace, TimePoint newStartOfRace) {
|
|
| 1676 | - // no action required; the update signaled by this call is implicit; for explicit updates
|
|
| 1677 | - // see raceTimesChanged(TimePoint, TimePoint, TimePoint).
|
|
| 1678 | - }
|
|
| 1679 | -
|
|
| 1680 | - @Override
|
|
| 1681 | - public void waypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) {
|
|
| 1682 | - // no-op; the course change is replicated by the separate CourseChangeReplicator
|
|
| 1683 | - }
|
|
| 1684 | -
|
|
| 1685 | - @Override
|
|
| 1686 | - public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) {
|
|
| 1687 | - // no-op; the course change is replicated by the separate CourseChangeReplicator
|
|
| 1688 | - }
|
|
| 1689 | -
|
|
| 1690 | - @Override
|
|
| 1691 | - public void delayToLiveChanged(long delayToLiveInMillis) {
|
|
| 1692 | - replicate(new UpdateRaceDelayToLive(getRaceIdentifier(), delayToLiveInMillis));
|
|
| 1693 | - }
|
|
| 1694 | -
|
|
| 1695 | - @Override
|
|
| 1696 | - public void windDataReceived(Wind wind, WindSource windSource) {
|
|
| 1697 | - replicate(new RecordWindFix(getRaceIdentifier(), windSource, wind));
|
|
| 1698 | - }
|
|
| 1699 | -
|
|
| 1700 | - @Override
|
|
| 1701 | - public void windDataRemoved(Wind wind, WindSource windSource) {
|
|
| 1702 | - replicate(new RemoveWindFix(getRaceIdentifier(), windSource, wind));
|
|
| 1703 | - }
|
|
| 1704 | -
|
|
| 1705 | - @Override
|
|
| 1706 | - public void windAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) {
|
|
| 1707 | - replicate(new UpdateWindAveragingTime(getRaceIdentifier(), newMillisecondsOverWhichToAverage));
|
|
| 1708 | - }
|
|
| 1709 | -
|
|
| 1710 | - @Override
|
|
| 1711 | - public void competitorPositionChanged(GPSFixMoving fix, Competitor competitor) {
|
|
| 1712 | - replicate(new RecordCompetitorGPSFix(getRaceIdentifier(), competitor, fix));
|
|
| 1713 | - }
|
|
| 1714 | -
|
|
| 1715 | - @Override
|
|
| 1716 | - public void statusChanged(TrackedRaceStatus newStatus, TrackedRaceStatus oldStatus) {
|
|
| 1717 | - replicate(new UpdateTrackedRaceStatus(getRaceIdentifier(), newStatus));
|
|
| 1718 | - }
|
|
| 1719 | -
|
|
| 1720 | - @Override
|
|
| 1721 | - public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) {
|
|
| 1722 | - final RecordMarkGPSFix operation;
|
|
| 1723 | - if (firstInTrack) {
|
|
| 1724 | - operation = new RecordMarkGPSFixForNewMarkTrack(getRaceIdentifier(), mark, fix);
|
|
| 1725 | - } else {
|
|
| 1726 | - operation = new RecordMarkGPSFixForExistingTrack(getRaceIdentifier(), mark, fix);
|
|
| 1727 | - }
|
|
| 1728 | - replicate(operation);
|
|
| 1729 | - }
|
|
| 1730 | -
|
|
| 1731 | - @Override
|
|
| 1732 | - public void markPassingReceived(Competitor competitor, Map<Waypoint, MarkPassing> oldMarkPassings,
|
|
| 1733 | - Iterable<MarkPassing> markPassings) {
|
|
| 1734 | - replicate(new UpdateMarkPassings(getRaceIdentifier(), competitor, markPassings));
|
|
| 1735 | - }
|
|
| 1736 | -
|
|
| 1737 | - @Override
|
|
| 1738 | - public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) {
|
|
| 1739 | - replicate(new UpdateWindAveragingTime(getRaceIdentifier(), newMillisecondsOverWhichToAverage));
|
|
| 1740 | - }
|
|
| 1741 | -
|
|
| 1742 | - private RegattaAndRaceIdentifier getRaceIdentifier() {
|
|
| 1743 | - return trackedRace.getRaceIdentifier();
|
|
| 1744 | - }
|
|
| 1745 | -
|
|
| 1746 | - }
|
|
| 1747 | -
|
|
| 1748 | - /**
|
|
| 1749 | - * Based on the <code>trackedRace</code>'s {@link TrackedRace#getRaceIdentifier() race identifier}, the tracked race
|
|
| 1750 | - * is (re-)associated to all {@link RaceColumn race columns} that currently have no
|
|
| 1751 | - * {@link RaceColumn#getTrackedRace(Fleet) tracked race assigned} and whose
|
|
| 1752 | - * {@link RaceColumn#getRaceIdentifier(Fleet) race identifier} equals that of <code>trackedRace</code>.
|
|
| 1753 | - */
|
|
| 1754 | - private void linkRaceToConfiguredLeaderboardColumns(TrackedRace trackedRace) {
|
|
| 1755 | - boolean leaderboardHasChanged = false;
|
|
| 1756 | - RegattaAndRaceIdentifier trackedRaceIdentifier = trackedRace.getRaceIdentifier();
|
|
| 1757 | - for (Leaderboard leaderboard : getLeaderboards().values()) {
|
|
| 1758 | - for (RaceColumn column : leaderboard.getRaceColumns()) {
|
|
| 1759 | - for (Fleet fleet : column.getFleets()) {
|
|
| 1760 | - if (trackedRaceIdentifier.equals(column.getRaceIdentifier(fleet))
|
|
| 1761 | - && column.getTrackedRace(fleet) == null) {
|
|
| 1762 | - column.setTrackedRace(fleet, trackedRace);
|
|
| 1763 | - leaderboardHasChanged = true;
|
|
| 1764 | - replicate(new ConnectTrackedRaceToLeaderboardColumn(leaderboard.getName(), column.getName(),
|
|
| 1765 | - fleet.getName(), trackedRaceIdentifier));
|
|
| 1766 | - }
|
|
| 1767 | - }
|
|
| 1768 | - }
|
|
| 1769 | - if (leaderboardHasChanged) {
|
|
| 1770 | - // Update the corresponding groups, to keep them in sync
|
|
| 1771 | - syncGroupsAfterLeaderboardChange(leaderboard, /* doDatabaseUpdate */false);
|
|
| 1772 | - }
|
|
| 1773 | - }
|
|
| 1774 | - }
|
|
| 1775 | -
|
|
| 1776 | - @Override
|
|
| 1777 | - public void stopTracking(Regatta regatta) throws MalformedURLException, IOException, InterruptedException {
|
|
| 1778 | - final Set<RaceTracker> trackersForRegatta = raceTrackersByRegatta.get(regatta);
|
|
| 1779 | - if (trackersForRegatta != null) {
|
|
| 1780 | - for (RaceTracker raceTracker : trackersForRegatta) {
|
|
| 1781 | - final Set<RaceDefinition> races = raceTracker.getRaces();
|
|
| 1782 | - if (races != null) {
|
|
| 1783 | - for (RaceDefinition race : races) {
|
|
| 1784 | - stopTrackingWind(regatta, race);
|
|
| 1785 | - }
|
|
| 1786 | - }
|
|
| 1787 | - raceTracker.stop(/* preemptive */false);
|
|
| 1788 | - final Object trackerId = raceTracker.getID();
|
|
| 1789 | - final NamedReentrantReadWriteLock lock = lockRaceTrackersById(trackerId);
|
|
| 1790 | - try {
|
|
| 1791 | - raceTrackersByID.remove(trackerId);
|
|
| 1792 | - } finally {
|
|
| 1793 | - unlockRaceTrackersById(trackerId, lock);
|
|
| 1794 | - }
|
|
| 1795 | - raceTrackersByID.remove(trackerId);
|
|
| 1796 | - }
|
|
| 1797 | - LockUtil.lockForWrite(raceTrackersByRegattaLock);
|
|
| 1798 | - try {
|
|
| 1799 | - raceTrackersByRegatta.remove(regatta);
|
|
| 1800 | - } finally {
|
|
| 1801 | - LockUtil.unlockAfterWrite(raceTrackersByRegattaLock);
|
|
| 1802 | - }
|
|
| 1803 | - }
|
|
| 1804 | - }
|
|
| 1805 | -
|
|
| 1806 | - @Override
|
|
| 1807 | - public void stopTrackingAndRemove(Regatta regatta) throws MalformedURLException, IOException, InterruptedException {
|
|
| 1808 | - stopTracking(regatta);
|
|
| 1809 | - if (regatta != null) {
|
|
| 1810 | - if (regatta.getName() != null) {
|
|
| 1811 | - logger.info("Removing regatta " + regatta.getName() + " (" + regatta.hashCode() + ") from " + this);
|
|
| 1812 | - LockUtil.lockForWrite(regattasByNameLock);
|
|
| 1813 | - try {
|
|
| 1814 | - regattasByName.remove(regatta.getName());
|
|
| 1815 | - } finally {
|
|
| 1816 | - LockUtil.unlockAfterWrite(regattasByNameLock);
|
|
| 1817 | - }
|
|
| 1818 | - LockUtil.lockForWrite(regattaTrackingCacheLock);
|
|
| 1819 | - try {
|
|
| 1820 | - regattaTrackingCache.remove(regatta);
|
|
| 1821 | - } finally {
|
|
| 1822 | - LockUtil.unlockAfterWrite(regattaTrackingCacheLock);
|
|
| 1823 | - }
|
|
| 1824 | - regatta.removeRegattaListener(this);
|
|
| 1825 | - regatta.removeRaceColumnListener(raceLogReplicator);
|
|
| 1826 | - regatta.removeRaceColumnListener(raceLogScoringReplicator);
|
|
| 1827 | - }
|
|
| 1828 | - for (RaceDefinition race : regatta.getAllRaces()) {
|
|
| 1829 | - stopTrackingWind(regatta, race);
|
|
| 1830 | - }
|
|
| 1831 | - }
|
|
| 1832 | - }
|
|
| 1833 | -
|
|
| 1834 | - /**
|
|
| 1835 | - * The tracker will initially try to connect to the tracking infrastructure to obtain basic race master data. If
|
|
| 1836 | - * this fails after some timeout, to avoid garbage and lingering threads, the task scheduled by this method will
|
|
| 1837 | - * check after the timeout expires if race master data was successfully received. If so, the tracker continues
|
|
| 1838 | - * normally. Otherwise, the tracker is shut down orderly by calling {@link RaceTracker#stop(boolean) stopping}.
|
|
| 1839 | - *
|
|
| 1840 | - * @return the scheduled task, in case the caller wants to {@link ScheduledFuture#cancel(boolean) cancel} it, e.g.,
|
|
| 1841 | - * when the tracker is stopped or has successfully received the race
|
|
| 1842 | - */
|
|
| 1843 | - private ScheduledFuture<?> scheduleAbortTrackerAfterInitialTimeout(final RaceTracker tracker,
|
|
| 1844 | - final long timeoutInMilliseconds) {
|
|
| 1845 | - ScheduledFuture<?> task = getScheduler().schedule(new Runnable() {
|
|
| 1846 | - @Override
|
|
| 1847 | - public void run() {
|
|
| 1848 | - if (tracker.getRaces() == null || tracker.getRaces().isEmpty()) {
|
|
| 1849 | - try {
|
|
| 1850 | - Regatta regatta = tracker.getRegatta();
|
|
| 1851 | - logger.log(Level.SEVERE, "RaceDefinition for a race in regatta " + regatta.getName()
|
|
| 1852 | - + " not obtained within " + timeoutInMilliseconds
|
|
| 1853 | - + "ms. Aborting tracker for this race.");
|
|
| 1854 | - Set<RaceTracker> trackersForRegatta = raceTrackersByRegatta.get(regatta);
|
|
| 1855 | - if (trackersForRegatta != null) {
|
|
| 1856 | - trackersForRegatta.remove(tracker);
|
|
| 1857 | - }
|
|
| 1858 | - tracker.stop(/* preemptive */true);
|
|
| 1859 | - final Object trackerId = tracker.getID();
|
|
| 1860 | - final NamedReentrantReadWriteLock lock = lockRaceTrackersById(trackerId);
|
|
| 1861 | - try {
|
|
| 1862 | - raceTrackersByID.remove(trackerId);
|
|
| 1863 | - } finally {
|
|
| 1864 | - unlockRaceTrackersById(trackerId, lock);
|
|
| 1865 | - }
|
|
| 1866 | - if (trackersForRegatta == null || trackersForRegatta.isEmpty()) {
|
|
| 1867 | - stopTracking(regatta);
|
|
| 1868 | - }
|
|
| 1869 | - } catch (Exception e) {
|
|
| 1870 | - logger.log(Level.SEVERE, "scheduleAbortTrackerAfterInitialTimeout", e);
|
|
| 1871 | - e.printStackTrace();
|
|
| 1872 | - }
|
|
| 1873 | - }
|
|
| 1874 | - }
|
|
| 1875 | - }, /* delay */timeoutInMilliseconds, /* unit */TimeUnit.MILLISECONDS);
|
|
| 1876 | - return task;
|
|
| 1877 | - }
|
|
| 1878 | -
|
|
| 1879 | - @Override
|
|
| 1880 | - public void stopTracking(Regatta regatta, RaceDefinition race) throws MalformedURLException, IOException,
|
|
| 1881 | - InterruptedException {
|
|
| 1882 | - logger.info("Stopping tracking for " + race + "...");
|
|
| 1883 | - final Set<RaceTracker> trackerSet = raceTrackersByRegatta.get(regatta);
|
|
| 1884 | - if (trackerSet != null) {
|
|
| 1885 | - Iterator<RaceTracker> trackerIter = trackerSet.iterator();
|
|
| 1886 | - while (trackerIter.hasNext()) {
|
|
| 1887 | - RaceTracker raceTracker = trackerIter.next();
|
|
| 1888 | - if (raceTracker.getRaces() != null && raceTracker.getRaces().contains(race)) {
|
|
| 1889 | - logger.info("Found tracker to stop for races " + raceTracker.getRaces());
|
|
| 1890 | - raceTracker.stop(/* preemptive */false);
|
|
| 1891 | - trackerIter.remove();
|
|
| 1892 | - final Object trackerId = raceTracker.getID();
|
|
| 1893 | - final NamedReentrantReadWriteLock lock = lockRaceTrackersById(trackerId);
|
|
| 1894 | - try {
|
|
| 1895 | - raceTrackersByID.remove(trackerId);
|
|
| 1896 | - } finally {
|
|
| 1897 | - unlockRaceTrackersById(trackerId, lock);
|
|
| 1898 | - }
|
|
| 1899 | - }
|
|
| 1900 | - }
|
|
| 1901 | - } else {
|
|
| 1902 | - logger.warning("Didn't find any trackers for regatta " + regatta);
|
|
| 1903 | - }
|
|
| 1904 | - stopTrackingWind(regatta, race);
|
|
| 1905 | - // if the last tracked race was removed, confirm that tracking for the entire regatta has stopped
|
|
| 1906 | - if (trackerSet == null || trackerSet.isEmpty()) {
|
|
| 1907 | - stopTracking(regatta);
|
|
| 1908 | - }
|
|
| 1909 | - }
|
|
| 1910 | -
|
|
| 1911 | - @Override
|
|
| 1912 | - public void removeRegatta(Regatta regatta) throws MalformedURLException, IOException, InterruptedException {
|
|
| 1913 | - Set<RegattaLeaderboard> leaderboardsToRemove = new HashSet<>();
|
|
| 1914 | - for (Leaderboard leaderboard : getLeaderboards().values()) {
|
|
| 1915 | - if (leaderboard instanceof RegattaLeaderboard) {
|
|
| 1916 | - RegattaLeaderboard regattaLeaderboard = (RegattaLeaderboard) leaderboard;
|
|
| 1917 | - if (regattaLeaderboard.getRegatta() == regatta) {
|
|
| 1918 | - leaderboardsToRemove.add(regattaLeaderboard);
|
|
| 1919 | - }
|
|
| 1920 | - }
|
|
| 1921 | - }
|
|
| 1922 | - for (RegattaLeaderboard regattaLeaderboardToRemove : leaderboardsToRemove) {
|
|
| 1923 | - removeLeaderboard(regattaLeaderboardToRemove.getName());
|
|
| 1924 | - }
|
|
| 1925 | - // avoid ConcurrentModificationException by copying the races to remove:
|
|
| 1926 | - Set<RaceDefinition> racesToRemove = new HashSet<>();
|
|
| 1927 | - Util.addAll(regatta.getAllRaces(), racesToRemove);
|
|
| 1928 | - for (RaceDefinition race : racesToRemove) {
|
|
| 1929 | - removeRace(regatta, race);
|
|
| 1930 | - mongoObjectFactory.removeRegattaForRaceID(race.getName(), regatta);
|
|
| 1931 | - persistentRegattasForRaceIDs.remove(race.getId().toString());
|
|
| 1932 | - }
|
|
| 1933 | - if (regatta.isPersistent()) {
|
|
| 1934 | - mongoObjectFactory.removeRegatta(regatta);
|
|
| 1935 | - }
|
|
| 1936 | - LockUtil.lockForWrite(regattasByNameLock);
|
|
| 1937 | - try {
|
|
| 1938 | - regattasByName.remove(regatta.getName());
|
|
| 1939 | - } finally {
|
|
| 1940 | - LockUtil.unlockAfterWrite(regattasByNameLock);
|
|
| 1941 | - }
|
|
| 1942 | - regatta.removeRegattaListener(this);
|
|
| 1943 | - regatta.removeRaceColumnListener(raceLogReplicator);
|
|
| 1944 | - regatta.removeRaceColumnListener(raceLogScoringReplicator);
|
|
| 1945 | - onRegattaLikeRemoved(regatta);
|
|
| 1946 | - }
|
|
| 1947 | -
|
|
| 1948 | - @Override
|
|
| 1949 | - public void removeSeries(Series series) throws MalformedURLException, IOException, InterruptedException {
|
|
| 1950 | - Regatta regatta = series.getRegatta();
|
|
| 1951 | - regatta.removeSeries(series);
|
|
| 1952 | - if (regatta.isPersistent()) {
|
|
| 1953 | - mongoObjectFactory.storeRegatta(regatta);
|
|
| 1954 | - }
|
|
| 1955 | - }
|
|
| 1956 | -
|
|
| 1957 | - @Override
|
|
| 1958 | - public Regatta updateRegatta(RegattaIdentifier regattaIdentifier, TimePoint startDate, TimePoint endDate,
|
|
| 1959 | - Serializable newDefaultCourseAreaId, RegattaConfiguration newRegattaConfiguration,
|
|
| 1960 | - Iterable<? extends Series> series, boolean useStartTimeInference) {
|
|
| 1961 | - // We're not doing any renaming of the regatta itself, therefore we don't have to sync on the maps.
|
|
| 1962 | - Regatta regatta = getRegatta(regattaIdentifier);
|
|
| 1963 | - CourseArea newCourseArea = getCourseArea(newDefaultCourseAreaId);
|
|
| 1964 | - if (newCourseArea != regatta.getDefaultCourseArea()) {
|
|
| 1965 | - regatta.setDefaultCourseArea(newCourseArea);
|
|
| 1966 | - }
|
|
| 1967 | - regatta.setStartDate(startDate);
|
|
| 1968 | - regatta.setEndDate(endDate);
|
|
| 1969 | - if (regatta.useStartTimeInference() != useStartTimeInference) {
|
|
| 1970 | - regatta.setUseStartTimeInference(useStartTimeInference);
|
|
| 1971 | - final DynamicTrackedRegatta trackedRegatta = getTrackedRegatta(regatta);
|
|
| 1972 | - if (trackedRegatta != null) {
|
|
| 1973 | - trackedRegatta.lockTrackedRacesForRead();
|
|
| 1974 | - try {
|
|
| 1975 | - for (DynamicTrackedRace trackedRace : trackedRegatta.getTrackedRaces()) {
|
|
| 1976 | - // the start times of the regatta's tracked races now have to be re-evaluated the next time they
|
|
| 1977 | - // are queried
|
|
| 1978 | - trackedRace.invalidateStartTime();
|
|
| 1979 | - }
|
|
| 1980 | - } finally {
|
|
| 1981 | - trackedRegatta.unlockTrackedRacesAfterRead();
|
|
| 1982 | - }
|
|
| 1983 | - }
|
|
| 1984 | - }
|
|
| 1985 | - regatta.setRegattaConfiguration(newRegattaConfiguration);
|
|
| 1986 | - if (series != null) {
|
|
| 1987 | - for (Series seriesObj : series) {
|
|
| 1988 | - regatta.addSeries(seriesObj);
|
|
| 1989 | - }
|
|
| 1990 | - }
|
|
| 1991 | - regatta.adjustEventToRegattaAssociation(this);
|
|
| 1992 | - if (regatta.isPersistent()) {
|
|
| 1993 | - mongoObjectFactory.storeRegatta(regatta);
|
|
| 1994 | - }
|
|
| 1995 | - return regatta;
|
|
| 1996 | - }
|
|
| 1997 | -
|
|
| 1998 | - @Override
|
|
| 1999 | - public void removeRace(Regatta regatta, RaceDefinition race) throws MalformedURLException, IOException,
|
|
| 2000 | - InterruptedException {
|
|
| 2001 | - logger.info("Removing the race " + race + "...");
|
|
| 2002 | - stopAllTrackersForWhichRaceIsLastReachable(regatta, race);
|
|
| 2003 | - stopTrackingWind(regatta, race);
|
|
| 2004 | - TrackedRace trackedRace = getExistingTrackedRace(regatta, race);
|
|
| 2005 | - if (trackedRace != null) {
|
|
| 2006 | - TrackedRegatta trackedRegatta = getTrackedRegatta(regatta);
|
|
| 2007 | - final boolean isTrackedRacesEmpty;
|
|
| 2008 | - if (trackedRegatta != null) {
|
|
| 2009 | - trackedRegatta.lockTrackedRacesForWrite();
|
|
| 2010 | - try {
|
|
| 2011 | - trackedRegatta.removeTrackedRace(trackedRace);
|
|
| 2012 | - isTrackedRacesEmpty = Util.isEmpty(trackedRegatta.getTrackedRaces());
|
|
| 2013 | - } finally {
|
|
| 2014 | - trackedRegatta.unlockTrackedRacesAfterWrite();
|
|
| 2015 | - }
|
|
| 2016 | - } else {
|
|
| 2017 | - isTrackedRacesEmpty = false;
|
|
| 2018 | - }
|
|
| 2019 | - if (isTrackedRacesEmpty) {
|
|
| 2020 | - removeTrackedRegatta(regatta);
|
|
| 2021 | - }
|
|
| 2022 | - // remove tracked race from RaceColumns of regatta
|
|
| 2023 | - for (Series series : regatta.getSeries()) {
|
|
| 2024 | - for (RaceColumnInSeries raceColumn : series.getRaceColumns()) {
|
|
| 2025 | - for (Fleet fleet : series.getFleets()) {
|
|
| 2026 | - if (raceColumn.getTrackedRace(fleet) == trackedRace) {
|
|
| 2027 | - raceColumn.releaseTrackedRace(fleet);
|
|
| 2028 | - }
|
|
| 2029 | - }
|
|
| 2030 | - }
|
|
| 2031 | - }
|
|
| 2032 | - for (Leaderboard leaderboard : getLeaderboards().values()) {
|
|
| 2033 | - if (leaderboard instanceof FlexibleLeaderboard) { // RegattaLeaderboards have implicitly been updated by
|
|
| 2034 | - // the code above
|
|
| 2035 | - for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
|
|
| 2036 | - for (Fleet fleet : raceColumn.getFleets()) {
|
|
| 2037 | - if (raceColumn.getTrackedRace(fleet) == trackedRace) {
|
|
| 2038 | - raceColumn.releaseTrackedRace(fleet); // but leave the RaceIdentifier on the race column
|
|
| 2039 | - // untouched, e.g., for later re-load
|
|
| 2040 | - }
|
|
| 2041 | - }
|
|
| 2042 | - }
|
|
| 2043 | - }
|
|
| 2044 | - }
|
|
| 2045 | - }
|
|
| 2046 | - // remove the race from the (default) regatta if the regatta is not persistently stored
|
|
| 2047 | - regatta.removeRace(race);
|
|
| 2048 | - if (!regatta.isPersistent() && Util.isEmpty(regatta.getAllRaces())) {
|
|
| 2049 | - logger.info("Removing regatta " + regatta.getName() + " (" + regatta.hashCode() + ") from service " + this);
|
|
| 2050 | - LockUtil.lockForWrite(regattasByNameLock);
|
|
| 2051 | - try {
|
|
| 2052 | - regattasByName.remove(regatta.getName());
|
|
| 2053 | - } finally {
|
|
| 2054 | - LockUtil.unlockAfterWrite(regattasByNameLock);
|
|
| 2055 | - }
|
|
| 2056 | - regatta.removeRegattaListener(this);
|
|
| 2057 | - regatta.removeRaceColumnListener(raceLogReplicator);
|
|
| 2058 | - regatta.removeRaceColumnListener(raceLogScoringReplicator);
|
|
| 2059 | - }
|
|
| 2060 | - }
|
|
| 2061 | -
|
|
| 2062 | - /**
|
|
| 2063 | - * Doesn't stop any wind trackers
|
|
| 2064 | - */
|
|
| 2065 | - private void stopAllTrackersForWhichRaceIsLastReachable(Regatta regatta, RaceDefinition race)
|
|
| 2066 | - throws MalformedURLException, IOException, InterruptedException {
|
|
| 2067 | - if (raceTrackersByRegatta.containsKey(regatta)) {
|
|
| 2068 | - Iterator<RaceTracker> trackerIter = raceTrackersByRegatta.get(regatta).iterator();
|
|
| 2069 | - while (trackerIter.hasNext()) {
|
|
| 2070 | - RaceTracker raceTracker = trackerIter.next();
|
|
| 2071 | - if (raceTracker.getRaces() != null && raceTracker.getRaces().contains(race)) {
|
|
| 2072 | - boolean foundReachableRace = false;
|
|
| 2073 | - for (RaceDefinition raceTrackedByTracker : raceTracker.getRaces()) {
|
|
| 2074 | - if (raceTrackedByTracker != race && isReachable(regatta, raceTrackedByTracker)) {
|
|
| 2075 | - foundReachableRace = true;
|
|
| 2076 | - break;
|
|
| 2077 | - }
|
|
| 2078 | - }
|
|
| 2079 | - if (!foundReachableRace) {
|
|
| 2080 | - // firstly stop the tracker
|
|
| 2081 | - raceTracker.stop(/* preemptive */true);
|
|
| 2082 | - // remove it from the raceTrackers by Regatta
|
|
| 2083 | - trackerIter.remove();
|
|
| 2084 | - final Object trackerId = raceTracker.getID();
|
|
| 2085 | - final NamedReentrantReadWriteLock lock = lockRaceTrackersById(trackerId);
|
|
| 2086 | - try {
|
|
| 2087 | - raceTrackersByID.remove(trackerId);
|
|
| 2088 | - } finally {
|
|
| 2089 | - unlockRaceTrackersById(trackerId, lock);
|
|
| 2090 | - }
|
|
| 2091 | - // if the last tracked race was removed, remove the entire regatta
|
|
| 2092 | - if (raceTrackersByRegatta.get(regatta).isEmpty()) {
|
|
| 2093 | - stopTracking(regatta);
|
|
| 2094 | - }
|
|
| 2095 | - }
|
|
| 2096 | - }
|
|
| 2097 | - }
|
|
| 2098 | - }
|
|
| 2099 | - }
|
|
| 2100 | -
|
|
| 2101 | - private boolean isReachable(Regatta regatta, RaceDefinition race) {
|
|
| 2102 | - return Util.contains(regatta.getAllRaces(), race);
|
|
| 2103 | - }
|
|
| 2104 | -
|
|
| 2105 | - @Override
|
|
| 2106 | - public void startTrackingWind(Regatta regatta, RaceDefinition race, boolean correctByDeclination) throws Exception {
|
|
| 2107 | - for (WindTrackerFactory windTrackerFactory : getWindTrackerFactories()) {
|
|
| 2108 | - windTrackerFactory.createWindTracker(getOrCreateTrackedRegatta(regatta), race, correctByDeclination);
|
|
| 2109 | - }
|
|
| 2110 | - }
|
|
| 2111 | -
|
|
| 2112 | - @Override
|
|
| 2113 | - public void stopTrackingWind(Regatta regatta, RaceDefinition race) throws SocketException, IOException {
|
|
| 2114 | - for (WindTrackerFactory windTrackerFactory : getWindTrackerFactories()) {
|
|
| 2115 | - WindTracker windTracker = windTrackerFactory.getExistingWindTracker(race);
|
|
| 2116 | - if (windTracker != null) {
|
|
| 2117 | - windTracker.stop();
|
|
| 2118 | - }
|
|
| 2119 | - }
|
|
| 2120 | - }
|
|
| 2121 | -
|
|
| 2122 | - @Override
|
|
| 2123 | - public Iterable<com.sap.sse.common.Util.Triple<Regatta, RaceDefinition, String>> getWindTrackedRaces() {
|
|
| 2124 | - List<com.sap.sse.common.Util.Triple<Regatta, RaceDefinition, String>> result = new ArrayList<com.sap.sse.common.Util.Triple<Regatta, RaceDefinition, String>>();
|
|
| 2125 | - for (Regatta regatta : getAllRegattas()) {
|
|
| 2126 | - for (RaceDefinition race : regatta.getAllRaces()) {
|
|
| 2127 | - for (WindTrackerFactory windTrackerFactory : getWindTrackerFactories()) {
|
|
| 2128 | - WindTracker windTracker = windTrackerFactory.getExistingWindTracker(race);
|
|
| 2129 | - if (windTracker != null) {
|
|
| 2130 | - result.add(new com.sap.sse.common.Util.Triple<Regatta, RaceDefinition, String>(regatta, race,
|
|
| 2131 | - windTracker.toString()));
|
|
| 2132 | - }
|
|
| 2133 | - }
|
|
| 2134 | - }
|
|
| 2135 | - }
|
|
| 2136 | - return result;
|
|
| 2137 | - }
|
|
| 2138 | -
|
|
| 2139 | - @Override
|
|
| 2140 | - public DynamicTrackedRace getTrackedRace(Regatta regatta, RaceDefinition race) {
|
|
| 2141 | - return getOrCreateTrackedRegatta(regatta).getTrackedRace(race);
|
|
| 2142 | - }
|
|
| 2143 | -
|
|
| 2144 | - private DynamicTrackedRace getExistingTrackedRace(Regatta regatta, RaceDefinition race) {
|
|
| 2145 | - return getOrCreateTrackedRegatta(regatta).getExistingTrackedRace(race);
|
|
| 2146 | - }
|
|
| 2147 | -
|
|
| 2148 | - @Override
|
|
| 2149 | - public DynamicTrackedRegatta getOrCreateTrackedRegatta(Regatta regatta) {
|
|
| 2150 | - cacheAndReplicateDefaultRegatta(regatta);
|
|
| 2151 | - LockUtil.lockForWrite(regattaTrackingCacheLock);
|
|
| 2152 | - try {
|
|
| 2153 | - DynamicTrackedRegatta result = regattaTrackingCache.get(regatta);
|
|
| 2154 | - if (result == null) {
|
|
| 2155 | - logger.info("Creating DynamicTrackedRegattaImpl for regatta " + regatta.getName() + " with hashCode "
|
|
| 2156 | - + regatta.hashCode());
|
|
| 2157 | - result = new DynamicTrackedRegattaImpl(regatta);
|
|
| 2158 | - replicate(new TrackRegatta(regatta.getRegattaIdentifier()));
|
|
| 2159 | - regattaTrackingCache.put(regatta, result);
|
|
| 2160 | - ensureRegattaIsObservedForDefaultLeaderboardAndAutoLeaderboardLinking(result);
|
|
| 2161 | - }
|
|
| 2162 | - return result;
|
|
| 2163 | - } finally {
|
|
| 2164 | - LockUtil.unlockAfterWrite(regattaTrackingCacheLock);
|
|
| 2165 | - }
|
|
| 2166 | - }
|
|
| 2167 | -
|
|
| 2168 | - @Override
|
|
| 2169 | - public DynamicTrackedRegatta getTrackedRegatta(com.sap.sailing.domain.base.Regatta regatta) {
|
|
| 2170 | - return regattaTrackingCache.get(regatta);
|
|
| 2171 | - }
|
|
| 2172 | -
|
|
| 2173 | - @Override
|
|
| 2174 | - public void removeTrackedRegatta(Regatta regatta) {
|
|
| 2175 | - logger.info("Removing regatta " + regatta.getName() + " from regattaTrackingCache");
|
|
| 2176 | - final DynamicTrackedRegatta trackedRegatta;
|
|
| 2177 | - LockUtil.lockForWrite(regattaTrackingCacheLock);
|
|
| 2178 | - try {
|
|
| 2179 | - trackedRegatta = regattaTrackingCache.remove(regatta);
|
|
| 2180 | - } finally {
|
|
| 2181 | - LockUtil.unlockAfterWrite(regattaTrackingCacheLock);
|
|
| 2182 | - }
|
|
| 2183 | - stopObservingRegattaForRedaultLeaderboardAndAutoLeaderboardLinking(trackedRegatta);
|
|
| 2184 | - }
|
|
| 2185 | -
|
|
| 2186 | - @Override
|
|
| 2187 | - public Regatta getRegatta(RegattaName regattaName) {
|
|
| 2188 | - return (Regatta) regattasByName.get(regattaName.getRegattaName());
|
|
| 2189 | - }
|
|
| 2190 | -
|
|
| 2191 | - @Override
|
|
| 2192 | - public Regatta getRegatta(RegattaIdentifier regattaIdentifier) {
|
|
| 2193 | - return (Regatta) regattaIdentifier.getRegatta(this);
|
|
| 2194 | - }
|
|
| 2195 | -
|
|
| 2196 | - @Override
|
|
| 2197 | - public DynamicTrackedRace getTrackedRace(RegattaAndRaceIdentifier raceIdentifier) {
|
|
| 2198 | - DynamicTrackedRace result = null;
|
|
| 2199 | - Regatta regatta = regattasByName.get(raceIdentifier.getRegattaName());
|
|
| 2200 | - if (regatta != null) {
|
|
| 2201 | - DynamicTrackedRegatta trackedRegatta = regattaTrackingCache.get(regatta);
|
|
| 2202 | - if (trackedRegatta != null) {
|
|
| 2203 | - RaceDefinition race = getRace(raceIdentifier);
|
|
| 2204 | - if (race != null) {
|
|
| 2205 | - result = trackedRegatta.getTrackedRace(race);
|
|
| 2206 | - }
|
|
| 2207 | - }
|
|
| 2208 | - }
|
|
| 2209 | - return result;
|
|
| 2210 | - }
|
|
| 2211 | -
|
|
| 2212 | - @Override
|
|
| 2213 | - public DynamicTrackedRace getExistingTrackedRace(RegattaAndRaceIdentifier raceIdentifier) {
|
|
| 2214 | - Regatta regatta = getRegattaByName(raceIdentifier.getRegattaName());
|
|
| 2215 | - DynamicTrackedRace trackedRace = null;
|
|
| 2216 | - if (regatta != null) {
|
|
| 2217 | - RaceDefinition race = regatta.getRaceByName(raceIdentifier.getRaceName());
|
|
| 2218 | - trackedRace = getOrCreateTrackedRegatta(regatta).getExistingTrackedRace(race);
|
|
| 2219 | - }
|
|
| 2220 | - return trackedRace;
|
|
| 2221 | - }
|
|
| 2222 | -
|
|
| 2223 | - @Override
|
|
| 2224 | - public RaceDefinition getRace(RegattaAndRaceIdentifier regattaNameAndRaceName) {
|
|
| 2225 | - RaceDefinition result = null;
|
|
| 2226 | - Regatta regatta = getRegatta(regattaNameAndRaceName);
|
|
| 2227 | - if (regatta != null) {
|
|
| 2228 | - result = regatta.getRaceByName(regattaNameAndRaceName.getRaceName());
|
|
| 2229 | - }
|
|
| 2230 | - return result;
|
|
| 2231 | - }
|
|
| 2232 | -
|
|
| 2233 | - @Override
|
|
| 2234 | - public Map<String, LeaderboardGroup> getLeaderboardGroups() {
|
|
| 2235 | - return Collections.unmodifiableMap(new HashMap<String, LeaderboardGroup>(leaderboardGroupsByName));
|
|
| 2236 | - }
|
|
| 2237 | -
|
|
| 2238 | - @Override
|
|
| 2239 | - public LeaderboardGroup getLeaderboardGroupByName(String groupName) {
|
|
| 2240 | - return leaderboardGroupsByName.get(groupName);
|
|
| 2241 | - }
|
|
| 2242 | -
|
|
| 2243 | - @Override
|
|
| 2244 | - public LeaderboardGroup getLeaderboardGroupByID(UUID leaderboardGroupID) {
|
|
| 2245 | - return leaderboardGroupsByID.get(leaderboardGroupID);
|
|
| 2246 | - }
|
|
| 2247 | -
|
|
| 2248 | - @Override
|
|
| 2249 | - public LeaderboardGroup addLeaderboardGroup(UUID id, String groupName, String description, String displayName,
|
|
| 2250 | - boolean displayGroupsInReverseOrder, List<String> leaderboardNames,
|
|
| 2251 | - int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType) {
|
|
| 2252 | - ArrayList<Leaderboard> leaderboards = new ArrayList<>();
|
|
| 2253 | - for (String leaderboardName : leaderboardNames) {
|
|
| 2254 | - Leaderboard leaderboard = leaderboardsByName.get(leaderboardName);
|
|
| 2255 | - if (leaderboard == null) {
|
|
| 2256 | - throw new IllegalArgumentException("No leaderboard with name " + leaderboardName + " found");
|
|
| 2257 | - } else {
|
|
| 2258 | - leaderboards.add(leaderboard);
|
|
| 2259 | - }
|
|
| 2260 | - }
|
|
| 2261 | - LeaderboardGroup result = new LeaderboardGroupImpl(id, groupName, description, displayName,
|
|
| 2262 | - displayGroupsInReverseOrder, leaderboards);
|
|
| 2263 | - if (overallLeaderboardScoringSchemeType != null) {
|
|
| 2264 | - // create overall leaderboard and its discards settings
|
|
| 2265 | - addOverallLeaderboardToLeaderboardGroup(result,
|
|
| 2266 | - getBaseDomainFactory().createScoringScheme(overallLeaderboardScoringSchemeType),
|
|
| 2267 | - overallLeaderboardDiscardThresholds);
|
|
| 2268 | - }
|
|
| 2269 | - LockUtil.lockForWrite(leaderboardGroupsByNameLock);
|
|
| 2270 | - try {
|
|
| 2271 | - if (leaderboardGroupsByName.containsKey(groupName)) {
|
|
| 2272 | - throw new IllegalArgumentException("Leaderboard group with name " + groupName + " already exists");
|
|
| 2273 | - }
|
|
| 2274 | - leaderboardGroupsByName.put(groupName, result);
|
|
| 2275 | - leaderboardGroupsByID.put(result.getId(), result);
|
|
| 2276 | - } finally {
|
|
| 2277 | - LockUtil.unlockAfterWrite(leaderboardGroupsByNameLock);
|
|
| 2278 | - }
|
|
| 2279 | - mongoObjectFactory.storeLeaderboardGroup(result);
|
|
| 2280 | - return result;
|
|
| 2281 | - }
|
|
| 2282 | -
|
|
| 2283 | - @Override
|
|
| 2284 | - public void addLeaderboardGroupWithoutReplication(LeaderboardGroup leaderboardGroup) {
|
|
| 2285 | - LockUtil.lockForWrite(leaderboardGroupsByNameLock);
|
|
| 2286 | - try {
|
|
| 2287 | - String groupName = leaderboardGroup.getName();
|
|
| 2288 | - if (leaderboardGroupsByName.containsKey(groupName)) {
|
|
| 2289 | - throw new IllegalArgumentException("Leaderboard group with name " + groupName + " already exists");
|
|
| 2290 | - }
|
|
| 2291 | - leaderboardGroupsByName.put(groupName, leaderboardGroup);
|
|
| 2292 | - leaderboardGroupsByID.put(leaderboardGroup.getId(), leaderboardGroup);
|
|
| 2293 | - } finally {
|
|
| 2294 | - LockUtil.unlockAfterWrite(leaderboardGroupsByNameLock);
|
|
| 2295 | - }
|
|
| 2296 | - if (leaderboardGroup.hasOverallLeaderboard()) {
|
|
| 2297 | - addLeaderboard(leaderboardGroup.getOverallLeaderboard());
|
|
| 2298 | - }
|
|
| 2299 | - mongoObjectFactory.storeLeaderboardGroup(leaderboardGroup);
|
|
| 2300 | - }
|
|
| 2301 | -
|
|
| 2302 | - @Override
|
|
| 2303 | - public void removeLeaderboardGroup(String groupName) {
|
|
| 2304 | - final LeaderboardGroup leaderboardGroup;
|
|
| 2305 | - LockUtil.lockForWrite(leaderboardGroupsByNameLock);
|
|
| 2306 | - try {
|
|
| 2307 | - leaderboardGroup = leaderboardGroupsByName.remove(groupName);
|
|
| 2308 | - if (leaderboardGroup != null) {
|
|
| 2309 | - leaderboardGroupsByID.remove(leaderboardGroup.getId());
|
|
| 2310 | - }
|
|
| 2311 | - } finally {
|
|
| 2312 | - LockUtil.unlockAfterWrite(leaderboardGroupsByNameLock);
|
|
| 2313 | - }
|
|
| 2314 | - mongoObjectFactory.removeLeaderboardGroup(groupName);
|
|
| 2315 | - if (leaderboardGroup != null && leaderboardGroup.getOverallLeaderboard() != null) {
|
|
| 2316 | - removeLeaderboard(leaderboardGroup.getOverallLeaderboard().getName());
|
|
| 2317 | - }
|
|
| 2318 | - }
|
|
| 2319 | -
|
|
| 2320 | - @Override
|
|
| 2321 | - public void renameLeaderboardGroup(String oldName, String newName) {
|
|
| 2322 | - LockUtil.lockForWrite(leaderboardGroupsByNameLock);
|
|
| 2323 | - try {
|
|
| 2324 | - final LeaderboardGroup toRename = leaderboardGroupsByName.get(oldName);
|
|
| 2325 | - if (toRename == null) {
|
|
| 2326 | - throw new IllegalArgumentException("No leaderboard group with name " + oldName + " found");
|
|
| 2327 | - }
|
|
| 2328 | - if (leaderboardGroupsByName.containsKey(newName)) {
|
|
| 2329 | - throw new IllegalArgumentException("Leaderboard group with name " + newName + " already exists");
|
|
| 2330 | - }
|
|
| 2331 | - leaderboardGroupsByName.remove(oldName);
|
|
| 2332 | - toRename.setName(newName);
|
|
| 2333 | - leaderboardGroupsByName.put(newName, toRename);
|
|
| 2334 | - } finally {
|
|
| 2335 | - LockUtil.unlockAfterWrite(leaderboardGroupsByNameLock);
|
|
| 2336 | - }
|
|
| 2337 | - mongoObjectFactory.renameLeaderboardGroup(oldName, newName);
|
|
| 2338 | - }
|
|
| 2339 | -
|
|
| 2340 | - @Override
|
|
| 2341 | - public void updateLeaderboardGroup(String oldName, String newName, String description, String displayName,
|
|
| 2342 | - List<String> leaderboardNames, int[] overallLeaderboardDiscardThresholds,
|
|
| 2343 | - ScoringSchemeType overallLeaderboardScoringSchemeType) {
|
|
| 2344 | - if (!oldName.equals(newName)) {
|
|
| 2345 | - renameLeaderboardGroup(oldName, newName);
|
|
| 2346 | - }
|
|
| 2347 | - LeaderboardGroup group = getLeaderboardGroupByName(newName);
|
|
| 2348 | - if (!description.equals(group.getDescription())) {
|
|
| 2349 | - group.setDescriptiom(description);
|
|
| 2350 | - }
|
|
| 2351 | - if (!Util.equalsWithNull(displayName, group.getDisplayName())) {
|
|
| 2352 | - group.setDisplayName(displayName);
|
|
| 2353 | - }
|
|
| 2354 | - group.clearLeaderboards();
|
|
| 2355 | - for (String leaderboardName : leaderboardNames) {
|
|
| 2356 | - Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
|
|
| 2357 | - if (leaderboard != null) {
|
|
| 2358 | - group.addLeaderboard(leaderboard);
|
|
| 2359 | - }
|
|
| 2360 | - }
|
|
| 2361 | - Leaderboard overallLeaderboard = group.getOverallLeaderboard();
|
|
| 2362 | - if (overallLeaderboard != null) {
|
|
| 2363 | - if (overallLeaderboardScoringSchemeType == null) {
|
|
| 2364 | - group.setOverallLeaderboard(null);
|
|
| 2365 | - removeLeaderboard(overallLeaderboard.getName());
|
|
| 2366 | - } else {
|
|
| 2367 | - // update existing overall leaderboard's discards settings; scoring scheme cannot be updated in-place
|
|
| 2368 | - overallLeaderboard.setCrossLeaderboardResultDiscardingRule(new ThresholdBasedResultDiscardingRuleImpl(
|
|
| 2369 | - overallLeaderboardDiscardThresholds));
|
|
| 2370 | - updateStoredLeaderboard(overallLeaderboard);
|
|
| 2371 | - }
|
|
| 2372 | - } else if (overallLeaderboard == null && overallLeaderboardScoringSchemeType != null) {
|
|
| 2373 | - addOverallLeaderboardToLeaderboardGroup(group,
|
|
| 2374 | - getBaseDomainFactory().createScoringScheme(overallLeaderboardScoringSchemeType),
|
|
| 2375 | - overallLeaderboardDiscardThresholds);
|
|
| 2376 | - }
|
|
| 2377 | - updateStoredLeaderboardGroup(group);
|
|
| 2378 | - }
|
|
| 2379 | -
|
|
| 2380 | - private void addOverallLeaderboardToLeaderboardGroup(LeaderboardGroup leaderboardGroup,
|
|
| 2381 | - ScoringScheme scoringScheme, int[] discardThresholds) {
|
|
| 2382 | - Leaderboard overallLeaderboard = new LeaderboardGroupMetaLeaderboard(leaderboardGroup, scoringScheme,
|
|
| 2383 | - new ThresholdBasedResultDiscardingRuleImpl(discardThresholds));
|
|
| 2384 | - leaderboardGroup.setOverallLeaderboard(overallLeaderboard);
|
|
| 2385 | - addLeaderboard(overallLeaderboard);
|
|
| 2386 | - updateStoredLeaderboard(overallLeaderboard);
|
|
| 2387 | - }
|
|
| 2388 | -
|
|
| 2389 | - @Override
|
|
| 2390 | - public void updateStoredLeaderboardGroup(LeaderboardGroup leaderboardGroup) {
|
|
| 2391 | - mongoObjectFactory.storeLeaderboardGroup(leaderboardGroup);
|
|
| 2392 | - }
|
|
| 2393 | -
|
|
| 2394 | - private ScheduledExecutorService getScheduler() {
|
|
| 2395 | - return scheduler;
|
|
| 2396 | - }
|
|
| 2397 | -
|
|
| 2398 | - @Override
|
|
| 2399 | - public ObjectInputStream createObjectInputStreamResolvingAgainstCache(InputStream is) throws IOException {
|
|
| 2400 | - return getBaseDomainFactory().createObjectInputStreamResolvingAgainstThisFactory(is);
|
|
| 2401 | - }
|
|
| 2402 | -
|
|
| 2403 | - @Override
|
|
| 2404 | - public ClassLoader getDeserializationClassLoader() {
|
|
| 2405 | - return joinedClassLoader;
|
|
| 2406 | - }
|
|
| 2407 | -
|
|
| 2408 | - @Override
|
|
| 2409 | - public Serializable getId() {
|
|
| 2410 | - return getClass().getName();
|
|
| 2411 | - }
|
|
| 2412 | -
|
|
| 2413 | - @Override
|
|
| 2414 | - public Iterable<OperationExecutionListener<RacingEventService>> getOperationExecutionListeners() {
|
|
| 2415 | - return operationExecutionListeners.keySet();
|
|
| 2416 | - }
|
|
| 2417 | -
|
|
| 2418 | - @Override
|
|
| 2419 | - public void addOperationExecutionListener(OperationExecutionListener<RacingEventService> listener) {
|
|
| 2420 | - operationExecutionListeners.put(listener, listener);
|
|
| 2421 | - }
|
|
| 2422 | -
|
|
| 2423 | - @Override
|
|
| 2424 | - public void removeOperationExecutionListener(OperationExecutionListener<RacingEventService> listener) {
|
|
| 2425 | - operationExecutionListeners.remove(listener);
|
|
| 2426 | - }
|
|
| 2427 | -
|
|
| 2428 | - @Override
|
|
| 2429 | - public void serializeForInitialReplicationInternal(ObjectOutputStream oos) throws IOException {
|
|
| 2430 | - StringBuffer logoutput = new StringBuffer();
|
|
| 2431 | -
|
|
| 2432 | - logger.info("Serializing regattas...");
|
|
| 2433 | - oos.writeObject(regattasByName);
|
|
| 2434 | - logoutput.append("Serialized " + regattasByName.size() + " regattas\n");
|
|
| 2435 | - for (Regatta regatta : regattasByName.values()) {
|
|
| 2436 | - logoutput.append(String.format("%3s\n", regatta.toString()));
|
|
| 2437 | - }
|
|
| 2438 | -
|
|
| 2439 | - logger.info("Serializing events...");
|
|
| 2440 | - oos.writeObject(eventsById);
|
|
| 2441 | - logoutput.append("\nSerialized " + eventsById.size() + " events\n");
|
|
| 2442 | - for (Event event : eventsById.values()) {
|
|
| 2443 | - logoutput.append(String.format("%3s\n", event.toString()));
|
|
| 2444 | - }
|
|
| 2445 | -
|
|
| 2446 | - logger.info("Serializing regattas observed...");
|
|
| 2447 | - oos.writeObject(regattasObservedForDefaultLeaderboard);
|
|
| 2448 | - logger.info("Serializing regatta tracking cache...");
|
|
| 2449 | - oos.writeObject(regattaTrackingCache);
|
|
| 2450 | - logger.info("Serializing leaderboard groups...");
|
|
| 2451 | - oos.writeObject(leaderboardGroupsByName);
|
|
| 2452 | - logoutput.append("Serialized " + leaderboardGroupsByName.size() + " leaderboard groups\n");
|
|
| 2453 | - for (LeaderboardGroup lg : leaderboardGroupsByName.values()) {
|
|
| 2454 | - logoutput.append(String.format("%3s\n", lg.toString()));
|
|
| 2455 | - }
|
|
| 2456 | - logger.info("Serializing leaderboards...");
|
|
| 2457 | - oos.writeObject(leaderboardsByName);
|
|
| 2458 | - logoutput.append("Serialized " + leaderboardsByName.size() + " leaderboards\n");
|
|
| 2459 | - for (Leaderboard lg : leaderboardsByName.values()) {
|
|
| 2460 | - logoutput.append(String.format("%3s\n", lg.toString()));
|
|
| 2461 | - }
|
|
| 2462 | - logger.info("Serializing media library...");
|
|
| 2463 | - mediaLibrary.serialize(oos);
|
|
| 2464 | - logoutput.append("Serialized " + mediaLibrary.allTracks().size() + " media tracks\n");
|
|
| 2465 | - for (MediaTrack lg : mediaLibrary.allTracks()) {
|
|
| 2466 | - logoutput.append(String.format("%3s\n", lg.toString()));
|
|
| 2467 | - }
|
|
| 2468 | - logger.info("Serializing persisted competitors...");
|
|
| 2469 | - oos.writeObject(competitorStore);
|
|
| 2470 | - logoutput.append("Serialized " + competitorStore.size() + " persisted competitors\n");
|
|
| 2471 | -
|
|
| 2472 | - logger.info("Serializing configuration map...");
|
|
| 2473 | - oos.writeObject(configurationMap);
|
|
| 2474 | - logoutput.append("Serialized " + configurationMap.size() + " configuration entries\n");
|
|
| 2475 | - for (DeviceConfigurationMatcher matcher : configurationMap.keySet()) {
|
|
| 2476 | - logoutput.append(String.format("%3s\n", matcher.toString()));
|
|
| 2477 | - }
|
|
| 2478 | -
|
|
| 2479 | - logger.info("Serializing remote sailing server references...");
|
|
| 2480 | - final ArrayList<RemoteSailingServerReference> remoteServerReferences = new ArrayList<>(remoteSailingServerSet
|
|
| 2481 | - .getCachedEventsForRemoteSailingServers().keySet());
|
|
| 2482 | - oos.writeObject(remoteServerReferences);
|
|
| 2483 | - logoutput.append("Serialized " + remoteServerReferences.size() + " remote sailing server references\n");
|
|
| 2484 | -
|
|
| 2485 | - logger.info(logoutput.toString());
|
|
| 2486 | - }
|
|
| 2487 | -
|
|
| 2488 | - @SuppressWarnings("unchecked")
|
|
| 2489 | - // all the casts of ois.readObject()'s return value to Map<..., ...>
|
|
| 2490 | - // the type-parameters in the casts of the de-serialized collection objects can't be checked
|
|
| 2491 | - @Override
|
|
| 2492 | - public void initiallyFillFromInternal(ObjectInputStream ois) throws IOException, ClassNotFoundException,
|
|
| 2493 | - InterruptedException {
|
|
| 2494 | - logger.info("Performing initial replication load on " + this);
|
|
| 2495 | - // Use this object's class's class loader as the context class loader which will then be used for
|
|
| 2496 | - // de-serialization; this will cause all classes to be visible that this bundle
|
|
| 2497 | - // (com.sap.sailing.server) can see
|
|
| 2498 | - StringBuffer logoutput = new StringBuffer();
|
|
| 2499 | - logger.info("Reading all regattas...");
|
|
| 2500 | - regattasByName.putAll((Map<String, Regatta>) ois.readObject());
|
|
| 2501 | - logoutput.append("Received " + regattasByName.size() + " NEW regattas\n");
|
|
| 2502 | - for (Regatta regatta : regattasByName.values()) {
|
|
| 2503 | - logoutput.append(String.format("%3s\n", regatta.toString()));
|
|
| 2504 | - }
|
|
| 2505 | -
|
|
| 2506 | - logger.info("Reading all events...");
|
|
| 2507 | - eventsById.putAll((Map<Serializable, Event>) ois.readObject());
|
|
| 2508 | - logoutput.append("\nReceived " + eventsById.size() + " NEW events\n");
|
|
| 2509 | - for (Event event : eventsById.values()) {
|
|
| 2510 | - logoutput.append(String.format("%3s\n", event.toString()));
|
|
| 2511 | - }
|
|
| 2512 | -
|
|
| 2513 | - // it is important that the leaderboards and tracked regattas are cleared before auto-linking to
|
|
| 2514 | - // old leaderboards takes place which then don't match the new ones
|
|
| 2515 | - logger.info("Reading all dynamic tracked regattas...");
|
|
| 2516 | - for (DynamicTrackedRegatta trackedRegattaToObserve : (Set<DynamicTrackedRegatta>) ois.readObject()) {
|
|
| 2517 | - ensureRegattaIsObservedForDefaultLeaderboardAndAutoLeaderboardLinking(trackedRegattaToObserve);
|
|
| 2518 | - }
|
|
| 2519 | -
|
|
| 2520 | - logger.info("Reading all of the regatta tracking cache...");
|
|
| 2521 | - regattaTrackingCache.putAll((Map<Regatta, DynamicTrackedRegatta>) ois.readObject());
|
|
| 2522 | - logoutput.append("Received " + regattaTrackingCache.size() + " NEW regatta tracking cache entries\n");
|
|
| 2523 | -
|
|
| 2524 | - logger.info("Reading leaderboard groups...");
|
|
| 2525 | - leaderboardGroupsByName.putAll((Map<String, LeaderboardGroup>) ois.readObject());
|
|
| 2526 | - logoutput.append("Received " + leaderboardGroupsByName.size() + " NEW leaderboard groups\n");
|
|
| 2527 | - for (LeaderboardGroup lg : leaderboardGroupsByName.values()) {
|
|
| 2528 | - leaderboardGroupsByID.put(lg.getId(), lg);
|
|
| 2529 | - logoutput.append(String.format("%3s\n", lg.toString()));
|
|
| 2530 | - }
|
|
| 2531 | -
|
|
| 2532 | - logger.info("Reading leaderboards by name...");
|
|
| 2533 | - leaderboardsByName.putAll((Map<String, Leaderboard>) ois.readObject());
|
|
| 2534 | - logoutput.append("Received " + leaderboardsByName.size() + " NEW leaderboards\n");
|
|
| 2535 | - for (Leaderboard leaderboard : leaderboardsByName.values()) {
|
|
| 2536 | - logoutput.append(String.format("%3s\n", leaderboard.toString()));
|
|
| 2537 | - }
|
|
| 2538 | -
|
|
| 2539 | - // now fix ScoreCorrectionListener setup for LeaderboardGroupMetaLeaderboard instances:
|
|
| 2540 | - for (Leaderboard leaderboard : leaderboardsByName.values()) {
|
|
| 2541 | - if (leaderboard instanceof LeaderboardGroupMetaLeaderboard) {
|
|
| 2542 | - ((LeaderboardGroupMetaLeaderboard) leaderboard)
|
|
| 2543 | - .registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards();
|
|
| 2544 | - } else if (leaderboard instanceof FlexibleLeaderboard) {
|
|
| 2545 | - // and re-establish the RaceLogReplicator as listener on FlexibleLeaderboard objects
|
|
| 2546 | - leaderboard.addRaceColumnListener(raceLogReplicator);
|
|
| 2547 | - }
|
|
| 2548 | - }
|
|
| 2549 | -
|
|
| 2550 | - logger.info("Reading media library...");
|
|
| 2551 | - mediaLibrary.deserialize(ois);
|
|
| 2552 | - logoutput.append("Received " + mediaLibrary.allTracks().size() + " NEW media tracks\n");
|
|
| 2553 | - for (MediaTrack mediatrack : mediaLibrary.allTracks()) {
|
|
| 2554 | - logoutput.append(String.format("%3s\n", mediatrack.toString()));
|
|
| 2555 | - }
|
|
| 2556 | -
|
|
| 2557 | - // only copy the competitors from the deserialized competitor store; don't use it because it will have set
|
|
| 2558 | - // a default Mongo object factory
|
|
| 2559 | - logger.info("Reading competitors...");
|
|
| 2560 | - for (Competitor competitor : ((CompetitorStore) ois.readObject()).getCompetitors()) {
|
|
| 2561 | - DynamicCompetitor dynamicCompetitor = (DynamicCompetitor) competitor;
|
|
| 2562 | - // the following should actually be redundant because during de-serialization the Competitor objects,
|
|
| 2563 | - // whose classes implement IsManagedByCache, should already have been got/created from/in the
|
|
| 2564 | - // competitor store
|
|
| 2565 | - competitorStore.getOrCreateCompetitor(dynamicCompetitor.getId(), dynamicCompetitor.getName(),
|
|
| 2566 | - dynamicCompetitor.getColor(), dynamicCompetitor.getEmail(), dynamicCompetitor.getFlagImage(),
|
|
| 2567 | - dynamicCompetitor.getTeam(), dynamicCompetitor.getBoat(), dynamicCompetitor.getTimeOnTimeFactor(),
|
|
| 2568 | - dynamicCompetitor.getTimeOnDistanceAllowancePerNauticalMile());
|
|
| 2569 | - }
|
|
| 2570 | - logoutput.append("Received " + competitorStore.size() + " NEW competitors\n");
|
|
| 2571 | -
|
|
| 2572 | - logger.info("Reading device configurations...");
|
|
| 2573 | - configurationMap.putAll((DeviceConfigurationMapImpl) ois.readObject());
|
|
| 2574 | - logoutput.append("Received " + configurationMap.size() + " NEW configuration entries\n");
|
|
| 2575 | - for (DeviceConfigurationMatcher matcher : configurationMap.keySet()) {
|
|
| 2576 | - logoutput.append(String.format("%3s\n", matcher.toString()));
|
|
| 2577 | - }
|
|
| 2578 | -
|
|
| 2579 | - logger.info("Reading remote sailing server references...");
|
|
| 2580 | - for (RemoteSailingServerReference remoteSailingServerReference : (Iterable<RemoteSailingServerReference>) ois
|
|
| 2581 | - .readObject()) {
|
|
| 2582 | - remoteSailingServerSet.add(remoteSailingServerReference);
|
|
| 2583 | - logoutput.append("Received remote sailing server reference " + remoteSailingServerReference);
|
|
| 2584 | - }
|
|
| 2585 | -
|
|
| 2586 | - // make sure to initialize listeners correctly
|
|
| 2587 | - for (Regatta regatta : regattasByName.values()) {
|
|
| 2588 | - RegattaImpl regattaImpl = (RegattaImpl) regatta;
|
|
| 2589 | - regattaImpl.initializeSeriesAfterDeserialize();
|
|
| 2590 | - regattaImpl.addRaceColumnListener(raceLogReplicator);
|
|
| 2591 | - }
|
|
| 2592 | - // re-establish RaceLogResolver references to this RacingEventService in all TrackedRace instances
|
|
| 2593 | - for (DynamicTrackedRegatta trackedRegatta : regattaTrackingCache.values()) {
|
|
| 2594 | - trackedRegatta.lockTrackedRacesForRead();
|
|
| 2595 | - try {
|
|
| 2596 | - for (TrackedRace trackedRace : trackedRegatta.getTrackedRaces()) {
|
|
| 2597 | - ((TrackedRaceImpl) trackedRace).setRaceLogResolver(this);
|
|
| 2598 | - }
|
|
| 2599 | - } finally {
|
|
| 2600 | - trackedRegatta.unlockTrackedRacesAfterRead();
|
|
| 2601 | - }
|
|
| 2602 | - }
|
|
| 2603 | - logger.info(logoutput.toString());
|
|
| 2604 | - }
|
|
| 2605 | -
|
|
| 2606 | - @Override
|
|
| 2607 | - public void clearReplicaState() throws MalformedURLException, IOException, InterruptedException {
|
|
| 2608 | - logger.info("Clearing all data structures...");
|
|
| 2609 | - LockUtil.lockForWrite(regattasByNameLock);
|
|
| 2610 | - try {
|
|
| 2611 | - regattasByName.clear();
|
|
| 2612 | - } finally {
|
|
| 2613 | - LockUtil.unlockAfterWrite(regattasByNameLock);
|
|
| 2614 | - }
|
|
| 2615 | - regattasObservedForDefaultLeaderboard.clear();
|
|
| 2616 | -
|
|
| 2617 | - if (raceTrackersByRegatta != null && !raceTrackersByRegatta.isEmpty()) {
|
|
| 2618 | - for (DynamicTrackedRegatta regatta : regattaTrackingCache.values()) {
|
|
| 2619 | - final Set<RaceTracker> trackers = raceTrackersByRegatta.get(regatta.getRegatta());
|
|
| 2620 | - if (trackers != null) {
|
|
| 2621 | - for (RaceTracker tracker : trackers) {
|
|
| 2622 | - tracker.stop(/* preemptive */true);
|
|
| 2623 | - }
|
|
| 2624 | - }
|
|
| 2625 | - }
|
|
| 2626 | - }
|
|
| 2627 | - LockUtil.lockForWrite(regattaTrackingCacheLock);
|
|
| 2628 | - try {
|
|
| 2629 | - regattaTrackingCache.clear();
|
|
| 2630 | - } finally {
|
|
| 2631 | - LockUtil.unlockAfterWrite(regattaTrackingCacheLock);
|
|
| 2632 | - }
|
|
| 2633 | - LockUtil.lockForWrite(raceTrackersByRegattaLock);
|
|
| 2634 | - try {
|
|
| 2635 | - raceTrackersByRegatta.clear();
|
|
| 2636 | - } finally {
|
|
| 2637 | - LockUtil.unlockAfterWrite(raceTrackersByRegattaLock);
|
|
| 2638 | - }
|
|
| 2639 | - LockUtil.lockForWrite(leaderboardGroupsByNameLock);
|
|
| 2640 | - try {
|
|
| 2641 | - leaderboardGroupsByName.clear();
|
|
| 2642 | - leaderboardGroupsByID.clear();
|
|
| 2643 | - } finally {
|
|
| 2644 | - LockUtil.unlockAfterWrite(leaderboardGroupsByNameLock);
|
|
| 2645 | - }
|
|
| 2646 | - LockUtil.lockForWrite(leaderboardsByNameLock);
|
|
| 2647 | - try {
|
|
| 2648 | - leaderboardsByName.clear();
|
|
| 2649 | - } finally {
|
|
| 2650 | - LockUtil.unlockAfterWrite(leaderboardsByNameLock);
|
|
| 2651 | - }
|
|
| 2652 | - eventsById.clear();
|
|
| 2653 | - mediaLibrary.clear();
|
|
| 2654 | - competitorStore.clear();
|
|
| 2655 | - remoteSailingServerSet.clear();
|
|
| 2656 | - }
|
|
| 2657 | -
|
|
| 2658 | - // Used for TESTING only
|
|
| 2659 | - @Override
|
|
| 2660 | - public Event addEvent(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate,
|
|
| 2661 | - String venue, boolean isPublic, UUID id) {
|
|
| 2662 | - Event result = createEventWithoutReplication(eventName, eventDescription, startDate, endDate, venue, isPublic,
|
|
| 2663 | - id, /* officialWebsiteURL */null, /* sailorsInfoWebsiteURLAsString */null,
|
|
| 2664 | - /* images */Collections.<ImageDescriptor> emptyList(), /* videos */Collections.<VideoDescriptor> emptyList());
|
|
| 2665 | - replicate(new CreateEvent(eventName, eventDescription, startDate, endDate, venue, isPublic, id,
|
|
| 2666 | - /* officialWebsiteURLAsString */null, /* sailorsInfoWebsiteURLAsString */null,
|
|
| 2667 | - /* images */Collections.<ImageDescriptor> emptyList(), /* videos */Collections.<VideoDescriptor> emptyList()));
|
|
| 2668 | - return result;
|
|
| 2669 | - }
|
|
| 2670 | -
|
|
| 2671 | - @Override
|
|
| 2672 | - public void addEventWithoutReplication(Event event) {
|
|
| 2673 | - addEvent(event);
|
|
| 2674 | - }
|
|
| 2675 | -
|
|
| 2676 | - @Override
|
|
| 2677 | - public Event createEventWithoutReplication(String eventName, String eventDescription, TimePoint startDate,
|
|
| 2678 | - TimePoint endDate, String venue, boolean isPublic, UUID id, URL officialWebsiteURL, URL sailorsInfoWebsiteURL,
|
|
| 2679 | - Iterable<ImageDescriptor> images, Iterable<VideoDescriptor> videos) {
|
|
| 2680 | - Event result = new EventImpl(eventName, startDate, endDate, venue, isPublic, id);
|
|
| 2681 | - addEvent(result);
|
|
| 2682 | - result.setDescription(eventDescription);
|
|
| 2683 | - result.setOfficialWebsiteURL(officialWebsiteURL);
|
|
| 2684 | - result.setImages(images);
|
|
| 2685 | - result.setVideos(videos);
|
|
| 2686 | - return result;
|
|
| 2687 | - }
|
|
| 2688 | -
|
|
| 2689 | - private void addEvent(Event result) {
|
|
| 2690 | - if (eventsById.containsKey(result.getId())) {
|
|
| 2691 | - throw new IllegalArgumentException("Event with ID " + result.getId()
|
|
| 2692 | - + " already exists which is pretty surprising...");
|
|
| 2693 | - }
|
|
| 2694 | - eventsById.put(result.getId(), result);
|
|
| 2695 | - mongoObjectFactory.storeEvent(result);
|
|
| 2696 | - }
|
|
| 2697 | -
|
|
| 2698 | - @Override
|
|
| 2699 | - public void updateEvent(UUID id, String eventName, String eventDescription, TimePoint startDate, TimePoint endDate,
|
|
| 2700 | - String venueName, boolean isPublic, Iterable<UUID> leaderboardGroupIds, URL officialWebsiteURL, URL sailorsInfoWebsiteURL,
|
|
| 2701 | - Iterable<ImageDescriptor> images, Iterable<VideoDescriptor> videos) {
|
|
| 2702 | - final Event event = eventsById.get(id);
|
|
| 2703 | - if (event == null) {
|
|
| 2704 | - throw new IllegalArgumentException("Sailing event with ID " + id + " does not exist.");
|
|
| 2705 | - }
|
|
| 2706 | - event.setName(eventName);
|
|
| 2707 | - event.setDescription(eventDescription);
|
|
| 2708 | - event.setStartDate(startDate);
|
|
| 2709 | - event.setEndDate(endDate);
|
|
| 2710 | - event.setPublic(isPublic);
|
|
| 2711 | - event.getVenue().setName(venueName);
|
|
| 2712 | - List<LeaderboardGroup> leaderboardGroups = new ArrayList<>();
|
|
| 2713 | - for (UUID lgid : leaderboardGroupIds) {
|
|
| 2714 | - LeaderboardGroup lg = getLeaderboardGroupByID(lgid);
|
|
| 2715 | - if (lg != null) {
|
|
| 2716 | - leaderboardGroups.add(lg);
|
|
| 2717 | - } else {
|
|
| 2718 | - logger.info("Couldn't find leaderboard group with ID " + lgid + " while updating event "
|
|
| 2719 | - + event.getName());
|
|
| 2720 | - }
|
|
| 2721 | - }
|
|
| 2722 | - event.setLeaderboardGroups(leaderboardGroups);
|
|
| 2723 | - event.setOfficialWebsiteURL(officialWebsiteURL);
|
|
| 2724 | - event.setSailorsInfoWebsiteURL(sailorsInfoWebsiteURL);
|
|
| 2725 | - event.setImages(images);
|
|
| 2726 | - event.setVideos(videos);
|
|
| 2727 | - // TODO consider use diffutils to compute diff between old and new leaderboard groups list and apply the patch
|
|
| 2728 | - // to keep changes minimial
|
|
| 2729 | - mongoObjectFactory.storeEvent(event);
|
|
| 2730 | - }
|
|
| 2731 | -
|
|
| 2732 | - @Override
|
|
| 2733 | - public void renameEvent(UUID id, String newName) {
|
|
| 2734 | - final Event toRename = eventsById.get(id);
|
|
| 2735 | - if (toRename == null) {
|
|
| 2736 | - throw new IllegalArgumentException("No sailing event with ID " + id + " found.");
|
|
| 2737 | - }
|
|
| 2738 | - toRename.setName(newName);
|
|
| 2739 | - mongoObjectFactory.renameEvent(id, newName);
|
|
| 2740 | - replicate(new RenameEvent(id, newName));
|
|
| 2741 | - }
|
|
| 2742 | -
|
|
| 2743 | - @Override
|
|
| 2744 | - public void removeEvent(UUID id) {
|
|
| 2745 | - removeEventFromEventsById(id);
|
|
| 2746 | - mongoObjectFactory.removeEvent(id);
|
|
| 2747 | - replicate(new RemoveEvent(id));
|
|
| 2748 | - }
|
|
| 2749 | -
|
|
| 2750 | - protected void removeEventFromEventsById(Serializable id) {
|
|
| 2751 | - eventsById.remove(id);
|
|
| 2752 | - }
|
|
| 2753 | -
|
|
| 2754 | - @Override
|
|
| 2755 | - public Regatta getRememberedRegattaForRace(Serializable raceID) {
|
|
| 2756 | - return persistentRegattasForRaceIDs.get(raceID.toString());
|
|
| 2757 | - }
|
|
| 2758 | -
|
|
| 2759 | - /**
|
|
| 2760 | - * Persistently remembers the association of the race with its {@link RaceDefinition#getId()} to the
|
|
| 2761 | - * <code>regatta</code> with its {@link Regatta#getRegattaIdentifier() identifier} so that the next time
|
|
| 2762 | - * {@link #getRememberedRegattaForRace(RaceDefinition)} is called with <code>race</code> as argument,
|
|
| 2763 | - * <code>regatta</code> will be returned.
|
|
| 2764 | - */
|
|
| 2765 | - private void setRegattaForRace(Regatta regatta, RaceDefinition race) {
|
|
| 2766 | - setRegattaForRace(regatta, race.getId().toString());
|
|
| 2767 | - }
|
|
| 2768 | -
|
|
| 2769 | - @Override
|
|
| 2770 | - public void setRegattaForRace(Regatta regatta, String raceIdAsString) {
|
|
| 2771 | - persistentRegattasForRaceIDs.put(raceIdAsString, regatta);
|
|
| 2772 | - mongoObjectFactory.storeRegattaForRaceID(raceIdAsString, regatta);
|
|
| 2773 | - }
|
|
| 2774 | -
|
|
| 2775 | - @Override
|
|
| 2776 | - public CourseArea[] addCourseAreas(UUID eventId, String[] courseAreaNames, UUID[] courseAreaIds) {
|
|
| 2777 | - final CourseArea[] courseAreas = addCourseAreasWithoutReplication(eventId, courseAreaIds, courseAreaNames);
|
|
| 2778 | - replicate(new AddCourseAreas(eventId, courseAreaNames, courseAreaIds));
|
|
| 2779 | - return courseAreas;
|
|
| 2780 | - }
|
|
| 2781 | -
|
|
| 2782 | - @Override
|
|
| 2783 | - public CourseArea[] addCourseAreasWithoutReplication(UUID eventId, UUID[] courseAreaIds, String[] courseAreaNames) {
|
|
| 2784 | - final CourseArea[] result = new CourseArea[courseAreaNames.length];
|
|
| 2785 | - for (int i=0; i<courseAreaIds.length; i++) {
|
|
| 2786 | - final CourseArea courseArea = getBaseDomainFactory().getOrCreateCourseArea(courseAreaIds[i], courseAreaNames[i]);
|
|
| 2787 | - final Event event = eventsById.get(eventId);
|
|
| 2788 | - if (event == null) {
|
|
| 2789 | - throw new IllegalArgumentException("No sailing event with ID " + eventId + " found.");
|
|
| 2790 | - }
|
|
| 2791 | - event.getVenue().addCourseArea(courseArea);
|
|
| 2792 | - mongoObjectFactory.storeEvent(event);
|
|
| 2793 | - result[i] = courseArea;
|
|
| 2794 | - }
|
|
| 2795 | - return result;
|
|
| 2796 | - }
|
|
| 2797 | -
|
|
| 2798 | - @Override
|
|
| 2799 | - public CourseArea[] removeCourseAreaWithoutReplication(UUID eventId, UUID[] courseAreaIds) {
|
|
| 2800 | - final Event event = eventsById.get(eventId);
|
|
| 2801 | - if (event == null) {
|
|
| 2802 | - throw new IllegalArgumentException("No sailing event with ID " + eventId + " found.");
|
|
| 2803 | - }
|
|
| 2804 | - final CourseArea[] courseAreasRemoved = new CourseArea[courseAreaIds.length];
|
|
| 2805 | - int i=0;
|
|
| 2806 | - for (final UUID courseAreaId : courseAreaIds) {
|
|
| 2807 | - final CourseArea courseArea = getBaseDomainFactory().getExistingCourseAreaById(courseAreaId);
|
|
| 2808 | - if (courseArea == null) {
|
|
| 2809 | - throw new IllegalArgumentException("No course area with ID " + courseAreaId + " found.");
|
|
| 2810 | - }
|
|
| 2811 | - courseAreasRemoved[i++] = courseArea;
|
|
| 2812 | - event.getVenue().removeCourseArea(courseArea);
|
|
| 2813 | - mongoObjectFactory.storeEvent(event);
|
|
| 2814 | - }
|
|
| 2815 | - return courseAreasRemoved;
|
|
| 2816 | - }
|
|
| 2817 | -
|
|
| 2818 | - @Override
|
|
| 2819 | - public void mediaTrackAdded(MediaTrack mediaTrack) {
|
|
| 2820 | - if (mediaTrack.dbId == null) {
|
|
| 2821 | - mediaTrack.dbId = mediaDB.insertMediaTrack(mediaTrack.title, mediaTrack.url, mediaTrack.startTime,
|
|
| 2822 | - mediaTrack.duration, mediaTrack.mimeType, mediaTrack.assignedRaces);
|
|
| 2823 | - }
|
|
| 2824 | - mediaLibrary.addMediaTrack(mediaTrack);
|
|
| 2825 | - replicate(new AddMediaTrackOperation(mediaTrack));
|
|
| 2826 | - }
|
|
| 2827 | -
|
|
| 2828 | - @Override
|
|
| 2829 | - public void mediaTracksAdded(Collection<MediaTrack> mediaTracks) {
|
|
| 2830 | - mediaLibrary.addMediaTracks(mediaTracks);
|
|
| 2831 | - }
|
|
| 2832 | -
|
|
| 2833 | - @Override
|
|
| 2834 | - public void mediaTrackTitleChanged(MediaTrack mediaTrack) {
|
|
| 2835 | - mediaDB.updateTitle(mediaTrack.dbId, mediaTrack.title);
|
|
| 2836 | - mediaLibrary.titleChanged(mediaTrack);
|
|
| 2837 | - replicate(new UpdateMediaTrackTitleOperation(mediaTrack));
|
|
| 2838 | - }
|
|
| 2839 | -
|
|
| 2840 | - @Override
|
|
| 2841 | - public void mediaTrackUrlChanged(MediaTrack mediaTrack) {
|
|
| 2842 | - mediaDB.updateUrl(mediaTrack.dbId, mediaTrack.url);
|
|
| 2843 | - mediaLibrary.urlChanged(mediaTrack);
|
|
| 2844 | - replicate(new UpdateMediaTrackUrlOperation(mediaTrack));
|
|
| 2845 | - }
|
|
| 2846 | -
|
|
| 2847 | - @Override
|
|
| 2848 | - public void mediaTrackStartTimeChanged(MediaTrack mediaTrack) {
|
|
| 2849 | - mediaDB.updateStartTime(mediaTrack.dbId, mediaTrack.startTime);
|
|
| 2850 | - mediaLibrary.startTimeChanged(mediaTrack);
|
|
| 2851 | - replicate(new UpdateMediaTrackStartTimeOperation(mediaTrack));
|
|
| 2852 | - }
|
|
| 2853 | -
|
|
| 2854 | - @Override
|
|
| 2855 | - public void mediaTrackDurationChanged(MediaTrack mediaTrack) {
|
|
| 2856 | - mediaDB.updateDuration(mediaTrack.dbId, mediaTrack.duration);
|
|
| 2857 | - mediaLibrary.durationChanged(mediaTrack);
|
|
| 2858 | - replicate(new UpdateMediaTrackDurationOperation(mediaTrack));
|
|
| 2859 | - }
|
|
| 2860 | -
|
|
| 2861 | - @Override
|
|
| 2862 | - public void mediaTrackAssignedRacesChanged(MediaTrack mediaTrack) {
|
|
| 2863 | - mediaDB.updateRace(mediaTrack.dbId, mediaTrack.assignedRaces);
|
|
| 2864 | - mediaLibrary.assignedRacesChanged(mediaTrack);
|
|
| 2865 | - replicate(new UpdateMediaTrackRacesOperation(mediaTrack));
|
|
| 2866 | -
|
|
| 2867 | - }
|
|
| 2868 | -
|
|
| 2869 | - @Override
|
|
| 2870 | - public void mediaTrackDeleted(MediaTrack mediaTrack) {
|
|
| 2871 | - mediaDB.deleteMediaTrack(mediaTrack.dbId);
|
|
| 2872 | - mediaLibrary.deleteMediaTrack(mediaTrack);
|
|
| 2873 | - replicate(new RemoveMediaTrackOperation(mediaTrack));
|
|
| 2874 | - }
|
|
| 2875 | -
|
|
| 2876 | - @Override
|
|
| 2877 | - public void mediaTracksImported(Collection<MediaTrack> mediaTracksToImport, boolean override) {
|
|
| 2878 | - for (MediaTrack trackToImport : mediaTracksToImport) {
|
|
| 2879 | - MediaTrack existingTrack = mediaLibrary.lookupMediaTrack(trackToImport);
|
|
| 2880 | - if (existingTrack == null) {
|
|
| 2881 | - mediaDB.insertMediaTrackWithId(trackToImport.dbId, trackToImport.title, trackToImport.url,
|
|
| 2882 | - trackToImport.startTime, trackToImport.duration, trackToImport.mimeType,
|
|
| 2883 | - trackToImport.assignedRaces);
|
|
| 2884 | - mediaTrackAdded(trackToImport);
|
|
| 2885 | - } else if (override) {
|
|
| 2886 | -
|
|
| 2887 | - // Using fine-grained update methods.
|
|
| 2888 | - // Rationale: Changes on more than one track property are rare
|
|
| 2889 | - // and don't justify the introduction of a new set
|
|
| 2890 | - // of methods (including replication).
|
|
| 2891 | - if (!Util.equalsWithNull(existingTrack.title, trackToImport.title)) {
|
|
| 2892 | - mediaTrackTitleChanged(trackToImport);
|
|
| 2893 | - }
|
|
| 2894 | - if (!Util.equalsWithNull(existingTrack.url, trackToImport.url)) {
|
|
| 2895 | - mediaTrackUrlChanged(trackToImport);
|
|
| 2896 | - }
|
|
| 2897 | - if (!Util.equalsWithNull(existingTrack.startTime, trackToImport.startTime)) {
|
|
| 2898 | - mediaTrackStartTimeChanged(trackToImport);
|
|
| 2899 | - }
|
|
| 2900 | - if (!Util.equalsWithNull(existingTrack.duration, trackToImport.duration)) {
|
|
| 2901 | - mediaTrackDurationChanged(trackToImport);
|
|
| 2902 | - }
|
|
| 2903 | - if (!Util.equalsWithNull(existingTrack.assignedRaces, trackToImport.assignedRaces)) {
|
|
| 2904 | - mediaTrackAssignedRacesChanged(trackToImport);
|
|
| 2905 | - }
|
|
| 2906 | - }
|
|
| 2907 | - }
|
|
| 2908 | - }
|
|
| 2909 | -
|
|
| 2910 | - @Override
|
|
| 2911 | - public Collection<MediaTrack> getMediaTracksForRace(RegattaAndRaceIdentifier regattaAndRaceIdentifier) {
|
|
| 2912 | - return mediaLibrary.findMediaTracksForRace(regattaAndRaceIdentifier);
|
|
| 2913 | - }
|
|
| 2914 | -
|
|
| 2915 | - @Override
|
|
| 2916 | - public Collection<MediaTrack> getMediaTracksInTimeRange(RegattaAndRaceIdentifier regattaAndRaceIdentifier) {
|
|
| 2917 | - TrackedRace trackedRace = getExistingTrackedRace(regattaAndRaceIdentifier);
|
|
| 2918 | - if (trackedRace != null) {
|
|
| 2919 | - if (trackedRace.isLive(MillisecondsTimePoint.now())) {
|
|
| 2920 | - return mediaLibrary.findLiveMediaTracks();
|
|
| 2921 | - } else {
|
|
| 2922 | - TimePoint raceStart = trackedRace.getStartOfRace() == null ? trackedRace.getStartOfTracking()
|
|
| 2923 | - : trackedRace.getStartOfRace();
|
|
| 2924 | - TimePoint raceEnd = trackedRace.getEndOfRace() == null ? trackedRace.getEndOfTracking() : trackedRace
|
|
| 2925 | - .getEndOfRace();
|
|
| 2926 | - return mediaLibrary.findMediaTracksInTimeRange(raceStart, raceEnd);
|
|
| 2927 | - }
|
|
| 2928 | - } else {
|
|
| 2929 | - return Collections.emptyList();
|
|
| 2930 | - }
|
|
| 2931 | - }
|
|
| 2932 | -
|
|
| 2933 | - @Override
|
|
| 2934 | - public Collection<MediaTrack> getAllMediaTracks() {
|
|
| 2935 | - return mediaLibrary.allTracks();
|
|
| 2936 | - }
|
|
| 2937 | -
|
|
| 2938 | - public String toString() {
|
|
| 2939 | - return "RacingEventService: " + this.hashCode() + " Build: " + ServerInfo.getBuildVersion();
|
|
| 2940 | - }
|
|
| 2941 | -
|
|
| 2942 | - @Override
|
|
| 2943 | - public void reloadRaceLog(String leaderboardName, String raceColumnName, String fleetName) {
|
|
| 2944 | - Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
|
|
| 2945 | - if (leaderboard != null) {
|
|
| 2946 | - RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName);
|
|
| 2947 | - if (raceColumn != null) {
|
|
| 2948 | - Fleet fleetImpl = raceColumn.getFleetByName(fleetName);
|
|
| 2949 | - RaceLog racelog = raceColumn.getRaceLog(fleetImpl);
|
|
| 2950 | - if (racelog != null) {
|
|
| 2951 | - raceColumn.reloadRaceLog(fleetImpl);
|
|
| 2952 | - logger.info("Reloaded race log for fleet " + fleetImpl + " for race column " + raceColumn.getName()
|
|
| 2953 | - + " for leaderboard " + leaderboard.getName());
|
|
| 2954 | - }
|
|
| 2955 | - }
|
|
| 2956 | - }
|
|
| 2957 | - }
|
|
| 2958 | -
|
|
| 2959 | - @Override
|
|
| 2960 | - public ConcurrentHashMap<String, Regatta> getPersistentRegattasForRaceIDs() {
|
|
| 2961 | - return persistentRegattasForRaceIDs;
|
|
| 2962 | - }
|
|
| 2963 | -
|
|
| 2964 | - @Override
|
|
| 2965 | - public WindStore getWindStore() {
|
|
| 2966 | - return windStore;
|
|
| 2967 | - }
|
|
| 2968 | -
|
|
| 2969 | - @Override
|
|
| 2970 | - public DeviceConfiguration getDeviceConfiguration(DeviceConfigurationIdentifier identifier) {
|
|
| 2971 | - return configurationMap.getByMatch(identifier);
|
|
| 2972 | - }
|
|
| 2973 | -
|
|
| 2974 | - @Override
|
|
| 2975 | - public void createOrUpdateDeviceConfiguration(DeviceConfigurationMatcher matcher, DeviceConfiguration configuration) {
|
|
| 2976 | - configurationMap.put(matcher, configuration);
|
|
| 2977 | - mongoObjectFactory.storeDeviceConfiguration(matcher, configuration);
|
|
| 2978 | - replicate(new CreateOrUpdateDeviceConfiguration(matcher, configuration));
|
|
| 2979 | - }
|
|
| 2980 | -
|
|
| 2981 | - @Override
|
|
| 2982 | - public void removeDeviceConfiguration(DeviceConfigurationMatcher matcher) {
|
|
| 2983 | - configurationMap.remove(matcher);
|
|
| 2984 | - mongoObjectFactory.removeDeviceConfiguration(matcher);
|
|
| 2985 | - replicate(new RemoveDeviceConfiguration(matcher));
|
|
| 2986 | - }
|
|
| 2987 | -
|
|
| 2988 | - @Override
|
|
| 2989 | - public Map<DeviceConfigurationMatcher, DeviceConfiguration> getAllDeviceConfigurations() {
|
|
| 2990 | - return new HashMap<DeviceConfigurationMatcher, DeviceConfiguration>(configurationMap);
|
|
| 2991 | - }
|
|
| 2992 | -
|
|
| 2993 | - @Override
|
|
| 2994 | - public TimePoint setStartTimeAndProcedure(String leaderboardName, String raceColumnName, String fleetName,
|
|
| 2995 | - String authorName, int authorPriority, int passId, TimePoint logicalTimePoint, TimePoint startTime,
|
|
| 2996 | - RacingProcedureType racingProcedure) {
|
|
| 2997 | - RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName);
|
|
| 2998 | - Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
|
|
| 2999 | - final TimePoint result;
|
|
| 3000 | - if (leaderboard instanceof HasRegattaLike && raceLog != null) {
|
|
| 3001 | - RaceState state = RaceStateImpl.create(/* race log resolver */ this, raceLog, new LogEventAuthorImpl(authorName, authorPriority));
|
|
| 3002 | - if (passId > raceLog.getCurrentPassId()) {
|
|
| 3003 | - state.setAdvancePass(logicalTimePoint);
|
|
| 3004 | - }
|
|
| 3005 | - state.setRacingProcedure(logicalTimePoint, racingProcedure);
|
|
| 3006 | - state.forceNewStartTime(logicalTimePoint, startTime);
|
|
| 3007 | - result = state.getStartTime();
|
|
| 3008 | - } else {
|
|
| 3009 | - result = null;
|
|
| 3010 | - }
|
|
| 3011 | - return result;
|
|
| 3012 | - }
|
|
| 3013 | -
|
|
| 3014 | - public RaceLog getRaceLog(String leaderboardName, String raceColumnName, String fleetName) {
|
|
| 3015 | - Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
|
|
| 3016 | - if (leaderboard != null) {
|
|
| 3017 | - RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName);
|
|
| 3018 | - if (raceColumn != null) {
|
|
| 3019 | - Fleet fleetImpl = raceColumn.getFleetByName(fleetName);
|
|
| 3020 | - return raceColumn.getRaceLog(fleetImpl);
|
|
| 3021 | - }
|
|
| 3022 | - }
|
|
| 3023 | - return null;
|
|
| 3024 | - }
|
|
| 3025 | -
|
|
| 3026 | - @Override
|
|
| 3027 | - public com.sap.sse.common.Util.Triple<TimePoint, Integer, RacingProcedureType> getStartTimeAndProcedure(
|
|
| 3028 | - String leaderboardName, String raceColumnName, String fleetName) {
|
|
| 3029 | - RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName);
|
|
| 3030 | - Leaderboard leaderboard = getLeaderboardByName(leaderboardName);
|
|
| 3031 | - final Triple<TimePoint, Integer, RacingProcedureType> result;
|
|
| 3032 | - if (leaderboard instanceof HasRegattaLike && raceLog != null) {
|
|
| 3033 | - ReadonlyRaceState state = ReadonlyRaceStateImpl.create(/* race log resolver */ this, raceLog);
|
|
| 3034 | - result = new com.sap.sse.common.Util.Triple<TimePoint, Integer, RacingProcedureType>(state.getStartTime(),
|
|
| 3035 | - raceLog.getCurrentPassId(), state.getRacingProcedure().getType());
|
|
| 3036 | - } else {
|
|
| 3037 | - result = null;
|
|
| 3038 | - }
|
|
| 3039 | - return result;
|
|
| 3040 | - }
|
|
| 3041 | -
|
|
| 3042 | - private Iterable<WindTrackerFactory> getWindTrackerFactories() {
|
|
| 3043 | - final Set<WindTrackerFactory> result;
|
|
| 3044 | - if (bundleContext == null) {
|
|
| 3045 | - result = Collections.singleton((WindTrackerFactory) ExpeditionWindTrackerFactory.getInstance());
|
|
| 3046 | - } else {
|
|
| 3047 | - ServiceTracker<WindTrackerFactory, WindTrackerFactory> tracker = new ServiceTracker<WindTrackerFactory, WindTrackerFactory>(
|
|
| 3048 | - bundleContext, WindTrackerFactory.class.getName(), null);
|
|
| 3049 | - tracker.open();
|
|
| 3050 | - result = new HashSet<>();
|
|
| 3051 | - for (WindTrackerFactory factory : tracker.getServices(new WindTrackerFactory[0])) {
|
|
| 3052 | - result.add(factory);
|
|
| 3053 | - }
|
|
| 3054 | - }
|
|
| 3055 | - return result;
|
|
| 3056 | - }
|
|
| 3057 | -
|
|
| 3058 | - @Override
|
|
| 3059 | - public GPSFixStore getGPSFixStore() {
|
|
| 3060 | - return gpsFixStore;
|
|
| 3061 | - }
|
|
| 3062 | -
|
|
| 3063 | - @Override
|
|
| 3064 | - public RaceTracker getRaceTrackerById(Object id) {
|
|
| 3065 | - return raceTrackersByID.get(id);
|
|
| 3066 | - }
|
|
| 3067 | -
|
|
| 3068 | - @Override
|
|
| 3069 | - public AbstractLogEventAuthor getServerAuthor() {
|
|
| 3070 | - return raceLogEventAuthorForServer;
|
|
| 3071 | - }
|
|
| 3072 | -
|
|
| 3073 | - @Override
|
|
| 3074 | - public CompetitorStore getCompetitorStore() {
|
|
| 3075 | - return competitorStore;
|
|
| 3076 | - }
|
|
| 3077 | -
|
|
| 3078 | - @Override
|
|
| 3079 | - public TypeBasedServiceFinderFactory getTypeBasedServiceFinderFactory() {
|
|
| 3080 | - return serviceFinderFactory;
|
|
| 3081 | - }
|
|
| 3082 | -
|
|
| 3083 | - @Override
|
|
| 3084 | - public DataImportLockWithProgress getDataImportLock() {
|
|
| 3085 | - return dataImportLock;
|
|
| 3086 | - }
|
|
| 3087 | -
|
|
| 3088 | - @Override
|
|
| 3089 | - public DataImportProgress createOrUpdateDataImportProgressWithReplication(UUID importOperationId,
|
|
| 3090 | - double overallProgressPct, String subProgressName, double subProgressPct) {
|
|
| 3091 | - // Create/Update locally
|
|
| 3092 | - DataImportProgress progress = createOrUpdateDataImportProgressWithoutReplication(importOperationId,
|
|
| 3093 | - overallProgressPct, subProgressName, subProgressPct);
|
|
| 3094 | - // Create/Update on replicas
|
|
| 3095 | - replicate(new CreateOrUpdateDataImportProgress(importOperationId, overallProgressPct, subProgressName,
|
|
| 3096 | - subProgressPct));
|
|
| 3097 | - return progress;
|
|
| 3098 | - }
|
|
| 3099 | -
|
|
| 3100 | - @Override
|
|
| 3101 | - public DataImportProgress createOrUpdateDataImportProgressWithoutReplication(UUID importOperationId,
|
|
| 3102 | - double overallProgressPct, String subProgressName, double subProgressPct) {
|
|
| 3103 | - DataImportProgress progress = dataImportLock.getProgress(importOperationId);
|
|
| 3104 | - boolean newObject = false;
|
|
| 3105 | - if (progress == null) {
|
|
| 3106 | - progress = new DataImportProgressImpl(importOperationId);
|
|
| 3107 | - newObject = true;
|
|
| 3108 | - }
|
|
| 3109 | - progress.setOverAllProgressPct(overallProgressPct);
|
|
| 3110 | - progress.setNameOfCurrentSubProgress(subProgressName);
|
|
| 3111 | - progress.setCurrentSubProgressPct(subProgressPct);
|
|
| 3112 | - if (newObject) {
|
|
| 3113 | - dataImportLock.addProgress(importOperationId, progress);
|
|
| 3114 | - }
|
|
| 3115 | - return progress;
|
|
| 3116 | - }
|
|
| 3117 | -
|
|
| 3118 | - @Override
|
|
| 3119 | - public void setDataImportFailedWithoutReplication(UUID importOperationId, String errorMessage) {
|
|
| 3120 | - DataImportProgress progress = dataImportLock.getProgress(importOperationId);
|
|
| 3121 | - if (progress != null) {
|
|
| 3122 | - progress.setFailed();
|
|
| 3123 | - progress.setErrorMessage(errorMessage);
|
|
| 3124 | - }
|
|
| 3125 | - }
|
|
| 3126 | -
|
|
| 3127 | - @Override
|
|
| 3128 | - public void setDataImportFailedWithReplication(UUID importOperationId, String errorMessage) {
|
|
| 3129 | - setDataImportFailedWithoutReplication(importOperationId, errorMessage);
|
|
| 3130 | - replicate(new DataImportFailed(importOperationId, errorMessage));
|
|
| 3131 | - }
|
|
| 3132 | -
|
|
| 3133 | - @Override
|
|
| 3134 | - public void setDataImportDeleteProgressFromMapTimerWithReplication(UUID importOperationId) {
|
|
| 3135 | - setDataImportDeleteProgressFromMapTimerWithoutReplication(importOperationId);
|
|
| 3136 | - replicate(new SetDataImportDeleteProgressFromMapTimer(importOperationId));
|
|
| 3137 | - }
|
|
| 3138 | -
|
|
| 3139 | - @Override
|
|
| 3140 | - public void setDataImportDeleteProgressFromMapTimerWithoutReplication(UUID importOperationId) {
|
|
| 3141 | - dataImportLock.setDeleteFromMapTimer(importOperationId);
|
|
| 3142 | - }
|
|
| 3143 | -
|
|
| 3144 | - @Override
|
|
| 3145 | - public Result<LeaderboardSearchResult> search(KeywordQuery query) {
|
|
| 3146 | - long start = System.currentTimeMillis();
|
|
| 3147 | - logger.info("Searching local server for " + query);
|
|
| 3148 | - Result<LeaderboardSearchResult> result = new RegattaByKeywordSearchService().search(this, query);
|
|
| 3149 | - logger.fine("Search for " + query + " took " + (System.currentTimeMillis() - start) + "ms");
|
|
| 3150 | - return result;
|
|
| 3151 | - }
|
|
| 3152 | -
|
|
| 3153 | - @Override
|
|
| 3154 | - public Result<LeaderboardSearchResultBase> searchRemotely(String remoteServerReferenceName, KeywordQuery query) {
|
|
| 3155 | - long start = System.currentTimeMillis();
|
|
| 3156 | - ResultImpl<LeaderboardSearchResultBase> result = null;
|
|
| 3157 | - RemoteSailingServerReference remoteRef = remoteSailingServerSet
|
|
| 3158 | - .getServerReferenceByName(remoteServerReferenceName);
|
|
| 3159 | - if (remoteRef == null) {
|
|
| 3160 | - result = null;
|
|
| 3161 | - } else {
|
|
| 3162 | - BufferedReader bufferedReader = null;
|
|
| 3163 | - try {
|
|
| 3164 | - try {
|
|
| 3165 | - final URL eventsURL = new URL(remoteRef.getURL(), "sailingserver/api/v1/search?q="
|
|
| 3166 | - + URLEncoder.encode(query.toString(), "UTF-8"));
|
|
| 3167 | - logger.info("Searching remote server " + remoteRef + " for " + query);
|
|
| 3168 | - URLConnection urlConnection = eventsURL.openConnection();
|
|
| 3169 | - urlConnection.connect();
|
|
| 3170 | - bufferedReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "UTF-8"));
|
|
| 3171 | - JSONParser parser = new JSONParser();
|
|
| 3172 | - Object eventsAsObject = parser.parse(bufferedReader);
|
|
| 3173 | - final LeaderboardGroupBaseJsonDeserializer leaderboardGroupBaseJsonDeserializer = new LeaderboardGroupBaseJsonDeserializer();
|
|
| 3174 | - LeaderboardSearchResultBaseJsonDeserializer deserializer = new LeaderboardSearchResultBaseJsonDeserializer(
|
|
| 3175 | - new EventBaseJsonDeserializer(new VenueJsonDeserializer(new CourseAreaJsonDeserializer(
|
|
| 3176 | - DomainFactory.INSTANCE)), leaderboardGroupBaseJsonDeserializer),
|
|
| 3177 | - leaderboardGroupBaseJsonDeserializer);
|
|
| 3178 | - result = new ResultImpl<LeaderboardSearchResultBase>(query,
|
|
| 3179 | - new LeaderboardSearchResultBaseRanker<LeaderboardSearchResultBase>());
|
|
| 3180 | - JSONArray hitsAsJsonArray = (JSONArray) eventsAsObject;
|
|
| 3181 | - for (Object hitAsObject : hitsAsJsonArray) {
|
|
| 3182 | - JSONObject hitAsJson = (JSONObject) hitAsObject;
|
|
| 3183 | - LeaderboardSearchResultBase hit = deserializer.deserialize(hitAsJson);
|
|
| 3184 | - result.addHit(hit);
|
|
| 3185 | - }
|
|
| 3186 | - } finally {
|
|
| 3187 | - if (bufferedReader != null) {
|
|
| 3188 | - bufferedReader.close();
|
|
| 3189 | - }
|
|
| 3190 | - }
|
|
| 3191 | - } catch (IOException | ParseException e) {
|
|
| 3192 | - logger.log(Level.INFO,
|
|
| 3193 | - "Exception trying to fetch events from remote server " + remoteRef + ": " + e.getMessage(), e);
|
|
| 3194 | - }
|
|
| 3195 | - }
|
|
| 3196 | - logger.fine("Remote search on " + remoteRef + " for " + query + " took " + (System.currentTimeMillis() - start)
|
|
| 3197 | - + "ms");
|
|
| 3198 | - return result;
|
|
| 3199 | - }
|
|
| 3200 | -
|
|
| 3201 | - @Override
|
|
| 3202 | - public ReplicationMasterDescriptor getMasterDescriptor() {
|
|
| 3203 | - return replicatingFromMaster;
|
|
| 3204 | - }
|
|
| 3205 | -
|
|
| 3206 | - @Override
|
|
| 3207 | - public void startedReplicatingFrom(ReplicationMasterDescriptor master) {
|
|
| 3208 | - this.replicatingFromMaster = master;
|
|
| 3209 | - }
|
|
| 3210 | -
|
|
| 3211 | - @Override
|
|
| 3212 | - public void stoppedReplicatingFrom(ReplicationMasterDescriptor master) {
|
|
| 3213 | - this.replicatingFromMaster = null;
|
|
| 3214 | - }
|
|
| 3215 | -
|
|
| 3216 | - @Override
|
|
| 3217 | - public void addOperationSentToMasterForReplication(
|
|
| 3218 | - OperationWithResultWithIdWrapper<RacingEventService, ?> operationWithResultWithIdWrapper) {
|
|
| 3219 | - this.operationsSentToMasterForReplication.add(operationWithResultWithIdWrapper);
|
|
| 3220 | - }
|
|
| 3221 | -
|
|
| 3222 | - @Override
|
|
| 3223 | - public boolean hasSentOperationToMaster(OperationWithResult<RacingEventService, ?> operation) {
|
|
| 3224 | - return this.operationsSentToMasterForReplication.remove(operation);
|
|
| 3225 | - }
|
|
| 3226 | -
|
|
| 3227 | - @Override
|
|
| 3228 | - public FileStorageManagementService getFileStorageManagementService() {
|
|
| 3229 | - ServiceReference<FileStorageManagementService> ref = bundleContext
|
|
| 3230 | - .getServiceReference(FileStorageManagementService.class);
|
|
| 3231 | - if (ref == null) {
|
|
| 3232 | - logger.warning("No file storage management service registered");
|
|
| 3233 | - return null;
|
|
| 3234 | - }
|
|
| 3235 | - return bundleContext.getService(ref);
|
|
| 3236 | - }
|
|
| 3237 | -
|
|
| 3238 | - public void addMasterDataClassLoader(ClassLoader classLoader) {
|
|
| 3239 | - masterDataClassLoaders.add(classLoader);
|
|
| 3240 | - }
|
|
| 3241 | -
|
|
| 3242 | - public void removeMasterDataClassLoader(ClassLoader classLoader) {
|
|
| 3243 | - masterDataClassLoaders.remove(classLoader);
|
|
| 3244 | - }
|
|
| 3245 | -
|
|
| 3246 | - @Override
|
|
| 3247 | - public ClassLoader getCombinedMasterDataClassLoader() {
|
|
| 3248 | - JoinedClassLoader joinedClassLoader = new JoinedClassLoader(masterDataClassLoaders);
|
|
| 3249 | - return joinedClassLoader;
|
|
| 3250 | - }
|
|
| 3251 | -
|
|
| 3252 | - public void setPolarDataService(PolarDataService service) {
|
|
| 3253 | - if (this.polarDataService == null && service != null) {
|
|
| 3254 | - polarDataService = service;
|
|
| 3255 | - polarDataService.registerDomainFactory(baseDomainFactory);
|
|
| 3256 | - setPolarDataServiceOnAllTrackedRaces(service);
|
|
| 3257 | - }
|
|
| 3258 | - }
|
|
| 3259 | -
|
|
| 3260 | - private void setPolarDataServiceOnAllTrackedRaces(PolarDataService service) {
|
|
| 3261 | - Iterable<Regatta> allRegattas = getAllRegattas();
|
|
| 3262 | - for (Regatta regatta : allRegattas) {
|
|
| 3263 | - DynamicTrackedRegatta trackedRegatta = getTrackedRegatta(regatta);
|
|
| 3264 | - if (trackedRegatta != null) {
|
|
| 3265 | - trackedRegatta.lockTrackedRacesForRead();
|
|
| 3266 | - try {
|
|
| 3267 | - Iterable<DynamicTrackedRace> trackedRaces = trackedRegatta.getTrackedRaces();
|
|
| 3268 | - for (TrackedRace trackedRace : trackedRaces) {
|
|
| 3269 | - trackedRace.setPolarDataService(service);
|
|
| 3270 | - service.insertExistingFixes(trackedRace);
|
|
| 3271 | - }
|
|
| 3272 | - } finally {
|
|
| 3273 | - trackedRegatta.unlockTrackedRacesAfterRead();
|
|
| 3274 | - }
|
|
| 3275 | - }
|
|
| 3276 | - }
|
|
| 3277 | - }
|
|
| 3278 | -
|
|
| 3279 | - public void unsetPolarDataService(PolarDataService service) {
|
|
| 3280 | - if (polarDataService == service) {
|
|
| 3281 | - polarDataService.unregisterDomainFactory(baseDomainFactory);
|
|
| 3282 | - polarDataService = null;
|
|
| 3283 | - setPolarDataService(null);
|
|
| 3284 | - }
|
|
| 3285 | - }
|
|
| 3286 | -
|
|
| 3287 | - @Override
|
|
| 3288 | - public Iterable<Competitor> getCompetitorInOrderOfWindwardDistanceTraveledFarthestFirst(TrackedRace trackedRace, TimePoint timePoint) {
|
|
| 3289 | - final RankingInfo rankingInfo = trackedRace.getRankingMetric().getRankingInfo(timePoint);
|
|
| 3290 | - final List<Competitor> result = new ArrayList<>();
|
|
| 3291 | - final Map<Competitor, Distance> windwardDistanceSailedPerCompetitor = new HashMap<>();
|
|
| 3292 | - for (final Competitor competitor : trackedRace.getRace().getCompetitors()) {
|
|
| 3293 | - result.add(competitor);
|
|
| 3294 | - final CompetitorRankingInfo competitorRankingInfo = rankingInfo.getCompetitorRankingInfo().apply(competitor);
|
|
| 3295 | - windwardDistanceSailedPerCompetitor.put(competitor, competitorRankingInfo == null ? null : competitorRankingInfo.getWindwardDistanceSailed());
|
|
| 3296 | - }
|
|
| 3297 | - final Comparator<Distance> durationComparatorNullsLast = Comparator.nullsLast(Comparator.naturalOrder());
|
|
| 3298 | - result.sort((c1, c2) -> durationComparatorNullsLast.compare(windwardDistanceSailedPerCompetitor.get(c2),
|
|
| 3299 | - windwardDistanceSailedPerCompetitor.get(c1)));
|
|
| 3300 | - return result;
|
|
| 3301 | - }
|
|
| 3302 | -
|
|
| 3303 | - /**
|
|
| 3304 | - * A {@link SimpleRaceLogIdentifier} in particular has a {@link SimpleRaceLogIdentifier#getRegattaLikeParentName()}
|
|
| 3305 | - * which identifies either a regatta by name or a flexible leaderboard by name. Here is why this can luckily be
|
|
| 3306 | - * resolved unanimously: A regatta leaderboard always uses as its name the regatta name (see
|
|
| 3307 | - * {@link RegattaImpl#getName()}). Trying to {@link RegattaLeaderboardImpl#setName(String) set} the regatta leaderboard's
|
|
| 3308 | - * name can only update its {@link Leaderboard#getDisplayName() display name}. Therefore, regatta leaderboards are always
|
|
| 3309 | - * keyed in {@link #leaderboardsByName} by their regatta's name. Thus, no flexible leaderboard can have a regatta's name
|
|
| 3310 | - * as its name, and therefore leaderboard names <em>and</em> regatta names are unitedly unique.
|
|
| 3311 | - */
|
|
| 3312 | - @Override
|
|
| 3313 | - public RaceLog resolve(SimpleRaceLogIdentifier identifier) {
|
|
| 3314 | - final RaceLog result;
|
|
| 3315 | - final IsRegattaLike regattaLike;
|
|
| 3316 | - final Regatta regatta = regattasByName.get(identifier.getRegattaLikeParentName());
|
|
| 3317 | - if (regatta != null) {
|
|
| 3318 | - regattaLike = regatta;
|
|
| 3319 | - } else {
|
|
| 3320 | - final Leaderboard leaderboard = leaderboardsByName.get(identifier.getRegattaLikeParentName());
|
|
| 3321 | - if (leaderboard != null && leaderboard instanceof FlexibleLeaderboard) {
|
|
| 3322 | - regattaLike = (FlexibleLeaderboard) leaderboard;
|
|
| 3323 | - } else {
|
|
| 3324 | - regattaLike = null;
|
|
| 3325 | - }
|
|
| 3326 | - }
|
|
| 3327 | - if (regattaLike != null) {
|
|
| 3328 | - final RaceColumn raceColumn = regattaLike.getRaceColumnByName(identifier.getRaceColumnName());
|
|
| 3329 | - if (raceColumn != null) {
|
|
| 3330 | - final Fleet fleet = raceColumn.getFleetByName(identifier.getFleetName());
|
|
| 3331 | - if (fleet != null) {
|
|
| 3332 | - result = raceColumn.getRaceLog(fleet);
|
|
| 3333 | - } else {
|
|
| 3334 | - result = null;
|
|
| 3335 | - }
|
|
| 3336 | - } else {
|
|
| 3337 | - result = null;
|
|
| 3338 | - }
|
|
| 3339 | - } else {
|
|
| 3340 | - result = null;
|
|
| 3341 | - }
|
|
| 3342 | - return result;
|
|
| 3343 | - }
|
|
| 3344 | -}
|
|
| 1 | +package com.sap.sailing.server.impl; |
|
| 2 | + |
|
| 3 | +import java.io.BufferedReader; |
|
| 4 | +import java.io.IOException; |
|
| 5 | +import java.io.InputStream; |
|
| 6 | +import java.io.InputStreamReader; |
|
| 7 | +import java.io.ObjectInputStream; |
|
| 8 | +import java.io.ObjectOutputStream; |
|
| 9 | +import java.io.Serializable; |
|
| 10 | +import java.net.MalformedURLException; |
|
| 11 | +import java.net.SocketException; |
|
| 12 | +import java.net.URL; |
|
| 13 | +import java.net.URLConnection; |
|
| 14 | +import java.net.URLEncoder; |
|
| 15 | +import java.util.ArrayList; |
|
| 16 | +import java.util.Arrays; |
|
| 17 | +import java.util.Collection; |
|
| 18 | +import java.util.Collections; |
|
| 19 | +import java.util.Comparator; |
|
| 20 | +import java.util.HashMap; |
|
| 21 | +import java.util.HashSet; |
|
| 22 | +import java.util.Iterator; |
|
| 23 | +import java.util.LinkedHashMap; |
|
| 24 | +import java.util.List; |
|
| 25 | +import java.util.Map; |
|
| 26 | +import java.util.Map.Entry; |
|
| 27 | +import java.util.Set; |
|
| 28 | +import java.util.UUID; |
|
| 29 | +import java.util.concurrent.ConcurrentHashMap; |
|
| 30 | +import java.util.concurrent.Executor; |
|
| 31 | +import java.util.concurrent.Executors; |
|
| 32 | +import java.util.concurrent.LinkedBlockingQueue; |
|
| 33 | +import java.util.concurrent.ScheduledExecutorService; |
|
| 34 | +import java.util.concurrent.ScheduledFuture; |
|
| 35 | +import java.util.concurrent.ThreadPoolExecutor; |
|
| 36 | +import java.util.concurrent.TimeUnit; |
|
| 37 | +import java.util.function.Function; |
|
| 38 | +import java.util.logging.Level; |
|
| 39 | +import java.util.logging.Logger; |
|
| 40 | + |
|
| 41 | +import org.json.simple.JSONArray; |
|
| 42 | +import org.json.simple.JSONObject; |
|
| 43 | +import org.json.simple.parser.JSONParser; |
|
| 44 | +import org.json.simple.parser.ParseException; |
|
| 45 | +import org.osgi.framework.BundleContext; |
|
| 46 | +import org.osgi.framework.ServiceReference; |
|
| 47 | +import org.osgi.util.tracker.ServiceTracker; |
|
| 48 | + |
|
| 49 | +import com.sap.sailing.domain.abstractlog.AbstractLog; |
|
| 50 | +import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; |
|
| 51 | +import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl; |
|
| 52 | +import com.sap.sailing.domain.abstractlog.race.RaceLog; |
|
| 53 | +import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; |
|
| 54 | +import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor; |
|
| 55 | +import com.sap.sailing.domain.abstractlog.race.SimpleRaceLogIdentifier; |
|
| 56 | +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver; |
|
| 57 | +import com.sap.sailing.domain.abstractlog.race.state.RaceState; |
|
| 58 | +import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState; |
|
| 59 | +import com.sap.sailing.domain.abstractlog.race.state.impl.RaceStateImpl; |
|
| 60 | +import com.sap.sailing.domain.abstractlog.race.state.impl.ReadonlyRaceStateImpl; |
|
| 61 | +import com.sap.sailing.domain.base.Competitor; |
|
| 62 | +import com.sap.sailing.domain.base.CompetitorStore; |
|
| 63 | +import com.sap.sailing.domain.base.CompetitorStore.CompetitorUpdateListener; |
|
| 64 | +import com.sap.sailing.domain.base.ControlPoint; |
|
| 65 | +import com.sap.sailing.domain.base.CourseArea; |
|
| 66 | +import com.sap.sailing.domain.base.DomainFactory; |
|
| 67 | +import com.sap.sailing.domain.base.Event; |
|
| 68 | +import com.sap.sailing.domain.base.EventBase; |
|
| 69 | +import com.sap.sailing.domain.base.EventFetcher; |
|
| 70 | +import com.sap.sailing.domain.base.Fleet; |
|
| 71 | +import com.sap.sailing.domain.base.LeaderboardSearchResult; |
|
| 72 | +import com.sap.sailing.domain.base.LeaderboardSearchResultBase; |
|
| 73 | +import com.sap.sailing.domain.base.Mark; |
|
| 74 | +import com.sap.sailing.domain.base.RaceColumn; |
|
| 75 | +import com.sap.sailing.domain.base.RaceColumnInSeries; |
|
| 76 | +import com.sap.sailing.domain.base.RaceDefinition; |
|
| 77 | +import com.sap.sailing.domain.base.Regatta; |
|
| 78 | +import com.sap.sailing.domain.base.RegattaListener; |
|
| 79 | +import com.sap.sailing.domain.base.RemoteSailingServerReference; |
|
| 80 | +import com.sap.sailing.domain.base.SailingServerConfiguration; |
|
| 81 | +import com.sap.sailing.domain.base.Series; |
|
| 82 | +import com.sap.sailing.domain.base.Sideline; |
|
| 83 | +import com.sap.sailing.domain.base.Waypoint; |
|
| 84 | +import com.sap.sailing.domain.base.configuration.DeviceConfiguration; |
|
| 85 | +import com.sap.sailing.domain.base.configuration.DeviceConfigurationIdentifier; |
|
| 86 | +import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher; |
|
| 87 | +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; |
|
| 88 | +import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationMapImpl; |
|
| 89 | +import com.sap.sailing.domain.base.impl.DynamicCompetitor; |
|
| 90 | +import com.sap.sailing.domain.base.impl.EventImpl; |
|
| 91 | +import com.sap.sailing.domain.base.impl.RegattaImpl; |
|
| 92 | +import com.sap.sailing.domain.base.impl.RemoteSailingServerReferenceImpl; |
|
| 93 | +import com.sap.sailing.domain.common.DataImportProgress; |
|
| 94 | +import com.sap.sailing.domain.common.Distance; |
|
| 95 | +import com.sap.sailing.domain.common.Position; |
|
| 96 | +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; |
|
| 97 | +import com.sap.sailing.domain.common.RegattaIdentifier; |
|
| 98 | +import com.sap.sailing.domain.common.RegattaName; |
|
| 99 | +import com.sap.sailing.domain.common.Renamable; |
|
| 100 | +import com.sap.sailing.domain.common.ScoringSchemeType; |
|
| 101 | +import com.sap.sailing.domain.common.TrackedRaceStatusEnum; |
|
| 102 | +import com.sap.sailing.domain.common.Wind; |
|
| 103 | +import com.sap.sailing.domain.common.WindSource; |
|
| 104 | +import com.sap.sailing.domain.common.dto.FleetDTO; |
|
| 105 | +import com.sap.sailing.domain.common.dto.RegattaCreationParametersDTO; |
|
| 106 | +import com.sap.sailing.domain.common.dto.SeriesCreationParametersDTO; |
|
| 107 | +import com.sap.sailing.domain.common.impl.DataImportProgressImpl; |
|
| 108 | +import com.sap.sailing.domain.common.media.MediaTrack; |
|
| 109 | +import com.sap.sailing.domain.common.racelog.RacingProcedureType; |
|
| 110 | +import com.sap.sailing.domain.common.tracking.GPSFix; |
|
| 111 | +import com.sap.sailing.domain.common.tracking.GPSFixMoving; |
|
| 112 | +import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; |
|
| 113 | +import com.sap.sailing.domain.leaderboard.FlexibleRaceColumn; |
|
| 114 | +import com.sap.sailing.domain.leaderboard.Leaderboard; |
|
| 115 | +import com.sap.sailing.domain.leaderboard.LeaderboardGroup; |
|
| 116 | +import com.sap.sailing.domain.leaderboard.LeaderboardRegistry; |
|
| 117 | +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; |
|
| 118 | +import com.sap.sailing.domain.leaderboard.ScoringScheme; |
|
| 119 | +import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl; |
|
| 120 | +import com.sap.sailing.domain.leaderboard.impl.LeaderboardGroupImpl; |
|
| 121 | +import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl; |
|
| 122 | +import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl; |
|
| 123 | +import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard; |
|
| 124 | +import com.sap.sailing.domain.persistence.DomainObjectFactory; |
|
| 125 | +import com.sap.sailing.domain.persistence.MongoObjectFactory; |
|
| 126 | +import com.sap.sailing.domain.persistence.MongoRaceLogStoreFactory; |
|
| 127 | +import com.sap.sailing.domain.persistence.MongoRegattaLogStoreFactory; |
|
| 128 | +import com.sap.sailing.domain.persistence.MongoWindStore; |
|
| 129 | +import com.sap.sailing.domain.persistence.MongoWindStoreFactory; |
|
| 130 | +import com.sap.sailing.domain.persistence.PersistenceFactory; |
|
| 131 | +import com.sap.sailing.domain.persistence.media.MediaDB; |
|
| 132 | +import com.sap.sailing.domain.persistence.media.MediaDBFactory; |
|
| 133 | +import com.sap.sailing.domain.persistence.racelog.tracking.MongoGPSFixStoreFactory; |
|
| 134 | +import com.sap.sailing.domain.polars.PolarDataService; |
|
| 135 | +import com.sap.sailing.domain.racelog.RaceLogIdentifier; |
|
| 136 | +import com.sap.sailing.domain.racelog.RaceLogStore; |
|
| 137 | +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; |
|
| 138 | +import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; |
|
| 139 | +import com.sap.sailing.domain.ranking.RankingMetric.CompetitorRankingInfo; |
|
| 140 | +import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo; |
|
| 141 | +import com.sap.sailing.domain.ranking.RankingMetricConstructor; |
|
| 142 | +import com.sap.sailing.domain.regattalike.HasRegattaLike; |
|
| 143 | +import com.sap.sailing.domain.regattalike.IsRegattaLike; |
|
| 144 | +import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike; |
|
| 145 | +import com.sap.sailing.domain.regattalog.RegattaLogStore; |
|
| 146 | +import com.sap.sailing.domain.tracking.DynamicTrackedRace; |
|
| 147 | +import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; |
|
| 148 | +import com.sap.sailing.domain.tracking.GPSFixTrack; |
|
| 149 | +import com.sap.sailing.domain.tracking.MarkPassing; |
|
| 150 | +import com.sap.sailing.domain.tracking.RaceChangeListener; |
|
| 151 | +import com.sap.sailing.domain.tracking.RaceHandle; |
|
| 152 | +import com.sap.sailing.domain.tracking.RaceListener; |
|
| 153 | +import com.sap.sailing.domain.tracking.RaceTracker; |
|
| 154 | +import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters; |
|
| 155 | +import com.sap.sailing.domain.tracking.TrackedRace; |
|
| 156 | +import com.sap.sailing.domain.tracking.TrackedRaceStatus; |
|
| 157 | +import com.sap.sailing.domain.tracking.TrackedRegatta; |
|
| 158 | +import com.sap.sailing.domain.tracking.WindStore; |
|
| 159 | +import com.sap.sailing.domain.tracking.WindTracker; |
|
| 160 | +import com.sap.sailing.domain.tracking.WindTrackerFactory; |
|
| 161 | +import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener; |
|
| 162 | +import com.sap.sailing.domain.tracking.impl.DynamicGPSFixTrackImpl; |
|
| 163 | +import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl; |
|
| 164 | +import com.sap.sailing.domain.tracking.impl.TrackedRaceImpl; |
|
| 165 | +import com.sap.sailing.expeditionconnector.ExpeditionWindTrackerFactory; |
|
| 166 | +import com.sap.sailing.server.RacingEventService; |
|
| 167 | +import com.sap.sailing.server.Replicator; |
|
| 168 | +import com.sap.sailing.server.gateway.deserialization.impl.CourseAreaJsonDeserializer; |
|
| 169 | +import com.sap.sailing.server.gateway.deserialization.impl.EventBaseJsonDeserializer; |
|
| 170 | +import com.sap.sailing.server.gateway.deserialization.impl.LeaderboardGroupBaseJsonDeserializer; |
|
| 171 | +import com.sap.sailing.server.gateway.deserialization.impl.LeaderboardSearchResultBaseJsonDeserializer; |
|
| 172 | +import com.sap.sailing.server.gateway.deserialization.impl.VenueJsonDeserializer; |
|
| 173 | +import com.sap.sailing.server.masterdata.DataImportLockWithProgress; |
|
| 174 | +import com.sap.sailing.server.operationaltransformation.AddCourseAreas; |
|
| 175 | +import com.sap.sailing.server.operationaltransformation.AddDefaultRegatta; |
|
| 176 | +import com.sap.sailing.server.operationaltransformation.AddMediaTrackOperation; |
|
| 177 | +import com.sap.sailing.server.operationaltransformation.AddRaceDefinition; |
|
| 178 | +import com.sap.sailing.server.operationaltransformation.AddSpecificRegatta; |
|
| 179 | +import com.sap.sailing.server.operationaltransformation.ConnectTrackedRaceToLeaderboardColumn; |
|
| 180 | +import com.sap.sailing.server.operationaltransformation.CreateEvent; |
|
| 181 | +import com.sap.sailing.server.operationaltransformation.CreateOrUpdateDataImportProgress; |
|
| 182 | +import com.sap.sailing.server.operationaltransformation.CreateOrUpdateDeviceConfiguration; |
|
| 183 | +import com.sap.sailing.server.operationaltransformation.CreateTrackedRace; |
|
| 184 | +import com.sap.sailing.server.operationaltransformation.DataImportFailed; |
|
| 185 | +import com.sap.sailing.server.operationaltransformation.RecordCompetitorGPSFix; |
|
| 186 | +import com.sap.sailing.server.operationaltransformation.RecordMarkGPSFix; |
|
| 187 | +import com.sap.sailing.server.operationaltransformation.RecordMarkGPSFixForExistingTrack; |
|
| 188 | +import com.sap.sailing.server.operationaltransformation.RecordMarkGPSFixForNewMarkTrack; |
|
| 189 | +import com.sap.sailing.server.operationaltransformation.RecordWindFix; |
|
| 190 | +import com.sap.sailing.server.operationaltransformation.RemoveDeviceConfiguration; |
|
| 191 | +import com.sap.sailing.server.operationaltransformation.RemoveEvent; |
|
| 192 | +import com.sap.sailing.server.operationaltransformation.RemoveMediaTrackOperation; |
|
| 193 | +import com.sap.sailing.server.operationaltransformation.RemoveWindFix; |
|
| 194 | +import com.sap.sailing.server.operationaltransformation.RenameEvent; |
|
| 195 | +import com.sap.sailing.server.operationaltransformation.SetDataImportDeleteProgressFromMapTimer; |
|
| 196 | +import com.sap.sailing.server.operationaltransformation.TrackRegatta; |
|
| 197 | +import com.sap.sailing.server.operationaltransformation.UpdateCompetitor; |
|
| 198 | +import com.sap.sailing.server.operationaltransformation.UpdateEndOfTracking; |
|
| 199 | +import com.sap.sailing.server.operationaltransformation.UpdateMarkPassings; |
|
| 200 | +import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackDurationOperation; |
|
| 201 | +import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackRacesOperation; |
|
| 202 | +import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackStartTimeOperation; |
|
| 203 | +import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackTitleOperation; |
|
| 204 | +import com.sap.sailing.server.operationaltransformation.UpdateMediaTrackUrlOperation; |
|
| 205 | +import com.sap.sailing.server.operationaltransformation.UpdateRaceDelayToLive; |
|
| 206 | +import com.sap.sailing.server.operationaltransformation.UpdateStartOfTracking; |
|
| 207 | +import com.sap.sailing.server.operationaltransformation.UpdateStartTimeReceived; |
|
| 208 | +import com.sap.sailing.server.operationaltransformation.UpdateTrackedRaceStatus; |
|
| 209 | +import com.sap.sailing.server.operationaltransformation.UpdateWindAveragingTime; |
|
| 210 | +import com.sap.sailing.server.operationaltransformation.UpdateWindSourcesToExclude; |
|
| 211 | +import com.sap.sailing.server.simulation.SimulationService; |
|
| 212 | +import com.sap.sailing.server.simulation.SimulationServiceFactory; |
|
| 213 | +import com.sap.sse.ServerInfo; |
|
| 214 | +import com.sap.sse.common.TimePoint; |
|
| 215 | +import com.sap.sse.common.TypeBasedServiceFinderFactory; |
|
| 216 | +import com.sap.sse.common.Util; |
|
| 217 | +import com.sap.sse.common.Util.Pair; |
|
| 218 | +import com.sap.sse.common.Util.Triple; |
|
| 219 | +import com.sap.sse.common.impl.MillisecondsTimePoint; |
|
| 220 | +import com.sap.sse.common.search.KeywordQuery; |
|
| 221 | +import com.sap.sse.common.search.Result; |
|
| 222 | +import com.sap.sse.common.search.ResultImpl; |
|
| 223 | +import com.sap.sse.concurrent.LockUtil; |
|
| 224 | +import com.sap.sse.concurrent.NamedReentrantReadWriteLock; |
|
| 225 | +import com.sap.sse.filestorage.FileStorageManagementService; |
|
| 226 | +import com.sap.sse.replication.OperationExecutionListener; |
|
| 227 | +import com.sap.sse.replication.OperationWithResult; |
|
| 228 | +import com.sap.sse.replication.ReplicationMasterDescriptor; |
|
| 229 | +import com.sap.sse.replication.impl.OperationWithResultWithIdWrapper; |
|
| 230 | +import com.sap.sse.shared.media.ImageDescriptor; |
|
| 231 | +import com.sap.sse.shared.media.VideoDescriptor; |
|
| 232 | +import com.sap.sse.util.ClearStateTestSupport; |
|
| 233 | +import com.sap.sse.util.JoinedClassLoader; |
|
| 234 | +import com.sap.sse.util.impl.ThreadFactoryWithPriority; |
|
| 235 | + |
|
| 236 | +public class RacingEventServiceImpl implements RacingEventService, ClearStateTestSupport, RegattaListener, |
|
| 237 | + LeaderboardRegistry, Replicator, EventFetcher { |
|
| 238 | + private static final Logger logger = Logger.getLogger(RacingEventServiceImpl.class.getName()); |
|
| 239 | + |
|
| 240 | + /** |
|
| 241 | + * A scheduler for the periodic checks of the paramURL documents for the advent of {@link ControlPoint}s with static |
|
| 242 | + * position information otherwise not available through <code>MarkPassingReceiver</code>'s events. |
|
| 243 | + */ |
|
| 244 | + private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, new ThreadFactoryWithPriority(Thread.NORM_PRIORITY, /* daemon */ true)); |
|
| 245 | + |
|
| 246 | + private final com.sap.sailing.domain.base.DomainFactory baseDomainFactory; |
|
| 247 | + |
|
| 248 | + /** |
|
| 249 | + * Holds the {@link Event} objects for those event registered with this service. Note that there may be |
|
| 250 | + * {@link Event} objects that exist outside this service for events not (yet) registered here. |
|
| 251 | + */ |
|
| 252 | + private final ConcurrentHashMap<Serializable, Event> eventsById; |
|
| 253 | + |
|
| 254 | + private final RemoteSailingServerSet remoteSailingServerSet; |
|
| 255 | + |
|
| 256 | + /** |
|
| 257 | + * Holds the {@link Regatta} objects for those races registered with this service. Note that there may be |
|
| 258 | + * {@link Regatta} objects that exist outside this service for regattas not (yet) registered here. |
|
| 259 | + */ |
|
| 260 | + protected final ConcurrentHashMap<String, Regatta> regattasByName; |
|
| 261 | + |
|
| 262 | + private final NamedReentrantReadWriteLock regattasByNameLock; |
|
| 263 | + |
|
| 264 | + private final ConcurrentHashMap<RaceDefinition, CourseChangeReplicator> courseListeners; |
|
| 265 | + |
|
| 266 | + protected final ConcurrentHashMap<Regatta, Set<RaceTracker>> raceTrackersByRegatta; |
|
| 267 | + |
|
| 268 | + /** |
|
| 269 | + * Although {@link #raceTrackersByRegatta} is a concurrent hash map, entering sets as values needs to be |
|
| 270 | + * synchronized using this lock's write lock to avoid two value sets overwriting each other. |
|
| 271 | + */ |
|
| 272 | + private final NamedReentrantReadWriteLock raceTrackersByRegattaLock; |
|
| 273 | + |
|
| 274 | + /** |
|
| 275 | + * Remembers the trackers by paramURL/liveURI/storedURI to avoid duplication |
|
| 276 | + */ |
|
| 277 | + protected final ConcurrentHashMap<Object, RaceTracker> raceTrackersByID; |
|
| 278 | + |
|
| 279 | + /** |
|
| 280 | + * {@link #addRace(RegattaIdentifier, RaceTrackingConnectivityParameters, long)} will check |
|
| 281 | + * {@link #raceTrackersByID} for the presence of a tracker and won't create a new tracker if one for the |
|
| 282 | + * connectivity parameters' ID already exists. This check and creation and addition to {@link #raceTrackersByID} |
|
| 283 | + * requires locking in the face of concurrent calls to |
|
| 284 | + * {@link #addRace(RegattaIdentifier, RaceTrackingConnectivityParameters, long)}. Using <code>synchronized</code> is |
|
| 285 | + * not ideal due to its coarse-grained locking style which allows for little concurrency. Instead, this map is used |
|
| 286 | + * to keep locks for any ID that any invocation of the |
|
| 287 | + * {@link #addRace(RegattaIdentifier, RaceTrackingConnectivityParameters, long)} method is currently working on. |
|
| 288 | + * Fetching or creating and putting a lock to this map happens in ({@link #getOrCreateRaceTrackersByIdLock}) which |
|
| 289 | + * takes care of managing concurrent access to this concurrent map. When done, the |
|
| 290 | + * {@link #addRace(RegattaIdentifier, RaceTrackingConnectivityParameters, long)} method cleans up by removing the |
|
| 291 | + * lock again from this map, again using a synchronized method ( |
|
| 292 | + * {@link #unlockRaceTrackersById(Object, NamedReentrantReadWriteLock)}). |
|
| 293 | + */ |
|
| 294 | + private final ConcurrentHashMap<Object, NamedReentrantReadWriteLock> raceTrackersByIDLocks; |
|
| 295 | + |
|
| 296 | + /** |
|
| 297 | + * Leaderboards managed by this racing event service |
|
| 298 | + */ |
|
| 299 | + private final ConcurrentHashMap<String, Leaderboard> leaderboardsByName; |
|
| 300 | + |
|
| 301 | + /** |
|
| 302 | + * {@link #leaderboardsByName} is already a concurrent hash map; however, when renaming a leaderboard, this shall |
|
| 303 | + * happen as an atomic transaction, not interruptible by other write accesses on the same map because otherwise |
|
| 304 | + * assumptions made during the rename process wouldn't hold. See, in particular, |
|
| 305 | + * {@link #renameLeaderboard(String, String)}. |
|
| 306 | + */ |
|
| 307 | + private final NamedReentrantReadWriteLock leaderboardsByNameLock; |
|
| 308 | + |
|
| 309 | + private final ConcurrentHashMap<String, LeaderboardGroup> leaderboardGroupsByName; |
|
| 310 | + |
|
| 311 | + private final ConcurrentHashMap<UUID, LeaderboardGroup> leaderboardGroupsByID; |
|
| 312 | + |
|
| 313 | + /** |
|
| 314 | + * See {@link #leaderboardsByNameLock} |
|
| 315 | + */ |
|
| 316 | + private final NamedReentrantReadWriteLock leaderboardGroupsByNameLock; |
|
| 317 | + |
|
| 318 | + private final CompetitorStore competitorStore; |
|
| 319 | + |
|
| 320 | + /** |
|
| 321 | + * A set based on a concurrent hash map, therefore being thread safe |
|
| 322 | + */ |
|
| 323 | + private Set<DynamicTrackedRegatta> regattasObservedForDefaultLeaderboard = Collections |
|
| 324 | + .newSetFromMap(new ConcurrentHashMap<DynamicTrackedRegatta, Boolean>()); |
|
| 325 | + |
|
| 326 | + private final MongoObjectFactory mongoObjectFactory; |
|
| 327 | + |
|
| 328 | + private final DomainObjectFactory domainObjectFactory; |
|
| 329 | + |
|
| 330 | + private final ConcurrentHashMap<Regatta, DynamicTrackedRegatta> regattaTrackingCache; |
|
| 331 | + |
|
| 332 | + /** |
|
| 333 | + * Protects write access transactions that do a previous read to {@link #regattaTrackingCache}; read-only access is |
|
| 334 | + * already synchronized by using a concurrent hash map for {@link #regattaTrackingCache}. |
|
| 335 | + */ |
|
| 336 | + private final NamedReentrantReadWriteLock regattaTrackingCacheLock; |
|
| 337 | + |
|
| 338 | + private final ConcurrentHashMap<OperationExecutionListener<RacingEventService>, OperationExecutionListener<RacingEventService>> operationExecutionListeners; |
|
| 339 | + |
|
| 340 | + /** |
|
| 341 | + * Keys are the toString() representation of the {@link RaceDefinition#getId() IDs} of races passed to |
|
| 342 | + * {@link #setRegattaForRace(Regatta, RaceDefinition)}. |
|
| 343 | + */ |
|
| 344 | + private final ConcurrentHashMap<String, Regatta> persistentRegattasForRaceIDs; |
|
| 345 | + |
|
| 346 | + private final RaceLogReplicator raceLogReplicator; |
|
| 347 | + private final RegattaLogReplicator regattaLogReplicator; |
|
| 348 | + |
|
| 349 | + private final RaceLogScoringReplicator raceLogScoringReplicator; |
|
| 350 | + |
|
| 351 | + private final MediaDB mediaDB; |
|
| 352 | + |
|
| 353 | + private final MediaLibrary mediaLibrary; |
|
| 354 | + |
|
| 355 | + /** |
|
| 356 | + * Currently valid pairs of {@link DeviceConfigurationMatcher}s and {@link DeviceConfiguration}s. The contents of |
|
| 357 | + * this map is persisted and replicated. See {@link DeviceConfigurationMapImpl}. |
|
| 358 | + */ |
|
| 359 | + protected final DeviceConfigurationMapImpl configurationMap; |
|
| 360 | + |
|
| 361 | + private final WindStore windStore; |
|
| 362 | + private final GPSFixStore gpsFixStore; |
|
| 363 | + |
|
| 364 | + /** |
|
| 365 | + * This author should be used for server generated race log events |
|
| 366 | + */ |
|
| 367 | + private final AbstractLogEventAuthor raceLogEventAuthorForServer = new LogEventAuthorImpl( |
|
| 368 | + RacingEventService.class.getName(), 0); |
|
| 369 | + |
|
| 370 | + private PolarDataService polarDataService; |
|
| 371 | + |
|
| 372 | + private final SimulationService simulationService; |
|
| 373 | + |
|
| 374 | + /** |
|
| 375 | + * Allow only one master data import at a time to avoid situation where multiple Imports override each other in |
|
| 376 | + * unpredictable fashion |
|
| 377 | + */ |
|
| 378 | + private final DataImportLockWithProgress dataImportLock; |
|
| 379 | + |
|
| 380 | + /** |
|
| 381 | + * If this service runs in the context of an OSGi environment, the activator should {@link #setBundleContext set the |
|
| 382 | + * bundle context} on this object so that service lookups become possible. |
|
| 383 | + */ |
|
| 384 | + private BundleContext bundleContext; |
|
| 385 | + |
|
| 386 | + private TypeBasedServiceFinderFactory serviceFinderFactory; |
|
| 387 | + |
|
| 388 | + /** |
|
| 389 | + * The master from which this replicable is currently replicating, or <code>null</code> if this replicable is not |
|
| 390 | + * currently replicated from any master. |
|
| 391 | + */ |
|
| 392 | + private ReplicationMasterDescriptor replicatingFromMaster; |
|
| 393 | + |
|
| 394 | + private Set<OperationWithResultWithIdWrapper<?, ?>> operationsSentToMasterForReplication; |
|
| 395 | + |
|
| 396 | + private ThreadLocal<Boolean> currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster = ThreadLocal |
|
| 397 | + .withInitial(() -> false); |
|
| 398 | + |
|
| 399 | + private final Set<ClassLoader> masterDataClassLoaders = new HashSet<ClassLoader>(); |
|
| 400 | + |
|
| 401 | + private final JoinedClassLoader joinedClassLoader; |
|
| 402 | + |
|
| 403 | + private SailingServerConfiguration sailingServerConfiguration; |
|
| 404 | + |
|
| 405 | + /** |
|
| 406 | + * Providing the constructor parameters for a new {@link RacingEventServiceImpl} instance is a bit tricky |
|
| 407 | + * in some cases because containment and initialization order of some types is fairly tightly coupled. |
|
| 408 | + * There is a dependency of many such objects on an instance of {@link RaceLogResolver} which is implemented |
|
| 409 | + * by {@link RacingEventServiceImpl}. However, therefore, this instance only becomes available in the |
|
| 410 | + * innermost constructor. |
|
| 411 | + * |
|
| 412 | + * @author Axel Uhl (d043530) |
|
| 413 | + * |
|
| 414 | + */ |
|
| 415 | + public static interface ConstructorParameters { |
|
| 416 | + DomainObjectFactory getDomainObjectFactory(); |
|
| 417 | + MongoObjectFactory getMongoObjectFactory(); |
|
| 418 | + com.sap.sailing.domain.base.DomainFactory getBaseDomainFactory(); |
|
| 419 | + CompetitorStore getCompetitorStore(); |
|
| 420 | + } |
|
| 421 | + |
|
| 422 | + /** |
|
| 423 | + * Constructs a {@link DomainFactory base domain factory} that uses this object's {@link #competitorStore competitor |
|
| 424 | + * store} for competitor management. This base domain factory is then also used for the construction of the |
|
| 425 | + * {@link DomainObjectFactory}. This constructor variant initially clears the persistent competitor collection, |
|
| 426 | + * hence removes all previously persistent competitors. This is the default for testing and for backward |
|
| 427 | + * compatibility with prior releases that did not support a persistent competitor collection. |
|
| 428 | + */ |
|
| 429 | + public RacingEventServiceImpl() { |
|
| 430 | + this(true, null); |
|
| 431 | + } |
|
| 432 | + |
|
| 433 | + public RacingEventServiceImpl(WindStore windStore, GPSFixStore gpsFixStore, |
|
| 434 | + TypeBasedServiceFinderFactory serviceFinderFactory) { |
|
| 435 | + this(true, windStore, gpsFixStore, serviceFinderFactory); |
|
| 436 | + } |
|
| 437 | + |
|
| 438 | + void setBundleContext(BundleContext bundleContext) { |
|
| 439 | + this.bundleContext = bundleContext; |
|
| 440 | + } |
|
| 441 | + |
|
| 442 | + /** |
|
| 443 | + * Like {@link #RacingEventServiceImpl()}, but allows callers to specify that the persistent competitor collection |
|
| 444 | + * be cleared before the service starts. |
|
| 445 | + * |
|
| 446 | + * @param clearPersistentCompetitorStore |
|
| 447 | + * if <code>true</code>, the {@link PersistentCompetitorStore} is created empty, with the corresponding |
|
| 448 | + * database collection cleared as well. Use with caution! When used with <code>false</code>, competitors |
|
| 449 | + * created and stored during previous service executions will initially be loaded. |
|
| 450 | + */ |
|
| 451 | + public RacingEventServiceImpl(boolean clearPersistentCompetitorStore, final TypeBasedServiceFinderFactory serviceFinderFactory) { |
|
| 452 | + this((final RaceLogResolver raceLogResolver)-> { |
|
| 453 | + return new ConstructorParameters() { |
|
| 454 | + private final MongoObjectFactory mongoObjectFactory = PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(serviceFinderFactory); |
|
| 455 | + private final PersistentCompetitorStore competitorStore = new PersistentCompetitorStore( |
|
| 456 | + PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(serviceFinderFactory), |
|
| 457 | + clearPersistentCompetitorStore, serviceFinderFactory, raceLogResolver); |
|
| 458 | + |
|
| 459 | + @Override public DomainObjectFactory getDomainObjectFactory() { return competitorStore.getDomainObjectFactory(); } |
|
| 460 | + @Override public MongoObjectFactory getMongoObjectFactory() { return mongoObjectFactory; } |
|
| 461 | + @Override public DomainFactory getBaseDomainFactory() { return competitorStore.getBaseDomainFactory(); } |
|
| 462 | + @Override public CompetitorStore getCompetitorStore() { return competitorStore; } |
|
| 463 | + }; |
|
| 464 | + }, MediaDBFactory.INSTANCE.getDefaultMediaDB(), null, null, serviceFinderFactory); |
|
| 465 | + } |
|
| 466 | + |
|
| 467 | + private RacingEventServiceImpl(final boolean clearPersistentCompetitorStore, WindStore windStore, |
|
| 468 | + GPSFixStore gpsFixStore, final TypeBasedServiceFinderFactory serviceFinderFactory) { |
|
| 469 | + this((final RaceLogResolver raceLogResolver)-> { |
|
| 470 | + return new ConstructorParameters() { |
|
| 471 | + private final MongoObjectFactory mongoObjectFactory = PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(serviceFinderFactory); |
|
| 472 | + private final PersistentCompetitorStore competitorStore = new PersistentCompetitorStore( |
|
| 473 | + mongoObjectFactory, |
|
| 474 | + clearPersistentCompetitorStore, serviceFinderFactory, raceLogResolver); |
|
| 475 | + |
|
| 476 | + @Override public DomainObjectFactory getDomainObjectFactory() { return competitorStore.getDomainObjectFactory(); } |
|
| 477 | + @Override public MongoObjectFactory getMongoObjectFactory() { return mongoObjectFactory; } |
|
| 478 | + @Override public DomainFactory getBaseDomainFactory() { return competitorStore.getBaseDomainFactory(); } |
|
| 479 | + @Override public CompetitorStore getCompetitorStore() { return competitorStore; } |
|
| 480 | + }; |
|
| 481 | + }, MediaDBFactory.INSTANCE.getDefaultMediaDB(), windStore, gpsFixStore, serviceFinderFactory); |
|
| 482 | + } |
|
| 483 | + |
|
| 484 | + /** |
|
| 485 | + * Uses the default factories for the tracking adapters and the {@link DomainFactory base domain factory} of the |
|
| 486 | + * {@link PersistenceFactory#getDefaultDomainObjectFactory() default domain object factory}. This constructor should |
|
| 487 | + * be used for testing because it provides a transient {@link CompetitorStore} as required for competitor |
|
| 488 | + * persistence. |
|
| 489 | + */ |
|
| 490 | + public RacingEventServiceImpl(Function<RaceLogResolver, DomainObjectFactory> domainObjectFactoryProvider, |
|
| 491 | + final MongoObjectFactory mongoObjectFactory, MediaDB mediaDB, WindStore windStore, GPSFixStore gpsFixStore) { |
|
| 492 | + this((final RaceLogResolver raceLogResolver)-> { |
|
| 493 | + return new ConstructorParameters() { |
|
| 494 | + private final DomainObjectFactory domainObjectFactory = domainObjectFactoryProvider.apply(raceLogResolver); |
|
| 495 | + |
|
| 496 | + @Override public DomainObjectFactory getDomainObjectFactory() { return domainObjectFactory; } |
|
| 497 | + @Override public MongoObjectFactory getMongoObjectFactory() { return mongoObjectFactory; } |
|
| 498 | + @Override public DomainFactory getBaseDomainFactory() { return domainObjectFactory.getBaseDomainFactory(); } |
|
| 499 | + @Override public CompetitorStore getCompetitorStore() { return getBaseDomainFactory().getCompetitorStore(); } |
|
| 500 | + }; |
|
| 501 | + }, mediaDB, windStore, gpsFixStore, null); |
|
| 502 | + } |
|
| 503 | + |
|
| 504 | + public RacingEventServiceImpl(final DomainObjectFactory domainObjectFactory, MongoObjectFactory mongoObjectFactory, |
|
| 505 | + MediaDB mediaDB, WindStore windStore, GPSFixStore gpsFixStore) { |
|
| 506 | + this((final RaceLogResolver raceLogResolver)-> { |
|
| 507 | + return new ConstructorParameters() { |
|
| 508 | + @Override public DomainObjectFactory getDomainObjectFactory() { return domainObjectFactory; } |
|
| 509 | + @Override public MongoObjectFactory getMongoObjectFactory() { return mongoObjectFactory; } |
|
| 510 | + @Override public DomainFactory getBaseDomainFactory() { return domainObjectFactory.getBaseDomainFactory(); } |
|
| 511 | + @Override public CompetitorStore getCompetitorStore() { return getBaseDomainFactory().getCompetitorStore(); } |
|
| 512 | + }; |
|
| 513 | + }, mediaDB, windStore, gpsFixStore, null); |
|
| 514 | + } |
|
| 515 | + |
|
| 516 | + /** |
|
| 517 | + * @param windStore |
|
| 518 | + * if <code>null</code>, a default {@link MongoWindStore} will be used, based on the persistence set-up |
|
| 519 | + * of this service |
|
| 520 | + * @param serviceFinderFactory |
|
| 521 | + * used to find the services handling specific types of tracking devices, such as the persistent storage |
|
| 522 | + * of {@link DeviceIdentifier}s of specific device types or the managing of the device-to-competitor |
|
| 523 | + * associations per race tracked. |
|
| 524 | + */ |
|
| 525 | + public RacingEventServiceImpl(Function<RaceLogResolver, ConstructorParameters> constructorParametersProvider, MediaDB mediaDb, |
|
| 526 | + WindStore windStore, GPSFixStore gpsFixStore, TypeBasedServiceFinderFactory serviceFinderFactory) { |
|
| 527 | + logger.info("Created " + this); |
|
| 528 | + final ConstructorParameters constructorParameters = constructorParametersProvider.apply(this); |
|
| 529 | + this.domainObjectFactory = constructorParameters.getDomainObjectFactory(); |
|
| 530 | + this.masterDataClassLoaders.add(this.getClass().getClassLoader()); |
|
| 531 | + joinedClassLoader = new JoinedClassLoader(masterDataClassLoaders); |
|
| 532 | + this.operationsSentToMasterForReplication = new HashSet<>(); |
|
| 533 | + this.baseDomainFactory = constructorParameters.getBaseDomainFactory(); |
|
| 534 | + this.mongoObjectFactory = constructorParameters.getMongoObjectFactory(); |
|
| 535 | + this.mediaDB = mediaDb; |
|
| 536 | + this.competitorStore = constructorParameters.getCompetitorStore(); |
|
| 537 | + if (windStore == null) { |
|
| 538 | + try { |
|
| 539 | + windStore = MongoWindStoreFactory.INSTANCE.getMongoWindStore(mongoObjectFactory, domainObjectFactory); |
|
| 540 | + } catch (Exception e) { |
|
| 541 | + throw new RuntimeException(e); |
|
| 542 | + } |
|
| 543 | + } |
|
| 544 | + this.competitorStore.addCompetitorUpdateListener(new CompetitorUpdateListener() { |
|
| 545 | + @Override |
|
| 546 | + public void competitorUpdated(Competitor competitor) { |
|
| 547 | + replicate(new UpdateCompetitor(competitor.getId().toString(), competitor.getName(), competitor |
|
| 548 | + .getColor(), competitor.getEmail(), competitor.getBoat().getSailID(), competitor.getTeam().getNationality(), |
|
| 549 | + competitor.getTeam().getImage(), competitor.getFlagImage(), |
|
| 550 | + competitor.getTimeOnTimeFactor(), competitor.getTimeOnDistanceAllowancePerNauticalMile())); |
|
| 551 | + } |
|
| 552 | + }); |
|
| 553 | + this.windStore = windStore; |
|
| 554 | + this.dataImportLock = new DataImportLockWithProgress(); |
|
| 555 | + |
|
| 556 | + remoteSailingServerSet = new RemoteSailingServerSet(scheduler); |
|
| 557 | + regattasByName = new ConcurrentHashMap<String, Regatta>(); |
|
| 558 | + regattasByNameLock = new NamedReentrantReadWriteLock("regattasByName for " + this, /* fair */false); |
|
| 559 | + eventsById = new ConcurrentHashMap<Serializable, Event>(); |
|
| 560 | + regattaTrackingCache = new ConcurrentHashMap<>(); |
|
| 561 | + regattaTrackingCacheLock = new NamedReentrantReadWriteLock("regattaTrackingCache for " + this, /* fair */false); |
|
| 562 | + raceTrackersByRegatta = new ConcurrentHashMap<>(); |
|
| 563 | + raceTrackersByRegattaLock = new NamedReentrantReadWriteLock("raceTrackersByRegatta for " + this, /* fair */ |
|
| 564 | + false); |
|
| 565 | + raceTrackersByID = new ConcurrentHashMap<>(); |
|
| 566 | + raceTrackersByIDLocks = new ConcurrentHashMap<>(); |
|
| 567 | + leaderboardGroupsByName = new ConcurrentHashMap<>(); |
|
| 568 | + leaderboardGroupsByID = new ConcurrentHashMap<>(); |
|
| 569 | + leaderboardGroupsByNameLock = new NamedReentrantReadWriteLock("leaderboardGroupsByName for " + this, /* fair */ |
|
| 570 | + false); |
|
| 571 | + leaderboardsByName = new ConcurrentHashMap<String, Leaderboard>(); |
|
| 572 | + leaderboardsByNameLock = new NamedReentrantReadWriteLock("leaderboardsByName for " + this, /* fair */false); |
|
| 573 | + operationExecutionListeners = new ConcurrentHashMap<>(); |
|
| 574 | + courseListeners = new ConcurrentHashMap<>(); |
|
| 575 | + persistentRegattasForRaceIDs = new ConcurrentHashMap<>(); |
|
| 576 | + final int SIMULATION_THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors()/3, 1); |
|
| 577 | + Executor simulatorExecutor = new ThreadPoolExecutor(/* corePoolSize */SIMULATION_THREAD_POOL_SIZE, |
|
| 578 | + /* maximumPoolSize */SIMULATION_THREAD_POOL_SIZE, |
|
| 579 | + /* keepAliveTime */60, TimeUnit.SECONDS, |
|
| 580 | + /* workQueue */new LinkedBlockingQueue<Runnable>()); |
|
| 581 | + // TODO: initialize smart-future-cache for simulation-results and add to simulation-service |
|
| 582 | + simulationService = SimulationServiceFactory.INSTANCE.getService(simulatorExecutor, this); |
|
| 583 | + this.raceLogReplicator = new RaceLogReplicator(this); |
|
| 584 | + this.regattaLogReplicator = new RegattaLogReplicator(this); |
|
| 585 | + this.raceLogScoringReplicator = new RaceLogScoringReplicator(this); |
|
| 586 | + this.mediaLibrary = new MediaLibrary(); |
|
| 587 | + if (gpsFixStore == null) { |
|
| 588 | + try { |
|
| 589 | + gpsFixStore = MongoGPSFixStoreFactory.INSTANCE.getMongoGPSFixStore(mongoObjectFactory, |
|
| 590 | + domainObjectFactory, serviceFinderFactory); |
|
| 591 | + } catch (Exception e) { |
|
| 592 | + e.printStackTrace(); |
|
| 593 | + throw new RuntimeException(e); |
|
| 594 | + } |
|
| 595 | + } |
|
| 596 | + this.gpsFixStore = gpsFixStore; |
|
| 597 | + this.configurationMap = new DeviceConfigurationMapImpl(); |
|
| 598 | + this.serviceFinderFactory = serviceFinderFactory; |
|
| 599 | + |
|
| 600 | + sailingServerConfiguration = domainObjectFactory.loadServerConfiguration(); |
|
| 601 | + final Iterable<Pair<Event, Boolean>> loadedEventsWithRequireStoreFlag = loadStoredEvents(); |
|
| 602 | + loadStoredRegattas(); |
|
| 603 | + loadRaceIDToRegattaAssociations(); |
|
| 604 | + loadStoredLeaderboardsAndGroups(); |
|
| 605 | + loadLinksFromEventsToLeaderboardGroups(); |
|
| 606 | + loadMediaLibary(); |
|
| 607 | + loadStoredDeviceConfigurations(); |
|
| 608 | + loadAllRemoteSailingServersAndSchedulePeriodicEventCacheRefresh(); |
|
| 609 | + |
|
| 610 | + // Stores all events which run through a data migration |
|
| 611 | + // Remark: must be called after loadLinksFromEventsToLeaderboardGroups(), otherwise would loose the Event -> LeaderboardGroup relation |
|
| 612 | + for (Pair<Event, Boolean> eventAndRequireStoreFlag : loadedEventsWithRequireStoreFlag) { |
|
| 613 | + if (eventAndRequireStoreFlag.getB()) { |
|
| 614 | + mongoObjectFactory.storeEvent(eventAndRequireStoreFlag.getA()); |
|
| 615 | + } |
|
| 616 | + } |
|
| 617 | + } |
|
| 618 | + |
|
| 619 | + @Override |
|
| 620 | + public boolean isCurrentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster() { |
|
| 621 | + return currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster.get(); |
|
| 622 | + } |
|
| 623 | + |
|
| 624 | + @Override |
|
| 625 | + public void setCurrentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster( |
|
| 626 | + boolean currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster) { |
|
| 627 | + this.currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster |
|
| 628 | + .set(currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster); |
|
| 629 | + } |
|
| 630 | + |
|
| 631 | + @Override |
|
| 632 | + public PolarDataService getPolarDataService() { |
|
| 633 | + return polarDataService; |
|
| 634 | + } |
|
| 635 | + |
|
| 636 | + @Override |
|
| 637 | + public SimulationService getSimulationService() { |
|
| 638 | + return simulationService; |
|
| 639 | + } |
|
| 640 | + |
|
| 641 | + @Override |
|
| 642 | + public void clearState() throws Exception { |
|
| 643 | + for (String leaderboardGroupName : new ArrayList<>(this.leaderboardGroupsByName.keySet())) { |
|
| 644 | + removeLeaderboardGroup(leaderboardGroupName); |
|
| 645 | + } |
|
| 646 | + for (String leaderboardName : new ArrayList<>(this.leaderboardsByName.keySet())) { |
|
| 647 | + removeLeaderboard(leaderboardName); |
|
| 648 | + } |
|
| 649 | + for (Regatta regatta : new ArrayList<>(this.regattasByName.values())) { |
|
| 650 | + stopTracking(regatta); |
|
| 651 | + removeRegatta(regatta); |
|
| 652 | + } |
|
| 653 | + for (Event event : new ArrayList<>(this.eventsById.values())) { |
|
| 654 | + removeEvent(event.getId()); |
|
| 655 | + } |
|
| 656 | + for (MediaTrack mediaTrack : this.mediaLibrary.allTracks()) { |
|
| 657 | + mediaTrackDeleted(mediaTrack); |
|
| 658 | + } |
|
| 659 | + // TODO clear user store? See bug 2430. |
|
| 660 | + this.competitorStore.clear(); |
|
| 661 | + } |
|
| 662 | + |
|
| 663 | + @Override |
|
| 664 | + public com.sap.sailing.domain.base.DomainFactory getBaseDomainFactory() { |
|
| 665 | + return baseDomainFactory; |
|
| 666 | + } |
|
| 667 | + |
|
| 668 | + @Override |
|
| 669 | + public MongoObjectFactory getMongoObjectFactory() { |
|
| 670 | + return mongoObjectFactory; |
|
| 671 | + } |
|
| 672 | + |
|
| 673 | + @Override |
|
| 674 | + public DomainObjectFactory getDomainObjectFactory() { |
|
| 675 | + return domainObjectFactory; |
|
| 676 | + } |
|
| 677 | + |
|
| 678 | + private void loadRaceIDToRegattaAssociations() { |
|
| 679 | + persistentRegattasForRaceIDs.putAll(domainObjectFactory.loadRaceIDToRegattaAssociations(this)); |
|
| 680 | + } |
|
| 681 | + |
|
| 682 | + private void loadStoredRegattas() { |
|
| 683 | + LockUtil.lockForWrite(regattasByNameLock); |
|
| 684 | + try { |
|
| 685 | + for (Regatta regatta : domainObjectFactory.loadAllRegattas(this)) { |
|
| 686 | + logger.info("putting regatta " + regatta.getName() + " (" + regatta.hashCode() |
|
| 687 | + + ") into regattasByName"); |
|
| 688 | + regattasByName.put(regatta.getName(), regatta); |
|
| 689 | + regatta.addRegattaListener(this); |
|
| 690 | + onRegattaLikeAdded(regatta); |
|
| 691 | + regatta.addRaceColumnListener(raceLogReplicator); |
|
| 692 | + regatta.addRaceColumnListener(raceLogScoringReplicator); |
|
| 693 | + } |
|
| 694 | + } finally { |
|
| 695 | + LockUtil.unlockAfterWrite(regattasByNameLock); |
|
| 696 | + } |
|
| 697 | + } |
|
| 698 | + |
|
| 699 | + private Iterable<Pair<Event, Boolean>> loadStoredEvents() { |
|
| 700 | + Iterable<Pair<Event, Boolean>> loadedEventsWithRequireStoreFlag = domainObjectFactory.loadAllEvents(); |
|
| 701 | + for (Pair<Event, Boolean> eventAndFlag : loadedEventsWithRequireStoreFlag) { |
|
| 702 | + Event event = eventAndFlag.getA(); |
|
| 703 | + if (event.getId() != null) |
|
| 704 | + eventsById.put(event.getId(), event); |
|
| 705 | + } |
|
| 706 | + return loadedEventsWithRequireStoreFlag; |
|
| 707 | + } |
|
| 708 | + |
|
| 709 | + private void loadLinksFromEventsToLeaderboardGroups() { |
|
| 710 | + domainObjectFactory.loadLeaderboardGroupLinksForEvents(/* eventResolver */this, /* leaderboardGroupResolver */ |
|
| 711 | + this); |
|
| 712 | + } |
|
| 713 | + |
|
| 714 | + private void loadAllRemoteSailingServersAndSchedulePeriodicEventCacheRefresh() { |
|
| 715 | + for (RemoteSailingServerReference sailingServer : domainObjectFactory.loadAllRemoteSailingServerReferences()) { |
|
| 716 | + remoteSailingServerSet.add(sailingServer); |
|
| 717 | + } |
|
| 718 | + } |
|
| 719 | + |
|
| 720 | + /** |
|
| 721 | + * Collects media track references from the configured sources (mongo DB by default, ftp folder yet to be |
|
| 722 | + * implemented). The method is expected to be called initially blocking the API until finished. |
|
| 723 | + * |
|
| 724 | + * Subsequent calls (assumed to be triggered from the admin console or in scheduled intervals) don't need to block. |
|
| 725 | + * In that case, the API will simply serve the current state. |
|
| 726 | + * |
|
| 727 | + */ |
|
| 728 | + private void loadMediaLibary() { |
|
| 729 | + Collection<MediaTrack> allDbMediaTracks = mediaDB.loadAllMediaTracks(); |
|
| 730 | + mediaTracksAdded(allDbMediaTracks); |
|
| 731 | + } |
|
| 732 | + |
|
| 733 | + private void loadStoredDeviceConfigurations() { |
|
| 734 | + for (Entry<DeviceConfigurationMatcher, DeviceConfiguration> entry : domainObjectFactory |
|
| 735 | + .loadAllDeviceConfigurations()) { |
|
| 736 | + configurationMap.put(entry.getKey(), entry.getValue()); |
|
| 737 | + } |
|
| 738 | + } |
|
| 739 | + |
|
| 740 | + @Override |
|
| 741 | + public void addLeaderboard(Leaderboard leaderboard) { |
|
| 742 | + LockUtil.lockForWrite(leaderboardsByNameLock); |
|
| 743 | + try { |
|
| 744 | + leaderboardsByName.put(leaderboard.getName(), leaderboard); |
|
| 745 | + } finally { |
|
| 746 | + LockUtil.unlockAfterWrite(leaderboardsByNameLock); |
|
| 747 | + } |
|
| 748 | + // RaceColumns of RegattaLeaderboards are tracked via its Regatta! |
|
| 749 | + if (leaderboard instanceof FlexibleLeaderboard) { |
|
| 750 | + onRegattaLikeAdded(((FlexibleLeaderboard) leaderboard).getRegattaLike()); |
|
| 751 | + leaderboard.addRaceColumnListener(raceLogReplicator); |
|
| 752 | + leaderboard.addRaceColumnListener(raceLogScoringReplicator); |
|
| 753 | + } |
|
| 754 | + } |
|
| 755 | + |
|
| 756 | + private void loadStoredLeaderboardsAndGroups() { |
|
| 757 | + logger.info("loading stored leaderboards and groups"); |
|
| 758 | + // Loading all leaderboard groups and the contained leaderboards |
|
| 759 | + for (LeaderboardGroup leaderboardGroup : domainObjectFactory.getAllLeaderboardGroups(this, this)) { |
|
| 760 | + logger.info("loaded leaderboard group " + leaderboardGroup.getName() + " into " + this); |
|
| 761 | + LockUtil.lockForWrite(leaderboardGroupsByNameLock); |
|
| 762 | + try { |
|
| 763 | + leaderboardGroupsByName.put(leaderboardGroup.getName(), leaderboardGroup); |
|
| 764 | + leaderboardGroupsByID.put(leaderboardGroup.getId(), leaderboardGroup); |
|
| 765 | + } finally { |
|
| 766 | + LockUtil.unlockAfterWrite(leaderboardGroupsByNameLock); |
|
| 767 | + } |
|
| 768 | + } |
|
| 769 | + // Loading the remaining leaderboards |
|
| 770 | + domainObjectFactory.getLeaderboardsNotInGroup(this, this); |
|
| 771 | + logger.info("done with loading stored leaderboards and groups"); |
|
| 772 | + } |
|
| 773 | + |
|
| 774 | + @Override |
|
| 775 | + public FlexibleLeaderboard addFlexibleLeaderboard(String leaderboardName, String leaderboardDisplayName, |
|
| 776 | + int[] discardThresholds, ScoringScheme scoringScheme, Serializable courseAreaId) { |
|
| 777 | + logger.info("adding flexible leaderboard " + leaderboardName); |
|
| 778 | + CourseArea courseArea = getCourseArea(courseAreaId); |
|
| 779 | + FlexibleLeaderboard result = new FlexibleLeaderboardImpl(getRaceLogStore(), getRegattaLogStore(), |
|
| 780 | + leaderboardName, new ThresholdBasedResultDiscardingRuleImpl(discardThresholds), scoringScheme, |
|
| 781 | + courseArea); |
|
| 782 | + result.setDisplayName(leaderboardDisplayName); |
|
| 783 | + if (getLeaderboardByName(leaderboardName) != null) { |
|
| 784 | + throw new IllegalArgumentException("Leaderboard with name " + leaderboardName + " already exists"); |
|
| 785 | + } |
|
| 786 | + addLeaderboard(result); |
|
| 787 | + mongoObjectFactory.storeLeaderboard(result); |
|
| 788 | + return result; |
|
| 789 | + } |
|
| 790 | + |
|
| 791 | + @Override |
|
| 792 | + public CourseArea getCourseArea(Serializable courseAreaId) { |
|
| 793 | + for (Event event : getAllEvents()) { |
|
| 794 | + for (CourseArea courseArea : event.getVenue().getCourseAreas()) { |
|
| 795 | + if (courseArea.getId().equals(courseAreaId)) { |
|
| 796 | + return courseArea; |
|
| 797 | + } |
|
| 798 | + } |
|
| 799 | + } |
|
| 800 | + return null; |
|
| 801 | + } |
|
| 802 | + |
|
| 803 | + @Override |
|
| 804 | + public RegattaLeaderboard addRegattaLeaderboard(RegattaIdentifier regattaIdentifier, String leaderboardDisplayName, |
|
| 805 | + int[] discardThresholds) { |
|
| 806 | + Regatta regatta = getRegatta(regattaIdentifier); |
|
| 807 | + logger.info("adding regatta leaderboard for regatta " |
|
| 808 | + + (regatta == null ? "null" : (regatta.getName() + " (" + regatta.hashCode() + ")")) + " to " + this); |
|
| 809 | + RegattaLeaderboard result = null; |
|
| 810 | + if (regatta != null) { |
|
| 811 | + result = new RegattaLeaderboardImpl(regatta, new ThresholdBasedResultDiscardingRuleImpl(discardThresholds)); |
|
| 812 | + result.setDisplayName(leaderboardDisplayName); |
|
| 813 | + if (getLeaderboardByName(result.getName()) != null) { |
|
| 814 | + throw new IllegalArgumentException("Leaderboard with name " + result.getName() + " already exists in " |
|
| 815 | + + this); |
|
| 816 | + } |
|
| 817 | + addLeaderboard(result); |
|
| 818 | + mongoObjectFactory.storeLeaderboard(result); |
|
| 819 | + } else { |
|
| 820 | + logger.warning("Cannot find regatta " + regattaIdentifier |
|
| 821 | + + ". Hence, cannot create regatta leaderboard for it."); |
|
| 822 | + } |
|
| 823 | + return result; |
|
| 824 | + } |
|
| 825 | + |
|
| 826 | + @Override |
|
| 827 | + public RaceColumn addColumnToLeaderboard(String columnName, String leaderboardName, boolean medalRace) { |
|
| 828 | + Leaderboard leaderboard = getLeaderboardByName(leaderboardName); |
|
| 829 | + if (leaderboard != null) { |
|
| 830 | + if (leaderboard instanceof FlexibleLeaderboard) { |
|
| 831 | + // uses the default fleet as the single fleet for the new column |
|
| 832 | + RaceColumn result = ((FlexibleLeaderboard) leaderboard).addRaceColumn(columnName, medalRace); |
|
| 833 | + updateStoredLeaderboard((FlexibleLeaderboard) leaderboard); |
|
| 834 | + return result; |
|
| 835 | + } else { |
|
| 836 | + throw new IllegalArgumentException("Leaderboard named " + leaderboardName |
|
| 837 | + + " is not a FlexibleLeaderboard"); |
|
| 838 | + } |
|
| 839 | + } else { |
|
| 840 | + throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found"); |
|
| 841 | + } |
|
| 842 | + } |
|
| 843 | + |
|
| 844 | + @Override |
|
| 845 | + public void moveLeaderboardColumnUp(String leaderboardName, String columnName) { |
|
| 846 | + Leaderboard leaderboard = getLeaderboardByName(leaderboardName); |
|
| 847 | + if (leaderboard != null && leaderboard instanceof FlexibleLeaderboard) { |
|
| 848 | + ((FlexibleLeaderboard) leaderboard).moveRaceColumnUp(columnName); |
|
| 849 | + updateStoredLeaderboard((FlexibleLeaderboard) leaderboard); |
|
| 850 | + } else { |
|
| 851 | + throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found"); |
|
| 852 | + } |
|
| 853 | + } |
|
| 854 | + |
|
| 855 | + @Override |
|
| 856 | + public void moveLeaderboardColumnDown(String leaderboardName, String columnName) { |
|
| 857 | + Leaderboard leaderboard = getLeaderboardByName(leaderboardName); |
|
| 858 | + if (leaderboard != null && leaderboard instanceof FlexibleLeaderboard) { |
|
| 859 | + ((FlexibleLeaderboard) leaderboard).moveRaceColumnDown(columnName); |
|
| 860 | + updateStoredLeaderboard((FlexibleLeaderboard) leaderboard); |
|
| 861 | + } else { |
|
| 862 | + throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found"); |
|
| 863 | + } |
|
| 864 | + } |
|
| 865 | + |
|
| 866 | + @Override |
|
| 867 | + public void removeLeaderboardColumn(String leaderboardName, String columnName) { |
|
| 868 | + Leaderboard leaderboard = getLeaderboardByName(leaderboardName); |
|
| 869 | + if (leaderboard == null) { |
|
| 870 | + throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found"); |
|
| 871 | + } else if (!(leaderboard instanceof FlexibleLeaderboard)) { |
|
| 872 | + throw new IllegalArgumentException("Columns cannot be removed from Leaderboard named " + leaderboardName); |
|
| 873 | + } else { |
|
| 874 | + ((FlexibleLeaderboard) leaderboard).removeRaceColumn(columnName); |
|
| 875 | + updateStoredLeaderboard((FlexibleLeaderboard) leaderboard); |
|
| 876 | + } |
|
| 877 | + } |
|
| 878 | + |
|
| 879 | + @Override |
|
| 880 | + public void renameLeaderboardColumn(String leaderboardName, String oldColumnName, String newColumnName) { |
|
| 881 | + Leaderboard leaderboard = getLeaderboardByName(leaderboardName); |
|
| 882 | + if (leaderboard != null) { |
|
| 883 | + final RaceColumn raceColumn = leaderboard.getRaceColumnByName(oldColumnName); |
|
| 884 | + if (raceColumn instanceof FlexibleRaceColumn) { |
|
| 885 | + // remove race log under old identifier; the race log identifier changes |
|
| 886 | + for (Fleet fleet : raceColumn.getFleets()) { |
|
| 887 | + getMongoObjectFactory().removeRaceLog(raceColumn.getRaceLogIdentifier(fleet)); |
|
| 888 | + } |
|
| 889 | + ((FlexibleRaceColumn) raceColumn).setName(newColumnName); |
|
| 890 | + // store the race logs again under the new identifiers |
|
| 891 | + storeRaceLogs(raceColumn); |
|
| 892 | + updateStoredLeaderboard(leaderboard); |
|
| 893 | + } else { |
|
| 894 | + throw new IllegalArgumentException("Race column " + oldColumnName + " cannot be renamed"); |
|
| 895 | + } |
|
| 896 | + } else { |
|
| 897 | + throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found"); |
|
| 898 | + } |
|
| 899 | + } |
|
| 900 | + |
|
| 901 | + /** |
|
| 902 | + * When a race column is renamed, its race log identifiers change. Therefore, the race logs need to be stored under |
|
| 903 | + * the new identifier again to be consistent with the in-memory image again. |
|
| 904 | + */ |
|
| 905 | + private void storeRaceLogs(RaceColumn raceColumn) { |
|
| 906 | + for (Fleet fleet : raceColumn.getFleets()) { |
|
| 907 | + RaceLogIdentifier identifier = raceColumn.getRaceLogIdentifier(fleet); |
|
| 908 | + RaceLogEventVisitor storeVisitor = MongoRaceLogStoreFactory.INSTANCE.getMongoRaceLogStoreVisitor( |
|
| 909 | + identifier, getMongoObjectFactory()); |
|
| 910 | + RaceLog raceLog = raceColumn.getRaceLog(fleet); |
|
| 911 | + raceLog.lockForRead(); |
|
| 912 | + try { |
|
| 913 | + for (RaceLogEvent e : raceLog.getRawFixes()) { |
|
| 914 | + e.accept(storeVisitor); |
|
| 915 | + } |
|
| 916 | + } finally { |
|
| 917 | + raceLog.unlockAfterRead(); |
|
| 918 | + } |
|
| 919 | + } |
|
| 920 | + } |
|
| 921 | + |
|
| 922 | + @Override |
|
| 923 | + public void updateLeaderboardColumnFactor(String leaderboardName, String columnName, Double factor) { |
|
| 924 | + Leaderboard leaderboard = getLeaderboardByName(leaderboardName); |
|
| 925 | + if (leaderboard != null) { |
|
| 926 | + final RaceColumn raceColumn = leaderboard.getRaceColumnByName(columnName); |
|
| 927 | + if (raceColumn != null) { |
|
| 928 | + raceColumn.setFactor(factor); |
|
| 929 | + updateStoredLeaderboard(leaderboard); |
|
| 930 | + } else { |
|
| 931 | + throw new IllegalArgumentException("Race column " + columnName + " not found in leaderboard " |
|
| 932 | + + leaderboardName); |
|
| 933 | + } |
|
| 934 | + } else { |
|
| 935 | + throw new IllegalArgumentException("Leaderboard named " + leaderboardName + " not found"); |
|
| 936 | + } |
|
| 937 | + } |
|
| 938 | + |
|
| 939 | + @Override |
|
| 940 | + public void renameLeaderboard(String oldName, String newName) { |
|
| 941 | + final Leaderboard toRename = leaderboardsByName.get(oldName); |
|
| 942 | + LockUtil.lockForWrite(leaderboardsByNameLock); |
|
| 943 | + try { |
|
| 944 | + if (toRename == null) { |
|
| 945 | + throw new IllegalArgumentException("No leaderboard with name " + oldName + " found"); |
|
| 946 | + } |
|
| 947 | + if (leaderboardsByName.containsKey(newName)) { |
|
| 948 | + throw new IllegalArgumentException("Leaderboard with name " + newName + " already exists"); |
|
| 949 | + } |
|
| 950 | + if (toRename instanceof Renamable) { |
|
| 951 | + ((Renamable) toRename).setName(newName); |
|
| 952 | + leaderboardsByName.remove(oldName); |
|
| 953 | + leaderboardsByName.put(newName, toRename); |
|
| 954 | + } else { |
|
| 955 | + throw new IllegalArgumentException("Leaderboard with name " + newName + " is of type " |
|
| 956 | + + toRename.getClass().getSimpleName() + " and therefore cannot be renamed"); |
|
| 957 | + } |
|
| 958 | + } finally { |
|
| 959 | + LockUtil.unlockAfterWrite(leaderboardsByNameLock); |
|
| 960 | + } |
|
| 961 | + // don't need the lock anymore to update DB |
|
| 962 | + if (toRename instanceof Renamable) { |
|
| 963 | + mongoObjectFactory.renameLeaderboard(oldName, newName); |
|
| 964 | + syncGroupsAfterLeaderboardChange(toRename, true); |
|
| 965 | + } |
|
| 966 | + } |
|
| 967 | + |
|
| 968 | + @Override |
|
| 969 | + public void updateStoredLeaderboard(Leaderboard leaderboard) { |
|
| 970 | + getMongoObjectFactory().storeLeaderboard(leaderboard); |
|
| 971 | + syncGroupsAfterLeaderboardChange(leaderboard, true); |
|
| 972 | + } |
|
| 973 | + |
|
| 974 | + @Override |
|
| 975 | + public void updateStoredRegatta(Regatta regatta) { |
|
| 976 | + if (regatta.isPersistent()) { |
|
| 977 | + mongoObjectFactory.storeRegatta(regatta); |
|
| 978 | + } |
|
| 979 | + } |
|
| 980 | + |
|
| 981 | + /** |
|
| 982 | + * Checks all groups, if they contain a leaderboard with the name of the <code>updatedLeaderboard</code> and |
|
| 983 | + * replaces the one in the group with the updated one.<br /> |
|
| 984 | + * This synchronizes things like the RaceIdentifier in the leaderboard columns. |
|
| 985 | + */ |
|
| 986 | + private void syncGroupsAfterLeaderboardChange(Leaderboard updatedLeaderboard, boolean doDatabaseUpdate) { |
|
| 987 | + boolean groupNeedsUpdate = false; |
|
| 988 | + for (LeaderboardGroup leaderboardGroup : leaderboardGroupsByName.values()) { |
|
| 989 | + for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) { |
|
| 990 | + if (leaderboard == updatedLeaderboard) { |
|
| 991 | + int index = leaderboardGroup.getIndexOf(leaderboard); |
|
| 992 | + leaderboardGroup.removeLeaderboard(leaderboard); |
|
| 993 | + leaderboardGroup.addLeaderboardAt(updatedLeaderboard, index); |
|
| 994 | + groupNeedsUpdate = true; |
|
| 995 | + // TODO we assume that the leaderboard names are unique, so we can break the inner loop here |
|
| 996 | + break; |
|
| 997 | + } |
|
| 998 | + } |
|
| 999 | + |
|
| 1000 | + if (doDatabaseUpdate && groupNeedsUpdate) { |
|
| 1001 | + mongoObjectFactory.storeLeaderboardGroup(leaderboardGroup); |
|
| 1002 | + } |
|
| 1003 | + groupNeedsUpdate = false; |
|
| 1004 | + } |
|
| 1005 | + } |
|
| 1006 | + |
|
| 1007 | + @Override |
|
| 1008 | + public void removeLeaderboard(String leaderboardName) { |
|
| 1009 | + Leaderboard leaderboard = removeLeaderboardFromLeaderboardsByName(leaderboardName); |
|
| 1010 | + if (leaderboard != null) { |
|
| 1011 | + leaderboard.removeRaceColumnListener(raceLogReplicator); |
|
| 1012 | + leaderboard.removeRaceColumnListener(raceLogScoringReplicator); |
|
| 1013 | + mongoObjectFactory.removeLeaderboard(leaderboardName); |
|
| 1014 | + syncGroupsAfterLeaderboardRemove(leaderboardName, true); |
|
| 1015 | + if (leaderboard instanceof FlexibleLeaderboard) { |
|
| 1016 | + onRegattaLikeRemoved(((FlexibleLeaderboard) leaderboard).getRegattaLike()); |
|
| 1017 | + } |
|
| 1018 | + leaderboard.destroy(); |
|
| 1019 | + } |
|
| 1020 | + } |
|
| 1021 | + |
|
| 1022 | + private Leaderboard removeLeaderboardFromLeaderboardsByName(String leaderboardName) { |
|
| 1023 | + LockUtil.lockForWrite(leaderboardsByNameLock); |
|
| 1024 | + try { |
|
| 1025 | + return leaderboardsByName.remove(leaderboardName); |
|
| 1026 | + } finally { |
|
| 1027 | + LockUtil.unlockAfterWrite(leaderboardsByNameLock); |
|
| 1028 | + } |
|
| 1029 | + } |
|
| 1030 | + |
|
| 1031 | + /** |
|
| 1032 | + * Checks all groups, if they contain a leaderboard with the <code>removedLeaderboardName</code> and removes it from |
|
| 1033 | + * the group. |
|
| 1034 | + * |
|
| 1035 | + * @param removedLeaderboardName |
|
| 1036 | + */ |
|
| 1037 | + private void syncGroupsAfterLeaderboardRemove(String removedLeaderboardName, boolean doDatabaseUpdate) { |
|
| 1038 | + boolean groupNeedsUpdate = false; |
|
| 1039 | + for (LeaderboardGroup leaderboardGroup : leaderboardGroupsByName.values()) { |
|
| 1040 | + for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) { |
|
| 1041 | + if (leaderboard.getName().equals(removedLeaderboardName)) { |
|
| 1042 | + leaderboardGroup.removeLeaderboard(leaderboard); |
|
| 1043 | + groupNeedsUpdate = true; |
|
| 1044 | + // TODO we assume that the leaderboard names are unique, so we can break the inner loop here |
|
| 1045 | + break; |
|
| 1046 | + } |
|
| 1047 | + } |
|
| 1048 | + |
|
| 1049 | + if (doDatabaseUpdate && groupNeedsUpdate) { |
|
| 1050 | + mongoObjectFactory.storeLeaderboardGroup(leaderboardGroup); |
|
| 1051 | + } |
|
| 1052 | + groupNeedsUpdate = false; |
|
| 1053 | + } |
|
| 1054 | + } |
|
| 1055 | + |
|
| 1056 | + @Override |
|
| 1057 | + public Leaderboard getLeaderboardByName(String name) { |
|
| 1058 | + return leaderboardsByName.get(name); |
|
| 1059 | + } |
|
| 1060 | + |
|
| 1061 | + @Override |
|
| 1062 | + public Position getMarkPosition(Mark mark, LeaderboardThatHasRegattaLike leaderboard, TimePoint timePoint, RaceLog raceLog) { |
|
| 1063 | + GPSFixTrack<Mark, GPSFix> track = null; |
|
| 1064 | + // If no spanning track is found, the fix closest to the time point requested is used instead |
|
| 1065 | + GPSFix nonSpanningFallback = null; |
|
| 1066 | + for (TrackedRace trackedRace : leaderboard.getTrackedRaces()) { |
|
| 1067 | + GPSFixTrack<Mark, GPSFix> trackCandidate = trackedRace.getTrack(mark); |
|
| 1068 | + if (trackCandidate != null) { |
|
| 1069 | + if (spansTimePoint(trackCandidate, timePoint)) { |
|
| 1070 | + track = trackCandidate; |
|
| 1071 | + break; |
|
| 1072 | + } else { |
|
| 1073 | + nonSpanningFallback = improveTimewiseClosestFix(nonSpanningFallback, trackCandidate, timePoint); |
|
| 1074 | + } |
|
| 1075 | + } |
|
| 1076 | + } |
|
| 1077 | + if (track == null) { // no spanning track found in any tracked race, or no tracked races found |
|
| 1078 | + // try to load from store |
|
| 1079 | + DynamicGPSFixTrackImpl<Mark> loadedTrack = new DynamicGPSFixTrackImpl<Mark>(mark, 0); |
|
| 1080 | + track = loadedTrack; |
|
| 1081 | + Set<AbstractLog<?, ?>> logs = new HashSet<>(); |
|
| 1082 | + logs.add(leaderboard.getRegattaLike().getRegattaLog()); |
|
| 1083 | + if (raceLog == null) { // no race log explicitly provided --> use all race logs |
|
| 1084 | + for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { |
|
| 1085 | + for (Fleet fleet : raceColumn.getFleets()) { |
|
| 1086 | + logs.add(raceColumn.getRaceLog(fleet)); |
|
| 1087 | + } |
|
| 1088 | + } |
|
| 1089 | + } else { |
|
| 1090 | + logs.add(raceLog); |
|
| 1091 | + } |
|
| 1092 | + for (AbstractLog<?, ?> log : logs) { |
|
| 1093 | + try { |
|
| 1094 | + getGPSFixStore().loadMarkTrack(loadedTrack, log, mark); |
|
| 1095 | + } catch (Exception e) { |
|
| 1096 | + logger.log(Level.WARNING, "Couldn't load mark track for mark " + mark + " from log " + log, e); |
|
| 1097 | + } |
|
| 1098 | + } |
|
| 1099 | + } |
|
| 1100 | + Position result = track.getEstimatedPosition(timePoint, /* extrapolate */ false); |
|
| 1101 | + if (result == null) { |
|
| 1102 | + result = nonSpanningFallback == null ? null : nonSpanningFallback.getPosition(); |
|
| 1103 | + } |
|
| 1104 | + return result; |
|
| 1105 | + } |
|
| 1106 | + |
|
| 1107 | + private GPSFix improveTimewiseClosestFix(GPSFix nonSpanningFallback, GPSFixTrack<Mark, GPSFix> track, final TimePoint timePoint) { |
|
| 1108 | + GPSFix lastAtOrBefore = track.getLastFixAtOrBefore(timePoint); |
|
| 1109 | + GPSFix firstAtOrAfter = track.getFirstFixAtOrAfter(timePoint); |
|
| 1110 | + // find the fix closes to timePoint, sorting null values to the end and fixes near timePoint to the beginning |
|
| 1111 | + final List<GPSFix> list = Arrays.asList(nonSpanningFallback, lastAtOrBefore, firstAtOrAfter); |
|
| 1112 | + list.sort(new Comparator<GPSFix>() { |
|
| 1113 | + @Override |
|
| 1114 | + public int compare(GPSFix o1, GPSFix o2) { |
|
| 1115 | + final int result; |
|
| 1116 | + if (o1 == null) { |
|
| 1117 | + if (o2 == null) { |
|
| 1118 | + result = 0; |
|
| 1119 | + } else { |
|
| 1120 | + result = 1; |
|
| 1121 | + } |
|
| 1122 | + } else if (o2 == null) { |
|
| 1123 | + result = -1; |
|
| 1124 | + } else { |
|
| 1125 | + result = new Long(Math.abs(o1.getTimePoint().until(timePoint).asMillis())).compareTo( |
|
| 1126 | + Math.abs(o2.getTimePoint().until(timePoint).asMillis())); |
|
| 1127 | + } |
|
| 1128 | + return result; |
|
| 1129 | + } |
|
| 1130 | + }); |
|
| 1131 | + return list.get(0); |
|
| 1132 | + } |
|
| 1133 | + |
|
| 1134 | + private boolean spansTimePoint(GPSFixTrack<Mark, GPSFix> track, TimePoint timePoint) { |
|
| 1135 | + return track.getLastFixAtOrBefore(timePoint) != null && track.getFirstFixAtOrAfter(timePoint) != null; |
|
| 1136 | + } |
|
| 1137 | + |
|
| 1138 | + @Override |
|
| 1139 | + public Map<String, Leaderboard> getLeaderboards() { |
|
| 1140 | + return Collections.unmodifiableMap(new HashMap<String, Leaderboard>(leaderboardsByName)); |
|
| 1141 | + } |
|
| 1142 | + |
|
| 1143 | + @Override |
|
| 1144 | + public SailingServerConfiguration getSailingServerConfiguration() { |
|
| 1145 | + return sailingServerConfiguration; |
|
| 1146 | + } |
|
| 1147 | + |
|
| 1148 | + @Override |
|
| 1149 | + public void updateServerConfiguration(SailingServerConfiguration serverConfiguration) { |
|
| 1150 | + this.sailingServerConfiguration = serverConfiguration; |
|
| 1151 | + mongoObjectFactory.storeServerConfiguration(serverConfiguration); |
|
| 1152 | + } |
|
| 1153 | + |
|
| 1154 | + @Override |
|
| 1155 | + public Map<RemoteSailingServerReference, com.sap.sse.common.Util.Pair<Iterable<EventBase>, Exception>> getPublicEventsOfAllSailingServers() { |
|
| 1156 | + return remoteSailingServerSet.getCachedEventsForRemoteSailingServers(); // FIXME should probably add our own |
|
| 1157 | + // stuff here... Is it enough to pass on |
|
| 1158 | + // the remote reference URL to the |
|
| 1159 | + // client for leaderboard group URL |
|
| 1160 | + // construction? |
|
| 1161 | + } |
|
| 1162 | + |
|
| 1163 | + @Override |
|
| 1164 | + public RemoteSailingServerReference addRemoteSailingServerReference(String name, URL url) { |
|
| 1165 | + RemoteSailingServerReference result = new RemoteSailingServerReferenceImpl(name, url); |
|
| 1166 | + remoteSailingServerSet.add(result); |
|
| 1167 | + mongoObjectFactory.storeSailingServer(result); |
|
| 1168 | + return result; |
|
| 1169 | + } |
|
| 1170 | + |
|
| 1171 | + @Override |
|
| 1172 | + public Iterable<RemoteSailingServerReference> getLiveRemoteServerReferences() { |
|
| 1173 | + return remoteSailingServerSet.getLiveRemoteServerReferences(); |
|
| 1174 | + } |
|
| 1175 | + |
|
| 1176 | + @Override |
|
| 1177 | + public RemoteSailingServerReference getRemoteServerReferenceByName(String remoteServerReferenceName) { |
|
| 1178 | + return remoteSailingServerSet.getServerReferenceByName(remoteServerReferenceName); |
|
| 1179 | + } |
|
| 1180 | + |
|
| 1181 | + @Override |
|
| 1182 | + public com.sap.sse.common.Util.Pair<Iterable<EventBase>, Exception> updateRemoteServerEventCacheSynchronously( |
|
| 1183 | + RemoteSailingServerReference ref) { |
|
| 1184 | + return remoteSailingServerSet.getEventsOrException(ref); |
|
| 1185 | + } |
|
| 1186 | + |
|
| 1187 | + @Override |
|
| 1188 | + public void removeRemoteSailingServerReference(String name) { |
|
| 1189 | + remoteSailingServerSet.remove(name); |
|
| 1190 | + mongoObjectFactory.removeSailingServer(name); |
|
| 1191 | + } |
|
| 1192 | + |
|
| 1193 | + @Override |
|
| 1194 | + public Iterable<Event> getAllEvents() { |
|
| 1195 | + return Collections.unmodifiableCollection(new ArrayList<Event>(eventsById.values())); |
|
| 1196 | + } |
|
| 1197 | + |
|
| 1198 | + @Override |
|
| 1199 | + public Event getEvent(Serializable id) { |
|
| 1200 | + return id == null ? null : eventsById.get(id); |
|
| 1201 | + } |
|
| 1202 | + |
|
| 1203 | + @Override |
|
| 1204 | + public Iterable<Regatta> getAllRegattas() { |
|
| 1205 | + return Collections.unmodifiableCollection(new ArrayList<Regatta>(regattasByName.values())); |
|
| 1206 | + } |
|
| 1207 | + |
|
| 1208 | + @Override |
|
| 1209 | + public boolean isRaceBeingTracked(Regatta regattaContext, RaceDefinition r) { |
|
| 1210 | + Set<RaceTracker> trackers = raceTrackersByRegatta.get(regattaContext); |
|
| 1211 | + if (trackers != null) { |
|
| 1212 | + for (RaceTracker tracker : trackers) { |
|
| 1213 | + final Set<RaceDefinition> races = tracker.getRaces(); |
|
| 1214 | + if (races != null && races.contains(r)) { |
|
| 1215 | + return true; |
|
| 1216 | + } |
|
| 1217 | + } |
|
| 1218 | + } |
|
| 1219 | + return false; |
|
| 1220 | + } |
|
| 1221 | + |
|
| 1222 | + @Override |
|
| 1223 | + public Regatta getRegattaByName(String name) { |
|
| 1224 | + return name == null ? null : regattasByName.get(name); |
|
| 1225 | + } |
|
| 1226 | + |
|
| 1227 | + @Override |
|
| 1228 | + public Regatta getOrCreateDefaultRegatta(String name, String boatClassName, Serializable id) { |
|
| 1229 | + Regatta result = regattasByName.get(name); |
|
| 1230 | + if (result == null) { |
|
| 1231 | + result = new RegattaImpl(getRaceLogStore(), getRegattaLogStore(), name, getBaseDomainFactory() |
|
| 1232 | + .getOrCreateBoatClass(boatClassName), /* startDate */null, /* endDate */null, this, |
|
| 1233 | + getBaseDomainFactory().createScoringScheme(ScoringSchemeType.LOW_POINT), id, null); |
|
| 1234 | + logger.info("Created default regatta " + result.getName() + " (" + hashCode() + ") on " + this); |
|
| 1235 | + onRegattaLikeAdded(result); |
|
| 1236 | + cacheAndReplicateDefaultRegatta(result); |
|
| 1237 | + } |
|
| 1238 | + return result; |
|
| 1239 | + } |
|
| 1240 | + |
|
| 1241 | + private void onRegattaLikeAdded(IsRegattaLike isRegattaLike) { |
|
| 1242 | + isRegattaLike.addListener(regattaLogReplicator); |
|
| 1243 | + } |
|
| 1244 | + |
|
| 1245 | + private void onRegattaLikeRemoved(IsRegattaLike isRegattaLike) { |
|
| 1246 | + isRegattaLike.removeListener(regattaLogReplicator); |
|
| 1247 | + getRegattaLogStore().removeRegattaLog(isRegattaLike.getRegattaLikeIdentifier()); |
|
| 1248 | + } |
|
| 1249 | + |
|
| 1250 | + @Override |
|
| 1251 | + public Regatta createRegatta(String fullRegattaName, String boatClassName, TimePoint startDate, TimePoint endDate, |
|
| 1252 | + Serializable id, Iterable<? extends Series> series, boolean persistent, ScoringScheme scoringScheme, |
|
| 1253 | + Serializable defaultCourseAreaId, boolean useStartTimeInference, RankingMetricConstructor rankingMetricConstructor) { |
|
| 1254 | + com.sap.sse.common.Util.Pair<Regatta, Boolean> regattaWithCreatedFlag = getOrCreateRegattaWithoutReplication( |
|
| 1255 | + fullRegattaName, boatClassName, startDate, endDate, id, series, persistent, scoringScheme, |
|
| 1256 | + defaultCourseAreaId, useStartTimeInference, rankingMetricConstructor); |
|
| 1257 | + Regatta regatta = regattaWithCreatedFlag.getA(); |
|
| 1258 | + if (regattaWithCreatedFlag.getB()) { |
|
| 1259 | + onRegattaLikeAdded(regatta); |
|
| 1260 | + replicateSpecificRegattaWithoutRaceColumns(regatta); |
|
| 1261 | + } |
|
| 1262 | + return regatta; |
|
| 1263 | + } |
|
| 1264 | + |
|
| 1265 | + @Override |
|
| 1266 | + public void addRegattaWithoutReplication(Regatta regatta) { |
|
| 1267 | + UUID defaultCourseAreaId = null; |
|
| 1268 | + if (regatta.getDefaultCourseArea() != null) { |
|
| 1269 | + defaultCourseAreaId = regatta.getDefaultCourseArea().getId(); |
|
| 1270 | + } |
|
| 1271 | + boolean wasAdded = addAndConnectRegatta(regatta.isPersistent(), defaultCourseAreaId, regatta); |
|
| 1272 | + if (!wasAdded) { |
|
| 1273 | + logger.info("Regatta with name " + regatta.getName() + " already existed, so it hasn't been added."); |
|
| 1274 | + } |
|
| 1275 | + } |
|
| 1276 | + |
|
| 1277 | + private RaceLogStore getRaceLogStore() { |
|
| 1278 | + return MongoRaceLogStoreFactory.INSTANCE.getMongoRaceLogStore(mongoObjectFactory, domainObjectFactory); |
|
| 1279 | + } |
|
| 1280 | + |
|
| 1281 | + private RegattaLogStore getRegattaLogStore() { |
|
| 1282 | + return MongoRegattaLogStoreFactory.INSTANCE.getMongoRegattaLogStore(mongoObjectFactory, domainObjectFactory); |
|
| 1283 | + } |
|
| 1284 | + |
|
| 1285 | + @Override |
|
| 1286 | + public com.sap.sse.common.Util.Pair<Regatta, Boolean> getOrCreateRegattaWithoutReplication(String fullRegattaName, |
|
| 1287 | + String boatClassName, TimePoint startDate, TimePoint endDate, Serializable id, |
|
| 1288 | + Iterable<? extends Series> series, boolean persistent, ScoringScheme scoringScheme, |
|
| 1289 | + Serializable defaultCourseAreaId, boolean useStartTimeInference, RankingMetricConstructor rankingMetricConstructor) { |
|
| 1290 | + CourseArea courseArea = getCourseArea(defaultCourseAreaId); |
|
| 1291 | + Regatta regatta = new RegattaImpl(getRaceLogStore(), getRegattaLogStore(), fullRegattaName, |
|
| 1292 | + getBaseDomainFactory().getOrCreateBoatClass(boatClassName), startDate, endDate, series, persistent, |
|
| 1293 | + scoringScheme, id, courseArea, useStartTimeInference, rankingMetricConstructor); |
|
| 1294 | + boolean wasCreated = addAndConnectRegatta(persistent, defaultCourseAreaId, regatta); |
|
| 1295 | + if (wasCreated) { |
|
| 1296 | + logger.info("Created regatta " + regatta.getName() + " (" + hashCode() + ") on " + this); |
|
| 1297 | + } |
|
| 1298 | + return new com.sap.sse.common.Util.Pair<Regatta, Boolean>(regatta, wasCreated); |
|
| 1299 | + } |
|
| 1300 | + |
|
| 1301 | + private boolean addAndConnectRegatta(boolean persistent, Serializable defaultCourseAreaId, Regatta regatta) { |
|
| 1302 | + boolean wasCreated = false; |
|
| 1303 | + // try a quick read protected by the concurrent hash map implementation |
|
| 1304 | + if (!regattasByName.containsKey(regatta.getName())) { |
|
| 1305 | + LockUtil.lockForWrite(regattasByNameLock); |
|
| 1306 | + try { |
|
| 1307 | + // check again, now that we hold the exclusive write lock |
|
| 1308 | + if (!regattasByName.containsKey(regatta.getName())) { |
|
| 1309 | + wasCreated = true; |
|
| 1310 | + logger.info("putting regatta " + regatta.getName() + " (" + regatta.hashCode() |
|
| 1311 | + + ") into regattasByName of " + this); |
|
| 1312 | + regattasByName.put(regatta.getName(), regatta); |
|
| 1313 | + regatta.addRegattaListener(this); |
|
| 1314 | + regatta.addRaceColumnListener(raceLogReplicator); |
|
| 1315 | + regatta.addRaceColumnListener(raceLogScoringReplicator); |
|
| 1316 | + } |
|
| 1317 | + } finally { |
|
| 1318 | + LockUtil.unlockAfterWrite(regattasByNameLock); |
|
| 1319 | + } |
|
| 1320 | + } |
|
| 1321 | + if (persistent) { |
|
| 1322 | + updateStoredRegatta(regatta); |
|
| 1323 | + } |
|
| 1324 | + for (Event event : getAllEvents()) { |
|
| 1325 | + for (CourseArea eventCourseArea : event.getVenue().getCourseAreas()) { |
|
| 1326 | + if (defaultCourseAreaId != null && eventCourseArea.getId().equals(defaultCourseAreaId)) { |
|
| 1327 | + event.addRegatta(regatta); |
|
| 1328 | + } |
|
| 1329 | + } |
|
| 1330 | + } |
|
| 1331 | + return wasCreated; |
|
| 1332 | + } |
|
| 1333 | + |
|
| 1334 | + @Override |
|
| 1335 | + public void addRace(RegattaIdentifier addToRegatta, RaceDefinition raceDefinition) { |
|
| 1336 | + Regatta regatta = getRegatta(addToRegatta); |
|
| 1337 | + regatta.addRace(raceDefinition); // will trigger the raceAdded operation because this service is listening on |
|
| 1338 | + // all its regattas |
|
| 1339 | + } |
|
| 1340 | + |
|
| 1341 | + /** |
|
| 1342 | + * If the <code>regatta</code> {@link Regatta#isPersistent() is a persistent one}, the association of the race with |
|
| 1343 | + * the regatta is remembered persistently so that {@link #getRememberedRegattaForRace(Serializable)} will provide |
|
| 1344 | + * it. |
|
| 1345 | + */ |
|
| 1346 | + @Override |
|
| 1347 | + public void raceAdded(Regatta regatta, RaceDefinition raceDefinition) { |
|
| 1348 | + if (regatta.isPersistent()) { |
|
| 1349 | + setRegattaForRace(regatta, raceDefinition); |
|
| 1350 | + } |
|
| 1351 | + final CourseChangeReplicator listener = new CourseChangeReplicator(this, regatta, raceDefinition); |
|
| 1352 | + courseListeners.put(raceDefinition, listener); |
|
| 1353 | + raceDefinition.getCourse().addCourseListener(listener); |
|
| 1354 | + replicate(new AddRaceDefinition(regatta.getRegattaIdentifier(), raceDefinition)); |
|
| 1355 | + } |
|
| 1356 | + |
|
| 1357 | + @Override |
|
| 1358 | + public void raceRemoved(Regatta regatta, RaceDefinition raceDefinition) { |
|
| 1359 | + raceDefinition.getCourse().removeCourseListener(courseListeners.remove(raceDefinition)); |
|
| 1360 | + } |
|
| 1361 | + |
|
| 1362 | + private NamedReentrantReadWriteLock lockRaceTrackersById(Object trackerId) { |
|
| 1363 | + NamedReentrantReadWriteLock lock; |
|
| 1364 | + synchronized (raceTrackersByIDLocks) { |
|
| 1365 | + lock = raceTrackersByIDLocks.get(trackerId); |
|
| 1366 | + if (lock == null) { |
|
| 1367 | + lock = new NamedReentrantReadWriteLock("raceTrackersByIDLock for " + trackerId, /* fair */false); |
|
| 1368 | + raceTrackersByIDLocks.put(trackerId, lock); |
|
| 1369 | + } |
|
| 1370 | + } |
|
| 1371 | + LockUtil.lockForWrite(lock); |
|
| 1372 | + return lock; |
|
| 1373 | + } |
|
| 1374 | + |
|
| 1375 | + /** |
|
| 1376 | + * @param lock |
|
| 1377 | + * need to pass the lock obtained from {@link #lockRaceTrackersById(Object)} because a competing thread |
|
| 1378 | + * may already have removed the lock from the {@link #raceTrackersByIDLocks} map |
|
| 1379 | + */ |
|
| 1380 | + private void unlockRaceTrackersById(Object trackerId, NamedReentrantReadWriteLock lock) { |
|
| 1381 | + LockUtil.unlockAfterWrite(lock); |
|
| 1382 | + synchronized (raceTrackersByIDLocks) { |
|
| 1383 | + raceTrackersByIDLocks.remove(trackerId); |
|
| 1384 | + } |
|
| 1385 | + } |
|
| 1386 | + |
|
| 1387 | + @Override |
|
| 1388 | + public RaceHandle addRace(RegattaIdentifier regattaToAddTo, RaceTrackingConnectivityParameters params, |
|
| 1389 | + long timeoutInMilliseconds) throws Exception { |
|
| 1390 | + final Object trackerID = params.getTrackerID(); |
|
| 1391 | + NamedReentrantReadWriteLock raceTrackersByIdLock = lockRaceTrackersById(trackerID); |
|
| 1392 | + try { |
|
| 1393 | + RaceTracker tracker = raceTrackersByID.get(trackerID); |
|
| 1394 | + if (tracker == null) { |
|
| 1395 | + Regatta regatta = regattaToAddTo == null ? null : getRegatta(regattaToAddTo); |
|
| 1396 | + if (regatta == null) { |
|
| 1397 | + // create tracker and use an existing or create a default regatta |
|
| 1398 | + tracker = params.createRaceTracker(this, windStore, gpsFixStore, /* raceLogResolver */ this); |
|
| 1399 | + } else { |
|
| 1400 | + // use the regatta selected by the RaceIdentifier regattaToAddTo |
|
| 1401 | + tracker = params.createRaceTracker(regatta, this, windStore, gpsFixStore, /* raceLogResolver */ this); |
|
| 1402 | + assert tracker.getRegatta() == regatta; |
|
| 1403 | + } |
|
| 1404 | + LockUtil.lockForWrite(raceTrackersByRegattaLock); |
|
| 1405 | + try { |
|
| 1406 | + raceTrackersByID.put(trackerID, tracker); |
|
| 1407 | + Set<RaceTracker> trackers = raceTrackersByRegatta.get(tracker.getRegatta()); |
|
| 1408 | + if (trackers == null) { |
|
| 1409 | + trackers = Collections.newSetFromMap(new ConcurrentHashMap<RaceTracker, Boolean>()); |
|
| 1410 | + raceTrackersByRegatta.put(tracker.getRegatta(), trackers); |
|
| 1411 | + } |
|
| 1412 | + trackers.add(tracker); |
|
| 1413 | + } finally { |
|
| 1414 | + LockUtil.unlockAfterWrite(raceTrackersByRegattaLock); |
|
| 1415 | + } |
|
| 1416 | + // TODO we assume here that the regatta name is unique which necessitates adding the boat class name to |
|
| 1417 | + // it in RegattaImpl constructor |
|
| 1418 | + String regattaName = tracker.getRegatta().getName(); |
|
| 1419 | + Regatta regattaWithName = regattasByName.get(regattaName); |
|
| 1420 | + // TODO we assume here that the regatta name is unique which necessitates adding the boat class name to |
|
| 1421 | + // it in RegattaImpl constructor |
|
| 1422 | + if (regattaWithName != null) { |
|
| 1423 | + if (regattaWithName != tracker.getRegatta()) { |
|
| 1424 | + if (Util.isEmpty(regattaWithName.getAllRaces())) { |
|
| 1425 | + // probably, tracker removed the last races from the old regatta and created a new one |
|
| 1426 | + LockUtil.lockForWrite(regattasByNameLock); |
|
| 1427 | + try { |
|
| 1428 | + regattasByName.remove(regattaName); |
|
| 1429 | + cacheAndReplicateDefaultRegatta(tracker.getRegatta()); |
|
| 1430 | + } finally { |
|
| 1431 | + LockUtil.unlockAfterWrite(regattasByNameLock); |
|
| 1432 | + } |
|
| 1433 | + } else { |
|
| 1434 | + throw new RuntimeException("Internal error. Two regatta objects with equal name " |
|
| 1435 | + + regattaName); |
|
| 1436 | + } |
|
| 1437 | + } |
|
| 1438 | + } else { |
|
| 1439 | + cacheAndReplicateDefaultRegatta(tracker.getRegatta()); |
|
| 1440 | + } |
|
| 1441 | + } else { |
|
| 1442 | + logger.warning("Race tracker with ID "+trackerID+" already found; not tracking twice to avoid race duplication"); |
|
| 1443 | + WindStore existingTrackersWindStore = tracker.getWindStore(); |
|
| 1444 | + if (!existingTrackersWindStore.equals(windStore)) { |
|
| 1445 | + logger.warning("Wind store mismatch. Requested wind store: " + windStore |
|
| 1446 | + + ". Wind store in use by existing tracker: " + existingTrackersWindStore); |
|
| 1447 | + } |
|
| 1448 | + GPSFixStore existingTrackersGPSFixStore = tracker.getGPSFixStore(); |
|
| 1449 | + if (!existingTrackersGPSFixStore.equals(gpsFixStore)) { |
|
| 1450 | + logger.warning("GPSFix store mismatch. Requested GPSFix store: " + gpsFixStore |
|
| 1451 | + + ". GPSFix store in use by existing tracker: " + existingTrackersGPSFixStore); |
|
| 1452 | + } |
|
| 1453 | + } |
|
| 1454 | + if (timeoutInMilliseconds != -1) { |
|
| 1455 | + scheduleAbortTrackerAfterInitialTimeout(tracker, timeoutInMilliseconds); |
|
| 1456 | + } |
|
| 1457 | + return tracker.getRacesHandle(); |
|
| 1458 | + } finally { |
|
| 1459 | + unlockRaceTrackersById(trackerID, raceTrackersByIdLock); |
|
| 1460 | + } |
|
| 1461 | + } |
|
| 1462 | + |
|
| 1463 | + /** |
|
| 1464 | + * The regatta and all its contained {@link Regatta#getAllRaces() races} are replicated to all replicas. |
|
| 1465 | + * |
|
| 1466 | + * @param regatta |
|
| 1467 | + * the series of this regatta must not have any {@link Series#getRaceColumns() race columns associated |
|
| 1468 | + * (yet)}. |
|
| 1469 | + */ |
|
| 1470 | + private void replicateSpecificRegattaWithoutRaceColumns(Regatta regatta) { |
|
| 1471 | + Serializable courseAreaId = null; |
|
| 1472 | + if (regatta.getDefaultCourseArea() != null) { |
|
| 1473 | + courseAreaId = regatta.getDefaultCourseArea().getId(); |
|
| 1474 | + } |
|
| 1475 | + replicate(new AddSpecificRegatta(regatta.getName(), regatta.getBoatClass() == null ? null : regatta |
|
| 1476 | + .getBoatClass().getName(), regatta.getStartDate(), regatta.getEndDate(), regatta.getId(), |
|
| 1477 | + getSeriesWithoutRaceColumnsConstructionParametersAsMap(regatta), regatta.isPersistent(), |
|
| 1478 | + regatta.getScoringScheme(), courseAreaId, regatta.useStartTimeInference(), regatta.getRankingMetricType())); |
|
| 1479 | + RegattaIdentifier regattaIdentifier = regatta.getRegattaIdentifier(); |
|
| 1480 | + for (RaceDefinition race : regatta.getAllRaces()) { |
|
| 1481 | + replicate(new AddRaceDefinition(regattaIdentifier, race)); |
|
| 1482 | + } |
|
| 1483 | + } |
|
| 1484 | + |
|
| 1485 | + private RegattaCreationParametersDTO getSeriesWithoutRaceColumnsConstructionParametersAsMap(Regatta regatta) { |
|
| 1486 | + LinkedHashMap<String, SeriesCreationParametersDTO> result = new LinkedHashMap<String, SeriesCreationParametersDTO>(); |
|
| 1487 | + for (Series s : regatta.getSeries()) { |
|
| 1488 | + assert Util.isEmpty(s.getRaceColumns()); |
|
| 1489 | + List<FleetDTO> fleetNamesAndOrdering = new ArrayList<FleetDTO>(); |
|
| 1490 | + for (Fleet f : s.getFleets()) { |
|
| 1491 | + fleetNamesAndOrdering.add(getBaseDomainFactory().convertToFleetDTO(f)); |
|
| 1492 | + } |
|
| 1493 | + result.put( |
|
| 1494 | + s.getName(), |
|
| 1495 | + new SeriesCreationParametersDTO(fleetNamesAndOrdering, s.isMedal(), s.isStartsWithZeroScore(), s |
|
| 1496 | + .isFirstColumnIsNonDiscardableCarryForward(), s.getResultDiscardingRule() == null ? null |
|
| 1497 | + : s.getResultDiscardingRule().getDiscardIndexResultsStartingWithHowManyRaces(), s |
|
| 1498 | + .hasSplitFleetContiguousScoring())); |
|
| 1499 | + } |
|
| 1500 | + return new RegattaCreationParametersDTO(result); |
|
| 1501 | + } |
|
| 1502 | + |
|
| 1503 | + /** |
|
| 1504 | + * If <code>regatta</code> is not yet in {@link #regattasByName}, it is added, this service is |
|
| 1505 | + * {@link Regatta#addRegattaListener(RegattaListener) added} as regatta listener, and the regatta and all its |
|
| 1506 | + * contained {@link Regatta#getAllRaces() races} are replicated to all replica. |
|
| 1507 | + */ |
|
| 1508 | + private void cacheAndReplicateDefaultRegatta(Regatta regatta) { |
|
| 1509 | + // try a quick read first, protected by regattasByName being a concurrent hash set |
|
| 1510 | + if (!regattasByName.containsKey(regatta.getName())) { |
|
| 1511 | + // now we need to obtain exclusive write access; in between, some other thread may have added a regatta by |
|
| 1512 | + // that |
|
| 1513 | + // name, so we need to check again: |
|
| 1514 | + LockUtil.lockForWrite(regattasByNameLock); |
|
| 1515 | + try { |
|
| 1516 | + if (!regattasByName.containsKey(regatta.getName())) { |
|
| 1517 | + logger.info("putting regatta " + regatta.getName() + " (" + regatta.hashCode() |
|
| 1518 | + + ") into regattasByName of " + this); |
|
| 1519 | + regattasByName.put(regatta.getName(), regatta); |
|
| 1520 | + regatta.addRegattaListener(this); |
|
| 1521 | + regatta.addRaceColumnListener(raceLogReplicator); |
|
| 1522 | + regatta.addRaceColumnListener(raceLogScoringReplicator); |
|
| 1523 | + |
|
| 1524 | + replicate(new AddDefaultRegatta(regatta.getName(), regatta.getBoatClass() == null ? null : regatta |
|
| 1525 | + .getBoatClass().getName(), regatta.getStartDate(), regatta.getEndDate(), regatta.getId())); |
|
| 1526 | + RegattaIdentifier regattaIdentifier = regatta.getRegattaIdentifier(); |
|
| 1527 | + for (RaceDefinition race : regatta.getAllRaces()) { |
|
| 1528 | + replicate(new AddRaceDefinition(regattaIdentifier, race)); |
|
| 1529 | + } |
|
| 1530 | + } |
|
| 1531 | + } finally { |
|
| 1532 | + LockUtil.unlockAfterWrite(regattasByNameLock); |
|
| 1533 | + } |
|
| 1534 | + } |
|
| 1535 | + } |
|
| 1536 | + |
|
| 1537 | + @Override |
|
| 1538 | + public DynamicTrackedRace createTrackedRace(RegattaAndRaceIdentifier raceIdentifier, WindStore windStore, |
|
| 1539 | + GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, |
|
| 1540 | + long millisecondsOverWhichToAverageSpeed, boolean useMarkPassingCalculator) { |
|
| 1541 | + DynamicTrackedRegatta trackedRegatta = getOrCreateTrackedRegatta(getRegatta(raceIdentifier)); |
|
| 1542 | + RaceDefinition race = getRace(raceIdentifier); |
|
| 1543 | + return trackedRegatta.createTrackedRace(race, Collections.<Sideline> emptyList(), windStore, gpsFixStore, |
|
| 1544 | + delayToLiveInMillis, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed, |
|
| 1545 | + /* raceDefinitionSetToUpdate */null, useMarkPassingCalculator, /* raceLogResolver */ this); |
|
| 1546 | + } |
|
| 1547 | + |
|
| 1548 | + private void ensureRegattaIsObservedForDefaultLeaderboardAndAutoLeaderboardLinking( |
|
| 1549 | + DynamicTrackedRegatta trackedRegatta) { |
|
| 1550 | + if (regattasObservedForDefaultLeaderboard.add(trackedRegatta)) { |
|
| 1551 | + trackedRegatta.addRaceListener(new RaceAdditionListener()); |
|
| 1552 | + } |
|
| 1553 | + } |
|
| 1554 | + |
|
| 1555 | + private void stopObservingRegattaForRedaultLeaderboardAndAutoLeaderboardLinking(DynamicTrackedRegatta trackedRegatta) { |
|
| 1556 | + regattasObservedForDefaultLeaderboard.remove(trackedRegatta); |
|
| 1557 | + } |
|
| 1558 | + |
|
| 1559 | + /** |
|
| 1560 | + * A listener class used to ensure that when a tracked race is added to any {@link TrackedRegatta} managed by this |
|
| 1561 | + * service, the service adds the tracked race to the default leaderboard and links it to the leaderboard columns |
|
| 1562 | + * that were previously connected to it. Additionally, a {@link RaceChangeListener} is added to the |
|
| 1563 | + * {@link TrackedRace} which is responsible for triggering the replication of all relevant changes to the tracked |
|
| 1564 | + * race. When a tracked race is removed, the {@link TrackedRaceReplicator} that was added as listener to that |
|
| 1565 | + * tracked race is removed again. |
|
| 1566 | + * |
|
| 1567 | + * A {@link PolarFixCacheUpdater} is added to every race so that polar fixes are aggregated when new GPS fixes |
|
| 1568 | + * arrive. |
|
| 1569 | + * |
|
| 1570 | + * @author Axel Uhl (d043530) |
|
| 1571 | + * |
|
| 1572 | + */ |
|
| 1573 | + private class RaceAdditionListener implements RaceListener, Serializable { |
|
| 1574 | + private static final long serialVersionUID = 1036955460477000265L; |
|
| 1575 | + |
|
| 1576 | + private final Map<TrackedRace, TrackedRaceReplicator> trackedRaceReplicators; |
|
| 1577 | + |
|
| 1578 | + private final Map<TrackedRace, PolarFixCacheUpdater> polarFixCacheUpdaters; |
|
| 1579 | + |
|
| 1580 | + public RaceAdditionListener() { |
|
| 1581 | + this.trackedRaceReplicators = new HashMap<TrackedRace, TrackedRaceReplicator>(); |
|
| 1582 | + this.polarFixCacheUpdaters = new HashMap<TrackedRace, PolarFixCacheUpdater>(); |
|
| 1583 | + } |
|
| 1584 | + |
|
| 1585 | + @Override |
|
| 1586 | + public void raceRemoved(TrackedRace trackedRace) { |
|
| 1587 | + TrackedRaceReplicator trackedRaceReplicator = trackedRaceReplicators.remove(trackedRace); |
|
| 1588 | + if (trackedRaceReplicator != null) { |
|
| 1589 | + trackedRace.removeListener(trackedRaceReplicator); |
|
| 1590 | + } |
|
| 1591 | + PolarFixCacheUpdater polarFixCacheUpdater = polarFixCacheUpdaters.remove(trackedRace); |
|
| 1592 | + if (polarFixCacheUpdater != null) { |
|
| 1593 | + trackedRace.removeListener(polarFixCacheUpdater); |
|
| 1594 | + } |
|
| 1595 | + } |
|
| 1596 | + |
|
| 1597 | + @Override |
|
| 1598 | + public void raceAdded(TrackedRace trackedRace) { |
|
| 1599 | + // replicate the addition of the tracked race: |
|
| 1600 | + CreateTrackedRace op = new CreateTrackedRace(trackedRace.getRaceIdentifier(), trackedRace.getWindStore(), |
|
| 1601 | + trackedRace.getGPSFixStore(), trackedRace.getDelayToLiveInMillis(), |
|
| 1602 | + trackedRace.getMillisecondsOverWhichToAverageWind(), |
|
| 1603 | + trackedRace.getMillisecondsOverWhichToAverageSpeed()); |
|
| 1604 | + replicate(op); |
|
| 1605 | + linkRaceToConfiguredLeaderboardColumns(trackedRace); |
|
| 1606 | + TrackedRaceReplicator trackedRaceReplicator = new TrackedRaceReplicator(trackedRace); |
|
| 1607 | + trackedRaceReplicators.put(trackedRace, trackedRaceReplicator); |
|
| 1608 | + trackedRace.addListener(trackedRaceReplicator, /* fire wind already loaded */true, true); |
|
| 1609 | + |
|
| 1610 | + PolarFixCacheUpdater polarFixCacheUpdater = new PolarFixCacheUpdater(trackedRace); |
|
| 1611 | + polarFixCacheUpdaters.put(trackedRace, polarFixCacheUpdater); |
|
| 1612 | + trackedRace.addListener(polarFixCacheUpdater); |
|
| 1613 | + |
|
| 1614 | + if (polarDataService != null) { |
|
| 1615 | + trackedRace.setPolarDataService(polarDataService); |
|
| 1616 | + } |
|
| 1617 | + } |
|
| 1618 | + } |
|
| 1619 | + |
|
| 1620 | + private class PolarFixCacheUpdater extends AbstractRaceChangeListener { |
|
| 1621 | + |
|
| 1622 | + private final TrackedRace race; |
|
| 1623 | + |
|
| 1624 | + public PolarFixCacheUpdater(TrackedRace race) { |
|
| 1625 | + this.race = race; |
|
| 1626 | + } |
|
| 1627 | + |
|
| 1628 | + @Override |
|
| 1629 | + public void competitorPositionChanged(GPSFixMoving fix, Competitor item) { |
|
| 1630 | + if (polarDataService != null) { |
|
| 1631 | + polarDataService.competitorPositionChanged(fix, item, race); |
|
| 1632 | + } |
|
| 1633 | + } |
|
| 1634 | + |
|
| 1635 | + @Override |
|
| 1636 | + public void statusChanged(TrackedRaceStatus newStatus, TrackedRaceStatus oldStatus) { |
|
| 1637 | + if (oldStatus.getStatus() == TrackedRaceStatusEnum.LOADING |
|
| 1638 | + && newStatus.getStatus() != TrackedRaceStatusEnum.LOADING) { |
|
| 1639 | + if (polarDataService != null) { |
|
| 1640 | + polarDataService.raceFinishedLoading(race); |
|
| 1641 | + } |
|
| 1642 | + } |
|
| 1643 | + } |
|
| 1644 | + |
|
| 1645 | + } |
|
| 1646 | + |
|
| 1647 | + private class TrackedRaceReplicator implements RaceChangeListener { |
|
| 1648 | + private final TrackedRace trackedRace; |
|
| 1649 | + |
|
| 1650 | + public TrackedRaceReplicator(TrackedRace trackedRace) { |
|
| 1651 | + this.trackedRace = trackedRace; |
|
| 1652 | + } |
|
| 1653 | + |
|
| 1654 | + @Override |
|
| 1655 | + public void windSourcesToExcludeChanged(Iterable<? extends WindSource> windSourcesToExclude) { |
|
| 1656 | + replicate(new UpdateWindSourcesToExclude(getRaceIdentifier(), windSourcesToExclude)); |
|
| 1657 | + } |
|
| 1658 | + |
|
| 1659 | + @Override |
|
| 1660 | + public void startOfTrackingChanged(TimePoint startOfTracking) { |
|
| 1661 | + replicate(new UpdateStartOfTracking(getRaceIdentifier(), startOfTracking)); |
|
| 1662 | + } |
|
| 1663 | + |
|
| 1664 | + @Override |
|
| 1665 | + public void endOfTrackingChanged(TimePoint endOfTracking) { |
|
| 1666 | + replicate(new UpdateEndOfTracking(getRaceIdentifier(), endOfTracking)); |
|
| 1667 | + } |
|
| 1668 | + |
|
| 1669 | + @Override |
|
| 1670 | + public void startTimeReceivedChanged(TimePoint startTimeReceived) { |
|
| 1671 | + replicate(new UpdateStartTimeReceived(getRaceIdentifier(), startTimeReceived)); |
|
| 1672 | + } |
|
| 1673 | + |
|
| 1674 | + @Override |
|
| 1675 | + public void startOfRaceChanged(TimePoint oldStartOfRace, TimePoint newStartOfRace) { |
|
| 1676 | + // no action required; the update signaled by this call is implicit; for explicit updates |
|
| 1677 | + // see raceTimesChanged(TimePoint, TimePoint, TimePoint). |
|
| 1678 | + } |
|
| 1679 | + |
|
| 1680 | + @Override |
|
| 1681 | + public void waypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) { |
|
| 1682 | + // no-op; the course change is replicated by the separate CourseChangeReplicator |
|
| 1683 | + } |
|
| 1684 | + |
|
| 1685 | + @Override |
|
| 1686 | + public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) { |
|
| 1687 | + // no-op; the course change is replicated by the separate CourseChangeReplicator |
|
| 1688 | + } |
|
| 1689 | + |
|
| 1690 | + @Override |
|
| 1691 | + public void delayToLiveChanged(long delayToLiveInMillis) { |
|
| 1692 | + replicate(new UpdateRaceDelayToLive(getRaceIdentifier(), delayToLiveInMillis)); |
|
| 1693 | + } |
|
| 1694 | + |
|
| 1695 | + @Override |
|
| 1696 | + public void windDataReceived(Wind wind, WindSource windSource) { |
|
| 1697 | + replicate(new RecordWindFix(getRaceIdentifier(), windSource, wind)); |
|
| 1698 | + } |
|
| 1699 | + |
|
| 1700 | + @Override |
|
| 1701 | + public void windDataRemoved(Wind wind, WindSource windSource) { |
|
| 1702 | + replicate(new RemoveWindFix(getRaceIdentifier(), windSource, wind)); |
|
| 1703 | + } |
|
| 1704 | + |
|
| 1705 | + @Override |
|
| 1706 | + public void windAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) { |
|
| 1707 | + replicate(new UpdateWindAveragingTime(getRaceIdentifier(), newMillisecondsOverWhichToAverage)); |
|
| 1708 | + } |
|
| 1709 | + |
|
| 1710 | + @Override |
|
| 1711 | + public void competitorPositionChanged(GPSFixMoving fix, Competitor competitor) { |
|
| 1712 | + replicate(new RecordCompetitorGPSFix(getRaceIdentifier(), competitor, fix)); |
|
| 1713 | + } |
|
| 1714 | + |
|
| 1715 | + @Override |
|
| 1716 | + public void statusChanged(TrackedRaceStatus newStatus, TrackedRaceStatus oldStatus) { |
|
| 1717 | + replicate(new UpdateTrackedRaceStatus(getRaceIdentifier(), newStatus)); |
|
| 1718 | + } |
|
| 1719 | + |
|
| 1720 | + @Override |
|
| 1721 | + public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) { |
|
| 1722 | + final RecordMarkGPSFix operation; |
|
| 1723 | + if (firstInTrack) { |
|
| 1724 | + operation = new RecordMarkGPSFixForNewMarkTrack(getRaceIdentifier(), mark, fix); |
|
| 1725 | + } else { |
|
| 1726 | + operation = new RecordMarkGPSFixForExistingTrack(getRaceIdentifier(), mark, fix); |
|
| 1727 | + } |
|
| 1728 | + replicate(operation); |
|
| 1729 | + } |
|
| 1730 | + |
|
| 1731 | + @Override |
|
| 1732 | + public void markPassingReceived(Competitor competitor, Map<Waypoint, MarkPassing> oldMarkPassings, |
|
| 1733 | + Iterable<MarkPassing> markPassings) { |
|
| 1734 | + replicate(new UpdateMarkPassings(getRaceIdentifier(), competitor, markPassings)); |
|
| 1735 | + } |
|
| 1736 | + |
|
| 1737 | + @Override |
|
| 1738 | + public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) { |
|
| 1739 | + replicate(new UpdateWindAveragingTime(getRaceIdentifier(), newMillisecondsOverWhichToAverage)); |
|
| 1740 | + } |
|
| 1741 | + |
|
| 1742 | + private RegattaAndRaceIdentifier getRaceIdentifier() { |
|
| 1743 | + return trackedRace.getRaceIdentifier(); |
|
| 1744 | + } |
|
| 1745 | + |
|
| 1746 | + } |
|
| 1747 | + |
|
| 1748 | + /** |
|
| 1749 | + * Based on the <code>trackedRace</code>'s {@link TrackedRace#getRaceIdentifier() race identifier}, the tracked race |
|
| 1750 | + * is (re-)associated to all {@link RaceColumn race columns} that currently have no |
|
| 1751 | + * {@link RaceColumn#getTrackedRace(Fleet) tracked race assigned} and whose |
|
| 1752 | + * {@link RaceColumn#getRaceIdentifier(Fleet) race identifier} equals that of <code>trackedRace</code>. |
|
| 1753 | + */ |
|
| 1754 | + private void linkRaceToConfiguredLeaderboardColumns(TrackedRace trackedRace) { |
|
| 1755 | + boolean leaderboardHasChanged = false; |
|
| 1756 | + RegattaAndRaceIdentifier trackedRaceIdentifier = trackedRace.getRaceIdentifier(); |
|
| 1757 | + for (Leaderboard leaderboard : getLeaderboards().values()) { |
|
| 1758 | + for (RaceColumn column : leaderboard.getRaceColumns()) { |
|
| 1759 | + for (Fleet fleet : column.getFleets()) { |
|
| 1760 | + if (trackedRaceIdentifier.equals(column.getRaceIdentifier(fleet)) |
|
| 1761 | + && column.getTrackedRace(fleet) == null) { |
|
| 1762 | + column.setTrackedRace(fleet, trackedRace); |
|
| 1763 | + leaderboardHasChanged = true; |
|
| 1764 | + replicate(new ConnectTrackedRaceToLeaderboardColumn(leaderboard.getName(), column.getName(), |
|
| 1765 | + fleet.getName(), trackedRaceIdentifier)); |
|
| 1766 | + } |
|
| 1767 | + } |
|
| 1768 | + } |
|
| 1769 | + if (leaderboardHasChanged) { |
|
| 1770 | + // Update the corresponding groups, to keep them in sync |
|
| 1771 | + syncGroupsAfterLeaderboardChange(leaderboard, /* doDatabaseUpdate */false); |
|
| 1772 | + } |
|
| 1773 | + } |
|
| 1774 | + } |
|
| 1775 | + |
|
| 1776 | + @Override |
|
| 1777 | + public void stopTracking(Regatta regatta) throws MalformedURLException, IOException, InterruptedException { |
|
| 1778 | + final Set<RaceTracker> trackersForRegatta = raceTrackersByRegatta.get(regatta); |
|
| 1779 | + if (trackersForRegatta != null) { |
|
| 1780 | + for (RaceTracker raceTracker : trackersForRegatta) { |
|
| 1781 | + final Set<RaceDefinition> races = raceTracker.getRaces(); |
|
| 1782 | + if (races != null) { |
|
| 1783 | + for (RaceDefinition race : races) { |
|
| 1784 | + stopTrackingWind(regatta, race); |
|
| 1785 | + } |
|
| 1786 | + } |
|
| 1787 | + raceTracker.stop(/* preemptive */false); |
|
| 1788 | + final Object trackerId = raceTracker.getID(); |
|
| 1789 | + final NamedReentrantReadWriteLock lock = lockRaceTrackersById(trackerId); |
|
| 1790 | + try { |
|
| 1791 | + raceTrackersByID.remove(trackerId); |
|
| 1792 | + } finally { |
|
| 1793 | + unlockRaceTrackersById(trackerId, lock); |
|
| 1794 | + } |
|
| 1795 | + raceTrackersByID.remove(trackerId); |
|
| 1796 | + } |
|
| 1797 | + LockUtil.lockForWrite(raceTrackersByRegattaLock); |
|
| 1798 | + try { |
|
| 1799 | + raceTrackersByRegatta.remove(regatta); |
|
| 1800 | + } finally { |
|
| 1801 | + LockUtil.unlockAfterWrite(raceTrackersByRegattaLock); |
|
| 1802 | + } |
|
| 1803 | + } |
|
| 1804 | + } |
|
| 1805 | + |
|
| 1806 | + @Override |
|
| 1807 | + public void stopTrackingAndRemove(Regatta regatta) throws MalformedURLException, IOException, InterruptedException { |
|
| 1808 | + stopTracking(regatta); |
|
| 1809 | + if (regatta != null) { |
|
| 1810 | + if (regatta.getName() != null) { |
|
| 1811 | + logger.info("Removing regatta " + regatta.getName() + " (" + regatta.hashCode() + ") from " + this); |
|
| 1812 | + LockUtil.lockForWrite(regattasByNameLock); |
|
| 1813 | + try { |
|
| 1814 | + regattasByName.remove(regatta.getName()); |
|
| 1815 | + } finally { |
|
| 1816 | + LockUtil.unlockAfterWrite(regattasByNameLock); |
|
| 1817 | + } |
|
| 1818 | + LockUtil.lockForWrite(regattaTrackingCacheLock); |
|
| 1819 | + try { |
|
| 1820 | + regattaTrackingCache.remove(regatta); |
|
| 1821 | + } finally { |
|
| 1822 | + LockUtil.unlockAfterWrite(regattaTrackingCacheLock); |
|
| 1823 | + } |
|
| 1824 | + regatta.removeRegattaListener(this); |
|
| 1825 | + regatta.removeRaceColumnListener(raceLogReplicator); |
|
| 1826 | + regatta.removeRaceColumnListener(raceLogScoringReplicator); |
|
| 1827 | + } |
|
| 1828 | + for (RaceDefinition race : regatta.getAllRaces()) { |
|
| 1829 | + stopTrackingWind(regatta, race); |
|
| 1830 | + } |
|
| 1831 | + } |
|
| 1832 | + } |
|
| 1833 | + |
|
| 1834 | + /** |
|
| 1835 | + * The tracker will initially try to connect to the tracking infrastructure to obtain basic race master data. If |
|
| 1836 | + * this fails after some timeout, to avoid garbage and lingering threads, the task scheduled by this method will |
|
| 1837 | + * check after the timeout expires if race master data was successfully received. If so, the tracker continues |
|
| 1838 | + * normally. Otherwise, the tracker is shut down orderly by calling {@link RaceTracker#stop(boolean) stopping}. |
|
| 1839 | + * |
|
| 1840 | + * @return the scheduled task, in case the caller wants to {@link ScheduledFuture#cancel(boolean) cancel} it, e.g., |
|
| 1841 | + * when the tracker is stopped or has successfully received the race |
|
| 1842 | + */ |
|
| 1843 | + private ScheduledFuture<?> scheduleAbortTrackerAfterInitialTimeout(final RaceTracker tracker, |
|
| 1844 | + final long timeoutInMilliseconds) { |
|
| 1845 | + ScheduledFuture<?> task = getScheduler().schedule(new Runnable() { |
|
| 1846 | + @Override |
|
| 1847 | + public void run() { |
|
| 1848 | + if (tracker.getRaces() == null || tracker.getRaces().isEmpty()) { |
|
| 1849 | + try { |
|
| 1850 | + Regatta regatta = tracker.getRegatta(); |
|
| 1851 | + logger.log(Level.SEVERE, "RaceDefinition for a race in regatta " + regatta.getName() |
|
| 1852 | + + " not obtained within " + timeoutInMilliseconds |
|
| 1853 | + + "ms. Aborting tracker for this race."); |
|
| 1854 | + Set<RaceTracker> trackersForRegatta = raceTrackersByRegatta.get(regatta); |
|
| 1855 | + if (trackersForRegatta != null) { |
|
| 1856 | + trackersForRegatta.remove(tracker); |
|
| 1857 | + } |
|
| 1858 | + tracker.stop(/* preemptive */true); |
|
| 1859 | + final Object trackerId = tracker.getID(); |
|
| 1860 | + final NamedReentrantReadWriteLock lock = lockRaceTrackersById(trackerId); |
|
| 1861 | + try { |
|
| 1862 | + raceTrackersByID.remove(trackerId); |
|
| 1863 | + } finally { |
|
| 1864 | + unlockRaceTrackersById(trackerId, lock); |
|
| 1865 | + } |
|
| 1866 | + if (trackersForRegatta == null || trackersForRegatta.isEmpty()) { |
|
| 1867 | + stopTracking(regatta); |
|
| 1868 | + } |
|
| 1869 | + } catch (Exception e) { |
|
| 1870 | + logger.log(Level.SEVERE, "scheduleAbortTrackerAfterInitialTimeout", e); |
|
| 1871 | + e.printStackTrace(); |
|
| 1872 | + } |
|
| 1873 | + } |
|
| 1874 | + } |
|
| 1875 | + }, /* delay */timeoutInMilliseconds, /* unit */TimeUnit.MILLISECONDS); |
|
| 1876 | + return task; |
|
| 1877 | + } |
|
| 1878 | + |
|
| 1879 | + @Override |
|
| 1880 | + public void stopTracking(Regatta regatta, RaceDefinition race) throws MalformedURLException, IOException, |
|
| 1881 | + InterruptedException { |
|
| 1882 | + logger.info("Stopping tracking for " + race + "..."); |
|
| 1883 | + final Set<RaceTracker> trackerSet = raceTrackersByRegatta.get(regatta); |
|
| 1884 | + if (trackerSet != null) { |
|
| 1885 | + Iterator<RaceTracker> trackerIter = trackerSet.iterator(); |
|
| 1886 | + while (trackerIter.hasNext()) { |
|
| 1887 | + RaceTracker raceTracker = trackerIter.next(); |
|
| 1888 | + if (raceTracker.getRaces() != null && raceTracker.getRaces().contains(race)) { |
|
| 1889 | + logger.info("Found tracker to stop for races " + raceTracker.getRaces()); |
|
| 1890 | + raceTracker.stop(/* preemptive */false); |
|
| 1891 | + trackerIter.remove(); |
|
| 1892 | + final Object trackerId = raceTracker.getID(); |
|
| 1893 | + final NamedReentrantReadWriteLock lock = lockRaceTrackersById(trackerId); |
|
| 1894 | + try { |
|
| 1895 | + raceTrackersByID.remove(trackerId); |
|
| 1896 | + } finally { |
|
| 1897 | + unlockRaceTrackersById(trackerId, lock); |
|
| 1898 | + } |
|
| 1899 | + } |
|
| 1900 | + } |
|
| 1901 | + } else { |
|
| 1902 | + logger.warning("Didn't find any trackers for regatta " + regatta); |
|
| 1903 | + } |
|
| 1904 | + stopTrackingWind(regatta, race); |
|
| 1905 | + // if the last tracked race was removed, confirm that tracking for the entire regatta has stopped |
|
| 1906 | + if (trackerSet == null || trackerSet.isEmpty()) { |
|
| 1907 | + stopTracking(regatta); |
|
| 1908 | + } |
|
| 1909 | + } |
|
| 1910 | + |
|
| 1911 | + @Override |
|
| 1912 | + public void removeRegatta(Regatta regatta) throws MalformedURLException, IOException, InterruptedException { |
|
| 1913 | + Set<RegattaLeaderboard> leaderboardsToRemove = new HashSet<>(); |
|
| 1914 | + for (Leaderboard leaderboard : getLeaderboards().values()) { |
|
| 1915 | + if (leaderboard instanceof RegattaLeaderboard) { |
|
| 1916 | + RegattaLeaderboard regattaLeaderboard = (RegattaLeaderboard) leaderboard; |
|
| 1917 | + if (regattaLeaderboard.getRegatta() == regatta) { |
|
| 1918 | + leaderboardsToRemove.add(regattaLeaderboard); |
|
| 1919 | + } |
|
| 1920 | + } |
|
| 1921 | + } |
|
| 1922 | + for (RegattaLeaderboard regattaLeaderboardToRemove : leaderboardsToRemove) { |
|
| 1923 | + removeLeaderboard(regattaLeaderboardToRemove.getName()); |
|
| 1924 | + } |
|
| 1925 | + // avoid ConcurrentModificationException by copying the races to remove: |
|
| 1926 | + Set<RaceDefinition> racesToRemove = new HashSet<>(); |
|
| 1927 | + Util.addAll(regatta.getAllRaces(), racesToRemove); |
|
| 1928 | + for (RaceDefinition race : racesToRemove) { |
|
| 1929 | + removeRace(regatta, race); |
|
| 1930 | + mongoObjectFactory.removeRegattaForRaceID(race.getName(), regatta); |
|
| 1931 | + persistentRegattasForRaceIDs.remove(race.getId().toString()); |
|
| 1932 | + } |
|
| 1933 | + if (regatta.isPersistent()) { |
|
| 1934 | + mongoObjectFactory.removeRegatta(regatta); |
|
| 1935 | + } |
|
| 1936 | + LockUtil.lockForWrite(regattasByNameLock); |
|
| 1937 | + try { |
|
| 1938 | + regattasByName.remove(regatta.getName()); |
|
| 1939 | + } finally { |
|
| 1940 | + LockUtil.unlockAfterWrite(regattasByNameLock); |
|
| 1941 | + } |
|
| 1942 | + regatta.removeRegattaListener(this); |
|
| 1943 | + regatta.removeRaceColumnListener(raceLogReplicator); |
|
| 1944 | + regatta.removeRaceColumnListener(raceLogScoringReplicator); |
|
| 1945 | + onRegattaLikeRemoved(regatta); |
|
| 1946 | + } |
|
| 1947 | + |
|
| 1948 | + @Override |
|
| 1949 | + public void removeSeries(Series series) throws MalformedURLException, IOException, InterruptedException { |
|
| 1950 | + Regatta regatta = series.getRegatta(); |
|
| 1951 | + regatta.removeSeries(series); |
|
| 1952 | + if (regatta.isPersistent()) { |
|
| 1953 | + mongoObjectFactory.storeRegatta(regatta); |
|
| 1954 | + } |
|
| 1955 | + } |
|
| 1956 | + |
|
| 1957 | + @Override |
|
| 1958 | + public Regatta updateRegatta(RegattaIdentifier regattaIdentifier, TimePoint startDate, TimePoint endDate, |
|
| 1959 | + Serializable newDefaultCourseAreaId, RegattaConfiguration newRegattaConfiguration, |
|
| 1960 | + Iterable<? extends Series> series, boolean useStartTimeInference) { |
|
| 1961 | + // We're not doing any renaming of the regatta itself, therefore we don't have to sync on the maps. |
|
| 1962 | + Regatta regatta = getRegatta(regattaIdentifier); |
|
| 1963 | + CourseArea newCourseArea = getCourseArea(newDefaultCourseAreaId); |
|
| 1964 | + if (newCourseArea != regatta.getDefaultCourseArea()) { |
|
| 1965 | + regatta.setDefaultCourseArea(newCourseArea); |
|
| 1966 | + } |
|
| 1967 | + regatta.setStartDate(startDate); |
|
| 1968 | + regatta.setEndDate(endDate); |
|
| 1969 | + if (regatta.useStartTimeInference() != useStartTimeInference) { |
|
| 1970 | + regatta.setUseStartTimeInference(useStartTimeInference); |
|
| 1971 | + final DynamicTrackedRegatta trackedRegatta = getTrackedRegatta(regatta); |
|
| 1972 | + if (trackedRegatta != null) { |
|
| 1973 | + trackedRegatta.lockTrackedRacesForRead(); |
|
| 1974 | + try { |
|
| 1975 | + for (DynamicTrackedRace trackedRace : trackedRegatta.getTrackedRaces()) { |
|
| 1976 | + // the start times of the regatta's tracked races now have to be re-evaluated the next time they |
|
| 1977 | + // are queried |
|
| 1978 | + trackedRace.invalidateStartTime(); |
|
| 1979 | + } |
|
| 1980 | + } finally { |
|
| 1981 | + trackedRegatta.unlockTrackedRacesAfterRead(); |
|
| 1982 | + } |
|
| 1983 | + } |
|
| 1984 | + } |
|
| 1985 | + regatta.setRegattaConfiguration(newRegattaConfiguration); |
|
| 1986 | + if (series != null) { |
|
| 1987 | + for (Series seriesObj : series) { |
|
| 1988 | + regatta.addSeries(seriesObj); |
|
| 1989 | + } |
|
| 1990 | + } |
|
| 1991 | + regatta.adjustEventToRegattaAssociation(this); |
|
| 1992 | + if (regatta.isPersistent()) { |
|
| 1993 | + mongoObjectFactory.storeRegatta(regatta); |
|
| 1994 | + } |
|
| 1995 | + return regatta; |
|
| 1996 | + } |
|
| 1997 | + |
|
| 1998 | + @Override |
|
| 1999 | + public void removeRace(Regatta regatta, RaceDefinition race) throws MalformedURLException, IOException, |
|
| 2000 | + InterruptedException { |
|
| 2001 | + logger.info("Removing the race " + race + "..."); |
|
| 2002 | + stopAllTrackersForWhichRaceIsLastReachable(regatta, race); |
|
| 2003 | + stopTrackingWind(regatta, race); |
|
| 2004 | + TrackedRace trackedRace = getExistingTrackedRace(regatta, race); |
|
| 2005 | + if (trackedRace != null) { |
|
| 2006 | + TrackedRegatta trackedRegatta = getTrackedRegatta(regatta); |
|
| 2007 | + final boolean isTrackedRacesEmpty; |
|
| 2008 | + if (trackedRegatta != null) { |
|
| 2009 | + trackedRegatta.lockTrackedRacesForWrite(); |
|
| 2010 | + try { |
|
| 2011 | + trackedRegatta.removeTrackedRace(trackedRace); |
|
| 2012 | + isTrackedRacesEmpty = Util.isEmpty(trackedRegatta.getTrackedRaces()); |
|
| 2013 | + } finally { |
|
| 2014 | + trackedRegatta.unlockTrackedRacesAfterWrite(); |
|
| 2015 | + } |
|
| 2016 | + } else { |
|
| 2017 | + isTrackedRacesEmpty = false; |
|
| 2018 | + } |
|
| 2019 | + if (isTrackedRacesEmpty) { |
|
| 2020 | + removeTrackedRegatta(regatta); |
|
| 2021 | + } |
|
| 2022 | + // remove tracked race from RaceColumns of regatta |
|
| 2023 | + for (Series series : regatta.getSeries()) { |
|
| 2024 | + for (RaceColumnInSeries raceColumn : series.getRaceColumns()) { |
|
| 2025 | + for (Fleet fleet : series.getFleets()) { |
|
| 2026 | + if (raceColumn.getTrackedRace(fleet) == trackedRace) { |
|
| 2027 | + raceColumn.releaseTrackedRace(fleet); |
|
| 2028 | + } |
|
| 2029 | + } |
|
| 2030 | + } |
|
| 2031 | + } |
|
| 2032 | + for (Leaderboard leaderboard : getLeaderboards().values()) { |
|
| 2033 | + if (leaderboard instanceof FlexibleLeaderboard) { // RegattaLeaderboards have implicitly been updated by |
|
| 2034 | + // the code above |
|
| 2035 | + for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { |
|
| 2036 | + for (Fleet fleet : raceColumn.getFleets()) { |
|
| 2037 | + if (raceColumn.getTrackedRace(fleet) == trackedRace) { |
|
| 2038 | + raceColumn.releaseTrackedRace(fleet); // but leave the RaceIdentifier on the race column |
|
| 2039 | + // untouched, e.g., for later re-load |
|
| 2040 | + } |
|
| 2041 | + } |
|
| 2042 | + } |
|
| 2043 | + } |
|
| 2044 | + } |
|
| 2045 | + } |
|
| 2046 | + // remove the race from the (default) regatta if the regatta is not persistently stored |
|
| 2047 | + regatta.removeRace(race); |
|
| 2048 | + if (!regatta.isPersistent() && Util.isEmpty(regatta.getAllRaces())) { |
|
| 2049 | + logger.info("Removing regatta " + regatta.getName() + " (" + regatta.hashCode() + ") from service " + this); |
|
| 2050 | + LockUtil.lockForWrite(regattasByNameLock); |
|
| 2051 | + try { |
|
| 2052 | + regattasByName.remove(regatta.getName()); |
|
| 2053 | + } finally { |
|
| 2054 | + LockUtil.unlockAfterWrite(regattasByNameLock); |
|
| 2055 | + } |
|
| 2056 | + regatta.removeRegattaListener(this); |
|
| 2057 | + regatta.removeRaceColumnListener(raceLogReplicator); |
|
| 2058 | + regatta.removeRaceColumnListener(raceLogScoringReplicator); |
|
| 2059 | + } |
|
| 2060 | + } |
|
| 2061 | + |
|
| 2062 | + /** |
|
| 2063 | + * Doesn't stop any wind trackers |
|
| 2064 | + */ |
|
| 2065 | + private void stopAllTrackersForWhichRaceIsLastReachable(Regatta regatta, RaceDefinition race) |
|
| 2066 | + throws MalformedURLException, IOException, InterruptedException { |
|
| 2067 | + if (raceTrackersByRegatta.containsKey(regatta)) { |
|
| 2068 | + Iterator<RaceTracker> trackerIter = raceTrackersByRegatta.get(regatta).iterator(); |
|
| 2069 | + while (trackerIter.hasNext()) { |
|
| 2070 | + RaceTracker raceTracker = trackerIter.next(); |
|
| 2071 | + if (raceTracker.getRaces() != null && raceTracker.getRaces().contains(race)) { |
|
| 2072 | + boolean foundReachableRace = false; |
|
| 2073 | + for (RaceDefinition raceTrackedByTracker : raceTracker.getRaces()) { |
|
| 2074 | + if (raceTrackedByTracker != race && isReachable(regatta, raceTrackedByTracker)) { |
|
| 2075 | + foundReachableRace = true; |
|
| 2076 | + break; |
|
| 2077 | + } |
|
| 2078 | + } |
|
| 2079 | + if (!foundReachableRace) { |
|
| 2080 | + // firstly stop the tracker |
|
| 2081 | + raceTracker.stop(/* preemptive */true); |
|
| 2082 | + // remove it from the raceTrackers by Regatta |
|
| 2083 | + trackerIter.remove(); |
|
| 2084 | + final Object trackerId = raceTracker.getID(); |
|
| 2085 | + final NamedReentrantReadWriteLock lock = lockRaceTrackersById(trackerId); |
|
| 2086 | + try { |
|
| 2087 | + raceTrackersByID.remove(trackerId); |
|
| 2088 | + } finally { |
|
| 2089 | + unlockRaceTrackersById(trackerId, lock); |
|
| 2090 | + } |
|
| 2091 | + // if the last tracked race was removed, remove the entire regatta |
|
| 2092 | + if (raceTrackersByRegatta.get(regatta).isEmpty()) { |
|
| 2093 | + stopTracking(regatta); |
|
| 2094 | + } |
|
| 2095 | + } |
|
| 2096 | + } |
|
| 2097 | + } |
|
| 2098 | + } |
|
| 2099 | + } |
|
| 2100 | + |
|
| 2101 | + private boolean isReachable(Regatta regatta, RaceDefinition race) { |
|
| 2102 | + return Util.contains(regatta.getAllRaces(), race); |
|
| 2103 | + } |
|
| 2104 | + |
|
| 2105 | + @Override |
|
| 2106 | + public void startTrackingWind(Regatta regatta, RaceDefinition race, boolean correctByDeclination) throws Exception { |
|
| 2107 | + for (WindTrackerFactory windTrackerFactory : getWindTrackerFactories()) { |
|
| 2108 | + windTrackerFactory.createWindTracker(getOrCreateTrackedRegatta(regatta), race, correctByDeclination); |
|
| 2109 | + } |
|
| 2110 | + } |
|
| 2111 | + |
|
| 2112 | + @Override |
|
| 2113 | + public void stopTrackingWind(Regatta regatta, RaceDefinition race) throws SocketException, IOException { |
|
| 2114 | + for (WindTrackerFactory windTrackerFactory : getWindTrackerFactories()) { |
|
| 2115 | + WindTracker windTracker = windTrackerFactory.getExistingWindTracker(race); |
|
| 2116 | + if (windTracker != null) { |
|
| 2117 | + windTracker.stop(); |
|
| 2118 | + } |
|
| 2119 | + } |
|
| 2120 | + } |
|
| 2121 | + |
|
| 2122 | + @Override |
|
| 2123 | + public Iterable<com.sap.sse.common.Util.Triple<Regatta, RaceDefinition, String>> getWindTrackedRaces() { |
|
| 2124 | + List<com.sap.sse.common.Util.Triple<Regatta, RaceDefinition, String>> result = new ArrayList<com.sap.sse.common.Util.Triple<Regatta, RaceDefinition, String>>(); |
|
| 2125 | + for (Regatta regatta : getAllRegattas()) { |
|
| 2126 | + for (RaceDefinition race : regatta.getAllRaces()) { |
|
| 2127 | + for (WindTrackerFactory windTrackerFactory : getWindTrackerFactories()) { |
|
| 2128 | + WindTracker windTracker = windTrackerFactory.getExistingWindTracker(race); |
|
| 2129 | + if (windTracker != null) { |
|
| 2130 | + result.add(new com.sap.sse.common.Util.Triple<Regatta, RaceDefinition, String>(regatta, race, |
|
| 2131 | + windTracker.toString())); |
|
| 2132 | + } |
|
| 2133 | + } |
|
| 2134 | + } |
|
| 2135 | + } |
|
| 2136 | + return result; |
|
| 2137 | + } |
|
| 2138 | + |
|
| 2139 | + @Override |
|
| 2140 | + public DynamicTrackedRace getTrackedRace(Regatta regatta, RaceDefinition race) { |
|
| 2141 | + return getOrCreateTrackedRegatta(regatta).getTrackedRace(race); |
|
| 2142 | + } |
|
| 2143 | + |
|
| 2144 | + private DynamicTrackedRace getExistingTrackedRace(Regatta regatta, RaceDefinition race) { |
|
| 2145 | + return getOrCreateTrackedRegatta(regatta).getExistingTrackedRace(race); |
|
| 2146 | + } |
|
| 2147 | + |
|
| 2148 | + @Override |
|
| 2149 | + public DynamicTrackedRegatta getOrCreateTrackedRegatta(Regatta regatta) { |
|
| 2150 | + cacheAndReplicateDefaultRegatta(regatta); |
|
| 2151 | + LockUtil.lockForWrite(regattaTrackingCacheLock); |
|
| 2152 | + try { |
|
| 2153 | + DynamicTrackedRegatta result = regattaTrackingCache.get(regatta); |
|
| 2154 | + if (result == null) { |
|
| 2155 | + logger.info("Creating DynamicTrackedRegattaImpl for regatta " + regatta.getName() + " with hashCode " |
|
| 2156 | + + regatta.hashCode()); |
|
| 2157 | + result = new DynamicTrackedRegattaImpl(regatta); |
|
| 2158 | + replicate(new TrackRegatta(regatta.getRegattaIdentifier())); |
|
| 2159 | + regattaTrackingCache.put(regatta, result); |
|
| 2160 | + ensureRegattaIsObservedForDefaultLeaderboardAndAutoLeaderboardLinking(result); |
|
| 2161 | + } |
|
| 2162 | + return result; |
|
| 2163 | + } finally { |
|
| 2164 | + LockUtil.unlockAfterWrite(regattaTrackingCacheLock); |
|
| 2165 | + } |
|
| 2166 | + } |
|
| 2167 | + |
|
| 2168 | + @Override |
|
| 2169 | + public DynamicTrackedRegatta getTrackedRegatta(com.sap.sailing.domain.base.Regatta regatta) { |
|
| 2170 | + return regattaTrackingCache.get(regatta); |
|
| 2171 | + } |
|
| 2172 | + |
|
| 2173 | + @Override |
|
| 2174 | + public void removeTrackedRegatta(Regatta regatta) { |
|
| 2175 | + logger.info("Removing regatta " + regatta.getName() + " from regattaTrackingCache"); |
|
| 2176 | + final DynamicTrackedRegatta trackedRegatta; |
|
| 2177 | + LockUtil.lockForWrite(regattaTrackingCacheLock); |
|
| 2178 | + try { |
|
| 2179 | + trackedRegatta = regattaTrackingCache.remove(regatta); |
|
| 2180 | + } finally { |
|
| 2181 | + LockUtil.unlockAfterWrite(regattaTrackingCacheLock); |
|
| 2182 | + } |
|
| 2183 | + stopObservingRegattaForRedaultLeaderboardAndAutoLeaderboardLinking(trackedRegatta); |
|
| 2184 | + } |
|
| 2185 | + |
|
| 2186 | + @Override |
|
| 2187 | + public Regatta getRegatta(RegattaName regattaName) { |
|
| 2188 | + return (Regatta) regattasByName.get(regattaName.getRegattaName()); |
|
| 2189 | + } |
|
| 2190 | + |
|
| 2191 | + @Override |
|
| 2192 | + public Regatta getRegatta(RegattaIdentifier regattaIdentifier) { |
|
| 2193 | + return (Regatta) regattaIdentifier.getRegatta(this); |
|
| 2194 | + } |
|
| 2195 | + |
|
| 2196 | + @Override |
|
| 2197 | + public DynamicTrackedRace getTrackedRace(RegattaAndRaceIdentifier raceIdentifier) { |
|
| 2198 | + DynamicTrackedRace result = null; |
|
| 2199 | + Regatta regatta = regattasByName.get(raceIdentifier.getRegattaName()); |
|
| 2200 | + if (regatta != null) { |
|
| 2201 | + DynamicTrackedRegatta trackedRegatta = regattaTrackingCache.get(regatta); |
|
| 2202 | + if (trackedRegatta != null) { |
|
| 2203 | + RaceDefinition race = getRace(raceIdentifier); |
|
| 2204 | + if (race != null) { |
|
| 2205 | + result = trackedRegatta.getTrackedRace(race); |
|
| 2206 | + } |
|
| 2207 | + } |
|
| 2208 | + } |
|
| 2209 | + return result; |
|
| 2210 | + } |
|
| 2211 | + |
|
| 2212 | + @Override |
|
| 2213 | + public DynamicTrackedRace getExistingTrackedRace(RegattaAndRaceIdentifier raceIdentifier) { |
|
| 2214 | + Regatta regatta = getRegattaByName(raceIdentifier.getRegattaName()); |
|
| 2215 | + DynamicTrackedRace trackedRace = null; |
|
| 2216 | + if (regatta != null) { |
|
| 2217 | + RaceDefinition race = regatta.getRaceByName(raceIdentifier.getRaceName()); |
|
| 2218 | + trackedRace = getOrCreateTrackedRegatta(regatta).getExistingTrackedRace(race); |
|
| 2219 | + } |
|
| 2220 | + return trackedRace; |
|
| 2221 | + } |
|
| 2222 | + |
|
| 2223 | + @Override |
|
| 2224 | + public RaceDefinition getRace(RegattaAndRaceIdentifier regattaNameAndRaceName) { |
|
| 2225 | + RaceDefinition result = null; |
|
| 2226 | + Regatta regatta = getRegatta(regattaNameAndRaceName); |
|
| 2227 | + if (regatta != null) { |
|
| 2228 | + result = regatta.getRaceByName(regattaNameAndRaceName.getRaceName()); |
|
| 2229 | + } |
|
| 2230 | + return result; |
|
| 2231 | + } |
|
| 2232 | + |
|
| 2233 | + @Override |
|
| 2234 | + public Map<String, LeaderboardGroup> getLeaderboardGroups() { |
|
| 2235 | + return Collections.unmodifiableMap(new HashMap<String, LeaderboardGroup>(leaderboardGroupsByName)); |
|
| 2236 | + } |
|
| 2237 | + |
|
| 2238 | + @Override |
|
| 2239 | + public LeaderboardGroup getLeaderboardGroupByName(String groupName) { |
|
| 2240 | + return leaderboardGroupsByName.get(groupName); |
|
| 2241 | + } |
|
| 2242 | + |
|
| 2243 | + @Override |
|
| 2244 | + public LeaderboardGroup getLeaderboardGroupByID(UUID leaderboardGroupID) { |
|
| 2245 | + return leaderboardGroupsByID.get(leaderboardGroupID); |
|
| 2246 | + } |
|
| 2247 | + |
|
| 2248 | + @Override |
|
| 2249 | + public LeaderboardGroup addLeaderboardGroup(UUID id, String groupName, String description, String displayName, |
|
| 2250 | + boolean displayGroupsInReverseOrder, List<String> leaderboardNames, |
|
| 2251 | + int[] overallLeaderboardDiscardThresholds, ScoringSchemeType overallLeaderboardScoringSchemeType) { |
|
| 2252 | + ArrayList<Leaderboard> leaderboards = new ArrayList<>(); |
|
| 2253 | + for (String leaderboardName : leaderboardNames) { |
|
| 2254 | + Leaderboard leaderboard = leaderboardsByName.get(leaderboardName); |
|
| 2255 | + if (leaderboard == null) { |
|
| 2256 | + throw new IllegalArgumentException("No leaderboard with name " + leaderboardName + " found"); |
|
| 2257 | + } else { |
|
| 2258 | + leaderboards.add(leaderboard); |
|
| 2259 | + } |
|
| 2260 | + } |
|
| 2261 | + LeaderboardGroup result = new LeaderboardGroupImpl(id, groupName, description, displayName, |
|
| 2262 | + displayGroupsInReverseOrder, leaderboards); |
|
| 2263 | + if (overallLeaderboardScoringSchemeType != null) { |
|
| 2264 | + // create overall leaderboard and its discards settings |
|
| 2265 | + addOverallLeaderboardToLeaderboardGroup(result, |
|
| 2266 | + getBaseDomainFactory().createScoringScheme(overallLeaderboardScoringSchemeType), |
|
| 2267 | + overallLeaderboardDiscardThresholds); |
|
| 2268 | + } |
|
| 2269 | + LockUtil.lockForWrite(leaderboardGroupsByNameLock); |
|
| 2270 | + try { |
|
| 2271 | + if (leaderboardGroupsByName.containsKey(groupName)) { |
|
| 2272 | + throw new IllegalArgumentException("Leaderboard group with name " + groupName + " already exists"); |
|
| 2273 | + } |
|
| 2274 | + leaderboardGroupsByName.put(groupName, result); |
|
| 2275 | + leaderboardGroupsByID.put(result.getId(), result); |
|
| 2276 | + } finally { |
|
| 2277 | + LockUtil.unlockAfterWrite(leaderboardGroupsByNameLock); |
|
| 2278 | + } |
|
| 2279 | + mongoObjectFactory.storeLeaderboardGroup(result); |
|
| 2280 | + return result; |
|
| 2281 | + } |
|
| 2282 | + |
|
| 2283 | + @Override |
|
| 2284 | + public void addLeaderboardGroupWithoutReplication(LeaderboardGroup leaderboardGroup) { |
|
| 2285 | + LockUtil.lockForWrite(leaderboardGroupsByNameLock); |
|
| 2286 | + try { |
|
| 2287 | + String groupName = leaderboardGroup.getName(); |
|
| 2288 | + if (leaderboardGroupsByName.containsKey(groupName)) { |
|
| 2289 | + throw new IllegalArgumentException("Leaderboard group with name " + groupName + " already exists"); |
|
| 2290 | + } |
|
| 2291 | + leaderboardGroupsByName.put(groupName, leaderboardGroup); |
|
| 2292 | + leaderboardGroupsByID.put(leaderboardGroup.getId(), leaderboardGroup); |
|
| 2293 | + } finally { |
|
| 2294 | + LockUtil.unlockAfterWrite(leaderboardGroupsByNameLock); |
|
| 2295 | + } |
|
| 2296 | + if (leaderboardGroup.hasOverallLeaderboard()) { |
|
| 2297 | + addLeaderboard(leaderboardGroup.getOverallLeaderboard()); |
|
| 2298 | + } |
|
| 2299 | + mongoObjectFactory.storeLeaderboardGroup(leaderboardGroup); |
|
| 2300 | + } |
|
| 2301 | + |
|
| 2302 | + @Override |
|
| 2303 | + public void removeLeaderboardGroup(String groupName) { |
|
| 2304 | + final LeaderboardGroup leaderboardGroup; |
|
| 2305 | + LockUtil.lockForWrite(leaderboardGroupsByNameLock); |
|
| 2306 | + try { |
|
| 2307 | + leaderboardGroup = leaderboardGroupsByName.remove(groupName); |
|
| 2308 | + if (leaderboardGroup != null) { |
|
| 2309 | + leaderboardGroupsByID.remove(leaderboardGroup.getId()); |
|
| 2310 | + } |
|
| 2311 | + } finally { |
|
| 2312 | + LockUtil.unlockAfterWrite(leaderboardGroupsByNameLock); |
|
| 2313 | + } |
|
| 2314 | + mongoObjectFactory.removeLeaderboardGroup(groupName); |
|
| 2315 | + if (leaderboardGroup != null && leaderboardGroup.getOverallLeaderboard() != null) { |
|
| 2316 | + removeLeaderboard(leaderboardGroup.getOverallLeaderboard().getName()); |
|
| 2317 | + } |
|
| 2318 | + } |
|
| 2319 | + |
|
| 2320 | + @Override |
|
| 2321 | + public void renameLeaderboardGroup(String oldName, String newName) { |
|
| 2322 | + LockUtil.lockForWrite(leaderboardGroupsByNameLock); |
|
| 2323 | + try { |
|
| 2324 | + final LeaderboardGroup toRename = leaderboardGroupsByName.get(oldName); |
|
| 2325 | + if (toRename == null) { |
|
| 2326 | + throw new IllegalArgumentException("No leaderboard group with name " + oldName + " found"); |
|
| 2327 | + } |
|
| 2328 | + if (leaderboardGroupsByName.containsKey(newName)) { |
|
| 2329 | + throw new IllegalArgumentException("Leaderboard group with name " + newName + " already exists"); |
|
| 2330 | + } |
|
| 2331 | + leaderboardGroupsByName.remove(oldName); |
|
| 2332 | + toRename.setName(newName); |
|
| 2333 | + leaderboardGroupsByName.put(newName, toRename); |
|
| 2334 | + } finally { |
|
| 2335 | + LockUtil.unlockAfterWrite(leaderboardGroupsByNameLock); |
|
| 2336 | + } |
|
| 2337 | + mongoObjectFactory.renameLeaderboardGroup(oldName, newName); |
|
| 2338 | + } |
|
| 2339 | + |
|
| 2340 | + @Override |
|
| 2341 | + public void updateLeaderboardGroup(String oldName, String newName, String description, String displayName, |
|
| 2342 | + List<String> leaderboardNames, int[] overallLeaderboardDiscardThresholds, |
|
| 2343 | + ScoringSchemeType overallLeaderboardScoringSchemeType) { |
|
| 2344 | + if (!oldName.equals(newName)) { |
|
| 2345 | + renameLeaderboardGroup(oldName, newName); |
|
| 2346 | + } |
|
| 2347 | + LeaderboardGroup group = getLeaderboardGroupByName(newName); |
|
| 2348 | + if (!description.equals(group.getDescription())) { |
|
| 2349 | + group.setDescriptiom(description); |
|
| 2350 | + } |
|
| 2351 | + if (!Util.equalsWithNull(displayName, group.getDisplayName())) { |
|
| 2352 | + group.setDisplayName(displayName); |
|
| 2353 | + } |
|
| 2354 | + group.clearLeaderboards(); |
|
| 2355 | + for (String leaderboardName : leaderboardNames) { |
|
| 2356 | + Leaderboard leaderboard = getLeaderboardByName(leaderboardName); |
|
| 2357 | + if (leaderboard != null) { |
|
| 2358 | + group.addLeaderboard(leaderboard); |
|
| 2359 | + } |
|
| 2360 | + } |
|
| 2361 | + Leaderboard overallLeaderboard = group.getOverallLeaderboard(); |
|
| 2362 | + if (overallLeaderboard != null) { |
|
| 2363 | + if (overallLeaderboardScoringSchemeType == null) { |
|
| 2364 | + group.setOverallLeaderboard(null); |
|
| 2365 | + removeLeaderboard(overallLeaderboard.getName()); |
|
| 2366 | + } else { |
|
| 2367 | + // update existing overall leaderboard's discards settings; scoring scheme cannot be updated in-place |
|
| 2368 | + overallLeaderboard.setCrossLeaderboardResultDiscardingRule(new ThresholdBasedResultDiscardingRuleImpl( |
|
| 2369 | + overallLeaderboardDiscardThresholds)); |
|
| 2370 | + updateStoredLeaderboard(overallLeaderboard); |
|
| 2371 | + } |
|
| 2372 | + } else if (overallLeaderboard == null && overallLeaderboardScoringSchemeType != null) { |
|
| 2373 | + addOverallLeaderboardToLeaderboardGroup(group, |
|
| 2374 | + getBaseDomainFactory().createScoringScheme(overallLeaderboardScoringSchemeType), |
|
| 2375 | + overallLeaderboardDiscardThresholds); |
|
| 2376 | + } |
|
| 2377 | + updateStoredLeaderboardGroup(group); |
|
| 2378 | + } |
|
| 2379 | + |
|
| 2380 | + private void addOverallLeaderboardToLeaderboardGroup(LeaderboardGroup leaderboardGroup, |
|
| 2381 | + ScoringScheme scoringScheme, int[] discardThresholds) { |
|
| 2382 | + Leaderboard overallLeaderboard = new LeaderboardGroupMetaLeaderboard(leaderboardGroup, scoringScheme, |
|
| 2383 | + new ThresholdBasedResultDiscardingRuleImpl(discardThresholds)); |
|
| 2384 | + leaderboardGroup.setOverallLeaderboard(overallLeaderboard); |
|
| 2385 | + addLeaderboard(overallLeaderboard); |
|
| 2386 | + updateStoredLeaderboard(overallLeaderboard); |
|
| 2387 | + } |
|
| 2388 | + |
|
| 2389 | + @Override |
|
| 2390 | + public void updateStoredLeaderboardGroup(LeaderboardGroup leaderboardGroup) { |
|
| 2391 | + mongoObjectFactory.storeLeaderboardGroup(leaderboardGroup); |
|
| 2392 | + } |
|
| 2393 | + |
|
| 2394 | + private ScheduledExecutorService getScheduler() { |
|
| 2395 | + return scheduler; |
|
| 2396 | + } |
|
| 2397 | + |
|
| 2398 | + @Override |
|
| 2399 | + public ObjectInputStream createObjectInputStreamResolvingAgainstCache(InputStream is) throws IOException { |
|
| 2400 | + return getBaseDomainFactory().createObjectInputStreamResolvingAgainstThisFactory(is); |
|
| 2401 | + } |
|
| 2402 | + |
|
| 2403 | + @Override |
|
| 2404 | + public ClassLoader getDeserializationClassLoader() { |
|
| 2405 | + return joinedClassLoader; |
|
| 2406 | + } |
|
| 2407 | + |
|
| 2408 | + @Override |
|
| 2409 | + public Serializable getId() { |
|
| 2410 | + return getClass().getName(); |
|
| 2411 | + } |
|
| 2412 | + |
|
| 2413 | + @Override |
|
| 2414 | + public Iterable<OperationExecutionListener<RacingEventService>> getOperationExecutionListeners() { |
|
| 2415 | + return operationExecutionListeners.keySet(); |
|
| 2416 | + } |
|
| 2417 | + |
|
| 2418 | + @Override |
|
| 2419 | + public void addOperationExecutionListener(OperationExecutionListener<RacingEventService> listener) { |
|
| 2420 | + operationExecutionListeners.put(listener, listener); |
|
| 2421 | + } |
|
| 2422 | + |
|
| 2423 | + @Override |
|
| 2424 | + public void removeOperationExecutionListener(OperationExecutionListener<RacingEventService> listener) { |
|
| 2425 | + operationExecutionListeners.remove(listener); |
|
| 2426 | + } |
|
| 2427 | + |
|
| 2428 | + @Override |
|
| 2429 | + public void serializeForInitialReplicationInternal(ObjectOutputStream oos) throws IOException { |
|
| 2430 | + StringBuffer logoutput = new StringBuffer(); |
|
| 2431 | + |
|
| 2432 | + logger.info("Serializing regattas..."); |
|
| 2433 | + oos.writeObject(regattasByName); |
|
| 2434 | + logoutput.append("Serialized " + regattasByName.size() + " regattas\n"); |
|
| 2435 | + for (Regatta regatta : regattasByName.values()) { |
|
| 2436 | + logoutput.append(String.format("%3s\n", regatta.toString())); |
|
| 2437 | + } |
|
| 2438 | + |
|
| 2439 | + logger.info("Serializing events..."); |
|
| 2440 | + oos.writeObject(eventsById); |
|
| 2441 | + logoutput.append("\nSerialized " + eventsById.size() + " events\n"); |
|
| 2442 | + for (Event event : eventsById.values()) { |
|
| 2443 | + logoutput.append(String.format("%3s\n", event.toString())); |
|
| 2444 | + } |
|
| 2445 | + |
|
| 2446 | + logger.info("Serializing regattas observed..."); |
|
| 2447 | + oos.writeObject(regattasObservedForDefaultLeaderboard); |
|
| 2448 | + logger.info("Serializing regatta tracking cache..."); |
|
| 2449 | + oos.writeObject(regattaTrackingCache); |
|
| 2450 | + logger.info("Serializing leaderboard groups..."); |
|
| 2451 | + oos.writeObject(leaderboardGroupsByName); |
|
| 2452 | + logoutput.append("Serialized " + leaderboardGroupsByName.size() + " leaderboard groups\n"); |
|
| 2453 | + for (LeaderboardGroup lg : leaderboardGroupsByName.values()) { |
|
| 2454 | + logoutput.append(String.format("%3s\n", lg.toString())); |
|
| 2455 | + } |
|
| 2456 | + logger.info("Serializing leaderboards..."); |
|
| 2457 | + oos.writeObject(leaderboardsByName); |
|
| 2458 | + logoutput.append("Serialized " + leaderboardsByName.size() + " leaderboards\n"); |
|
| 2459 | + for (Leaderboard lg : leaderboardsByName.values()) { |
|
| 2460 | + logoutput.append(String.format("%3s\n", lg.toString())); |
|
| 2461 | + } |
|
| 2462 | + logger.info("Serializing media library..."); |
|
| 2463 | + mediaLibrary.serialize(oos); |
|
| 2464 | + logoutput.append("Serialized " + mediaLibrary.allTracks().size() + " media tracks\n"); |
|
| 2465 | + for (MediaTrack lg : mediaLibrary.allTracks()) { |
|
| 2466 | + logoutput.append(String.format("%3s\n", lg.toString())); |
|
| 2467 | + } |
|
| 2468 | + logger.info("Serializing persisted competitors..."); |
|
| 2469 | + oos.writeObject(competitorStore); |
|
| 2470 | + logoutput.append("Serialized " + competitorStore.size() + " persisted competitors\n"); |
|
| 2471 | + |
|
| 2472 | + logger.info("Serializing configuration map..."); |
|
| 2473 | + oos.writeObject(configurationMap); |
|
| 2474 | + logoutput.append("Serialized " + configurationMap.size() + " configuration entries\n"); |
|
| 2475 | + for (DeviceConfigurationMatcher matcher : configurationMap.keySet()) { |
|
| 2476 | + logoutput.append(String.format("%3s\n", matcher.toString())); |
|
| 2477 | + } |
|
| 2478 | + |
|
| 2479 | + logger.info("Serializing remote sailing server references..."); |
|
| 2480 | + final ArrayList<RemoteSailingServerReference> remoteServerReferences = new ArrayList<>(remoteSailingServerSet |
|
| 2481 | + .getCachedEventsForRemoteSailingServers().keySet()); |
|
| 2482 | + oos.writeObject(remoteServerReferences); |
|
| 2483 | + logoutput.append("Serialized " + remoteServerReferences.size() + " remote sailing server references\n"); |
|
| 2484 | + |
|
| 2485 | + logger.info(logoutput.toString()); |
|
| 2486 | + } |
|
| 2487 | + |
|
| 2488 | + @SuppressWarnings("unchecked") |
|
| 2489 | + // all the casts of ois.readObject()'s return value to Map<..., ...> |
|
| 2490 | + // the type-parameters in the casts of the de-serialized collection objects can't be checked |
|
| 2491 | + @Override |
|
| 2492 | + public void initiallyFillFromInternal(ObjectInputStream ois) throws IOException, ClassNotFoundException, |
|
| 2493 | + InterruptedException { |
|
| 2494 | + logger.info("Performing initial replication load on " + this); |
|
| 2495 | + // Use this object's class's class loader as the context class loader which will then be used for |
|
| 2496 | + // de-serialization; this will cause all classes to be visible that this bundle |
|
| 2497 | + // (com.sap.sailing.server) can see |
|
| 2498 | + StringBuffer logoutput = new StringBuffer(); |
|
| 2499 | + logger.info("Reading all regattas..."); |
|
| 2500 | + regattasByName.putAll((Map<String, Regatta>) ois.readObject()); |
|
| 2501 | + logoutput.append("Received " + regattasByName.size() + " NEW regattas\n"); |
|
| 2502 | + for (Regatta regatta : regattasByName.values()) { |
|
| 2503 | + logoutput.append(String.format("%3s\n", regatta.toString())); |
|
| 2504 | + } |
|
| 2505 | + |
|
| 2506 | + logger.info("Reading all events..."); |
|
| 2507 | + eventsById.putAll((Map<Serializable, Event>) ois.readObject()); |
|
| 2508 | + logoutput.append("\nReceived " + eventsById.size() + " NEW events\n"); |
|
| 2509 | + for (Event event : eventsById.values()) { |
|
| 2510 | + logoutput.append(String.format("%3s\n", event.toString())); |
|
| 2511 | + } |
|
| 2512 | + |
|
| 2513 | + // it is important that the leaderboards and tracked regattas are cleared before auto-linking to |
|
| 2514 | + // old leaderboards takes place which then don't match the new ones |
|
| 2515 | + logger.info("Reading all dynamic tracked regattas..."); |
|
| 2516 | + for (DynamicTrackedRegatta trackedRegattaToObserve : (Set<DynamicTrackedRegatta>) ois.readObject()) { |
|
| 2517 | + ensureRegattaIsObservedForDefaultLeaderboardAndAutoLeaderboardLinking(trackedRegattaToObserve); |
|
| 2518 | + } |
|
| 2519 | + |
|
| 2520 | + logger.info("Reading all of the regatta tracking cache..."); |
|
| 2521 | + regattaTrackingCache.putAll((Map<Regatta, DynamicTrackedRegatta>) ois.readObject()); |
|
| 2522 | + logoutput.append("Received " + regattaTrackingCache.size() + " NEW regatta tracking cache entries\n"); |
|
| 2523 | + |
|
| 2524 | + logger.info("Reading leaderboard groups..."); |
|
| 2525 | + leaderboardGroupsByName.putAll((Map<String, LeaderboardGroup>) ois.readObject()); |
|
| 2526 | + logoutput.append("Received " + leaderboardGroupsByName.size() + " NEW leaderboard groups\n"); |
|
| 2527 | + for (LeaderboardGroup lg : leaderboardGroupsByName.values()) { |
|
| 2528 | + leaderboardGroupsByID.put(lg.getId(), lg); |
|
| 2529 | + logoutput.append(String.format("%3s\n", lg.toString())); |
|
| 2530 | + } |
|
| 2531 | + |
|
| 2532 | + logger.info("Reading leaderboards by name..."); |
|
| 2533 | + leaderboardsByName.putAll((Map<String, Leaderboard>) ois.readObject()); |
|
| 2534 | + logoutput.append("Received " + leaderboardsByName.size() + " NEW leaderboards\n"); |
|
| 2535 | + for (Leaderboard leaderboard : leaderboardsByName.values()) { |
|
| 2536 | + logoutput.append(String.format("%3s\n", leaderboard.toString())); |
|
| 2537 | + } |
|
| 2538 | + |
|
| 2539 | + // now fix ScoreCorrectionListener setup for LeaderboardGroupMetaLeaderboard instances: |
|
| 2540 | + for (Leaderboard leaderboard : leaderboardsByName.values()) { |
|
| 2541 | + if (leaderboard instanceof LeaderboardGroupMetaLeaderboard) { |
|
| 2542 | + ((LeaderboardGroupMetaLeaderboard) leaderboard) |
|
| 2543 | + .registerAsScoreCorrectionChangeForwarderAndRaceColumnListenerOnAllLeaderboards(); |
|
| 2544 | + } else if (leaderboard instanceof FlexibleLeaderboard) { |
|
| 2545 | + // and re-establish the RaceLogReplicator as listener on FlexibleLeaderboard objects |
|
| 2546 | + leaderboard.addRaceColumnListener(raceLogReplicator); |
|
| 2547 | + } |
|
| 2548 | + } |
|
| 2549 | + |
|
| 2550 | + logger.info("Reading media library..."); |
|
| 2551 | + mediaLibrary.deserialize(ois); |
|
| 2552 | + logoutput.append("Received " + mediaLibrary.allTracks().size() + " NEW media tracks\n"); |
|
| 2553 | + for (MediaTrack mediatrack : mediaLibrary.allTracks()) { |
|
| 2554 | + logoutput.append(String.format("%3s\n", mediatrack.toString())); |
|
| 2555 | + } |
|
| 2556 | + |
|
| 2557 | + // only copy the competitors from the deserialized competitor store; don't use it because it will have set |
|
| 2558 | + // a default Mongo object factory |
|
| 2559 | + logger.info("Reading competitors..."); |
|
| 2560 | + for (Competitor competitor : ((CompetitorStore) ois.readObject()).getCompetitors()) { |
|
| 2561 | + DynamicCompetitor dynamicCompetitor = (DynamicCompetitor) competitor; |
|
| 2562 | + // the following should actually be redundant because during de-serialization the Competitor objects, |
|
| 2563 | + // whose classes implement IsManagedByCache, should already have been got/created from/in the |
|
| 2564 | + // competitor store |
|
| 2565 | + competitorStore.getOrCreateCompetitor(dynamicCompetitor.getId(), dynamicCompetitor.getName(), |
|
| 2566 | + dynamicCompetitor.getColor(), dynamicCompetitor.getEmail(), dynamicCompetitor.getFlagImage(), |
|
| 2567 | + dynamicCompetitor.getTeam(), dynamicCompetitor.getBoat(), dynamicCompetitor.getTimeOnTimeFactor(), |
|
| 2568 | + dynamicCompetitor.getTimeOnDistanceAllowancePerNauticalMile()); |
|
| 2569 | + } |
|
| 2570 | + logoutput.append("Received " + competitorStore.size() + " NEW competitors\n"); |
|
| 2571 | + |
|
| 2572 | + logger.info("Reading device configurations..."); |
|
| 2573 | + configurationMap.putAll((DeviceConfigurationMapImpl) ois.readObject()); |
|
| 2574 | + logoutput.append("Received " + configurationMap.size() + " NEW configuration entries\n"); |
|
| 2575 | + for (DeviceConfigurationMatcher matcher : configurationMap.keySet()) { |
|
| 2576 | + logoutput.append(String.format("%3s\n", matcher.toString())); |
|
| 2577 | + } |
|
| 2578 | + |
|
| 2579 | + logger.info("Reading remote sailing server references..."); |
|
| 2580 | + for (RemoteSailingServerReference remoteSailingServerReference : (Iterable<RemoteSailingServerReference>) ois |
|
| 2581 | + .readObject()) { |
|
| 2582 | + remoteSailingServerSet.add(remoteSailingServerReference); |
|
| 2583 | + logoutput.append("Received remote sailing server reference " + remoteSailingServerReference); |
|
| 2584 | + } |
|
| 2585 | + |
|
| 2586 | + // make sure to initialize listeners correctly |
|
| 2587 | + for (Regatta regatta : regattasByName.values()) { |
|
| 2588 | + RegattaImpl regattaImpl = (RegattaImpl) regatta; |
|
| 2589 | + regattaImpl.initializeSeriesAfterDeserialize(); |
|
| 2590 | + regattaImpl.addRaceColumnListener(raceLogReplicator); |
|
| 2591 | + } |
|
| 2592 | + // re-establish RaceLogResolver references to this RacingEventService in all TrackedRace instances |
|
| 2593 | + for (DynamicTrackedRegatta trackedRegatta : regattaTrackingCache.values()) { |
|
| 2594 | + trackedRegatta.lockTrackedRacesForRead(); |
|
| 2595 | + try { |
|
| 2596 | + for (TrackedRace trackedRace : trackedRegatta.getTrackedRaces()) { |
|
| 2597 | + ((TrackedRaceImpl) trackedRace).setRaceLogResolver(this); |
|
| 2598 | + } |
|
| 2599 | + } finally { |
|
| 2600 | + trackedRegatta.unlockTrackedRacesAfterRead(); |
|
| 2601 | + } |
|
| 2602 | + } |
|
| 2603 | + logger.info(logoutput.toString()); |
|
| 2604 | + } |
|
| 2605 | + |
|
| 2606 | + @Override |
|
| 2607 | + public void clearReplicaState() throws MalformedURLException, IOException, InterruptedException { |
|
| 2608 | + logger.info("Clearing all data structures..."); |
|
| 2609 | + LockUtil.lockForWrite(regattasByNameLock); |
|
| 2610 | + try { |
|
| 2611 | + regattasByName.clear(); |
|
| 2612 | + } finally { |
|
| 2613 | + LockUtil.unlockAfterWrite(regattasByNameLock); |
|
| 2614 | + } |
|
| 2615 | + regattasObservedForDefaultLeaderboard.clear(); |
|
| 2616 | + |
|
| 2617 | + if (raceTrackersByRegatta != null && !raceTrackersByRegatta.isEmpty()) { |
|
| 2618 | + for (DynamicTrackedRegatta regatta : regattaTrackingCache.values()) { |
|
| 2619 | + final Set<RaceTracker> trackers = raceTrackersByRegatta.get(regatta.getRegatta()); |
|
| 2620 | + if (trackers != null) { |
|
| 2621 | + for (RaceTracker tracker : trackers) { |
|
| 2622 | + tracker.stop(/* preemptive */true); |
|
| 2623 | + } |
|
| 2624 | + } |
|
| 2625 | + } |
|
| 2626 | + } |
|
| 2627 | + LockUtil.lockForWrite(regattaTrackingCacheLock); |
|
| 2628 | + try { |
|
| 2629 | + regattaTrackingCache.clear(); |
|
| 2630 | + } finally { |
|
| 2631 | + LockUtil.unlockAfterWrite(regattaTrackingCacheLock); |
|
| 2632 | + } |
|
| 2633 | + LockUtil.lockForWrite(raceTrackersByRegattaLock); |
|
| 2634 | + try { |
|
| 2635 | + raceTrackersByRegatta.clear(); |
|
| 2636 | + } finally { |
|
| 2637 | + LockUtil.unlockAfterWrite(raceTrackersByRegattaLock); |
|
| 2638 | + } |
|
| 2639 | + LockUtil.lockForWrite(leaderboardGroupsByNameLock); |
|
| 2640 | + try { |
|
| 2641 | + leaderboardGroupsByName.clear(); |
|
| 2642 | + leaderboardGroupsByID.clear(); |
|
| 2643 | + } finally { |
|
| 2644 | + LockUtil.unlockAfterWrite(leaderboardGroupsByNameLock); |
|
| 2645 | + } |
|
| 2646 | + LockUtil.lockForWrite(leaderboardsByNameLock); |
|
| 2647 | + try { |
|
| 2648 | + leaderboardsByName.clear(); |
|
| 2649 | + } finally { |
|
| 2650 | + LockUtil.unlockAfterWrite(leaderboardsByNameLock); |
|
| 2651 | + } |
|
| 2652 | + eventsById.clear(); |
|
| 2653 | + mediaLibrary.clear(); |
|
| 2654 | + competitorStore.clear(); |
|
| 2655 | + remoteSailingServerSet.clear(); |
|
| 2656 | + } |
|
| 2657 | + |
|
| 2658 | + // Used for TESTING only |
|
| 2659 | + @Override |
|
| 2660 | + public Event addEvent(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, |
|
| 2661 | + String venue, boolean isPublic, UUID id) { |
|
| 2662 | + Event result = createEventWithoutReplication(eventName, eventDescription, startDate, endDate, venue, isPublic, |
|
| 2663 | + id, /* officialWebsiteURL */null, /* sailorsInfoWebsiteURLAsString */null, |
|
| 2664 | + /* images */Collections.<ImageDescriptor> emptyList(), /* videos */Collections.<VideoDescriptor> emptyList()); |
|
| 2665 | + replicate(new CreateEvent(eventName, eventDescription, startDate, endDate, venue, isPublic, id, |
|
| 2666 | + /* officialWebsiteURLAsString */null, /* sailorsInfoWebsiteURLAsString */null, |
|
| 2667 | + /* images */Collections.<ImageDescriptor> emptyList(), /* videos */Collections.<VideoDescriptor> emptyList())); |
|
| 2668 | + return result; |
|
| 2669 | + } |
|
| 2670 | + |
|
| 2671 | + @Override |
|
| 2672 | + public void addEventWithoutReplication(Event event) { |
|
| 2673 | + addEvent(event); |
|
| 2674 | + } |
|
| 2675 | + |
|
| 2676 | + @Override |
|
| 2677 | + public Event createEventWithoutReplication(String eventName, String eventDescription, TimePoint startDate, |
|
| 2678 | + TimePoint endDate, String venue, boolean isPublic, UUID id, URL officialWebsiteURL, URL sailorsInfoWebsiteURL, |
|
| 2679 | + Iterable<ImageDescriptor> images, Iterable<VideoDescriptor> videos) { |
|
| 2680 | + Event result = new EventImpl(eventName, startDate, endDate, venue, isPublic, id); |
|
| 2681 | + addEvent(result); |
|
| 2682 | + result.setDescription(eventDescription); |
|
| 2683 | + result.setOfficialWebsiteURL(officialWebsiteURL); |
|
| 2684 | + result.setImages(images); |
|
| 2685 | + result.setVideos(videos); |
|
| 2686 | + return result; |
|
| 2687 | + } |
|
| 2688 | + |
|
| 2689 | + private void addEvent(Event result) { |
|
| 2690 | + if (eventsById.containsKey(result.getId())) { |
|
| 2691 | + throw new IllegalArgumentException("Event with ID " + result.getId() |
|
| 2692 | + + " already exists which is pretty surprising..."); |
|
| 2693 | + } |
|
| 2694 | + eventsById.put(result.getId(), result); |
|
| 2695 | + mongoObjectFactory.storeEvent(result); |
|
| 2696 | + } |
|
| 2697 | + |
|
| 2698 | + @Override |
|
| 2699 | + public void updateEvent(UUID id, String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, |
|
| 2700 | + String venueName, boolean isPublic, Iterable<UUID> leaderboardGroupIds, URL officialWebsiteURL, URL sailorsInfoWebsiteURL, |
|
| 2701 | + Iterable<ImageDescriptor> images, Iterable<VideoDescriptor> videos) { |
|
| 2702 | + final Event event = eventsById.get(id); |
|
| 2703 | + if (event == null) { |
|
| 2704 | + throw new IllegalArgumentException("Sailing event with ID " + id + " does not exist."); |
|
| 2705 | + } |
|
| 2706 | + event.setName(eventName); |
|
| 2707 | + event.setDescription(eventDescription); |
|
| 2708 | + event.setStartDate(startDate); |
|
| 2709 | + event.setEndDate(endDate); |
|
| 2710 | + event.setPublic(isPublic); |
|
| 2711 | + event.getVenue().setName(venueName); |
|
| 2712 | + List<LeaderboardGroup> leaderboardGroups = new ArrayList<>(); |
|
| 2713 | + for (UUID lgid : leaderboardGroupIds) { |
|
| 2714 | + LeaderboardGroup lg = getLeaderboardGroupByID(lgid); |
|
| 2715 | + if (lg != null) { |
|
| 2716 | + leaderboardGroups.add(lg); |
|
| 2717 | + } else { |
|
| 2718 | + logger.info("Couldn't find leaderboard group with ID " + lgid + " while updating event " |
|
| 2719 | + + event.getName()); |
|
| 2720 | + } |
|
| 2721 | + } |
|
| 2722 | + event.setLeaderboardGroups(leaderboardGroups); |
|
| 2723 | + event.setOfficialWebsiteURL(officialWebsiteURL); |
|
| 2724 | + event.setSailorsInfoWebsiteURL(sailorsInfoWebsiteURL); |
|
| 2725 | + event.setImages(images); |
|
| 2726 | + event.setVideos(videos); |
|
| 2727 | + // TODO consider use diffutils to compute diff between old and new leaderboard groups list and apply the patch |
|
| 2728 | + // to keep changes minimial |
|
| 2729 | + mongoObjectFactory.storeEvent(event); |
|
| 2730 | + } |
|
| 2731 | + |
|
| 2732 | + @Override |
|
| 2733 | + public void renameEvent(UUID id, String newName) { |
|
| 2734 | + final Event toRename = eventsById.get(id); |
|
| 2735 | + if (toRename == null) { |
|
| 2736 | + throw new IllegalArgumentException("No sailing event with ID " + id + " found."); |
|
| 2737 | + } |
|
| 2738 | + toRename.setName(newName); |
|
| 2739 | + mongoObjectFactory.renameEvent(id, newName); |
|
| 2740 | + replicate(new RenameEvent(id, newName)); |
|
| 2741 | + } |
|
| 2742 | + |
|
| 2743 | + @Override |
|
| 2744 | + public void removeEvent(UUID id) { |
|
| 2745 | + removeEventFromEventsById(id); |
|
| 2746 | + mongoObjectFactory.removeEvent(id); |
|
| 2747 | + replicate(new RemoveEvent(id)); |
|
| 2748 | + } |
|
| 2749 | + |
|
| 2750 | + protected void removeEventFromEventsById(Serializable id) { |
|
| 2751 | + eventsById.remove(id); |
|
| 2752 | + } |
|
| 2753 | + |
|
| 2754 | + @Override |
|
| 2755 | + public Regatta getRememberedRegattaForRace(Serializable raceID) { |
|
| 2756 | + return persistentRegattasForRaceIDs.get(raceID.toString()); |
|
| 2757 | + } |
|
| 2758 | + |
|
| 2759 | + /** |
|
| 2760 | + * Persistently remembers the association of the race with its {@link RaceDefinition#getId()} to the |
|
| 2761 | + * <code>regatta</code> with its {@link Regatta#getRegattaIdentifier() identifier} so that the next time |
|
| 2762 | + * {@link #getRememberedRegattaForRace(RaceDefinition)} is called with <code>race</code> as argument, |
|
| 2763 | + * <code>regatta</code> will be returned. |
|
| 2764 | + */ |
|
| 2765 | + private void setRegattaForRace(Regatta regatta, RaceDefinition race) { |
|
| 2766 | + setRegattaForRace(regatta, race.getId().toString()); |
|
| 2767 | + } |
|
| 2768 | + |
|
| 2769 | + @Override |
|
| 2770 | + public void setRegattaForRace(Regatta regatta, String raceIdAsString) { |
|
| 2771 | + persistentRegattasForRaceIDs.put(raceIdAsString, regatta); |
|
| 2772 | + mongoObjectFactory.storeRegattaForRaceID(raceIdAsString, regatta); |
|
| 2773 | + } |
|
| 2774 | + |
|
| 2775 | + @Override |
|
| 2776 | + public CourseArea[] addCourseAreas(UUID eventId, String[] courseAreaNames, UUID[] courseAreaIds) { |
|
| 2777 | + final CourseArea[] courseAreas = addCourseAreasWithoutReplication(eventId, courseAreaIds, courseAreaNames); |
|
| 2778 | + replicate(new AddCourseAreas(eventId, courseAreaNames, courseAreaIds)); |
|
| 2779 | + return courseAreas; |
|
| 2780 | + } |
|
| 2781 | + |
|
| 2782 | + @Override |
|
| 2783 | + public CourseArea[] addCourseAreasWithoutReplication(UUID eventId, UUID[] courseAreaIds, String[] courseAreaNames) { |
|
| 2784 | + final CourseArea[] result = new CourseArea[courseAreaNames.length]; |
|
| 2785 | + for (int i=0; i<courseAreaIds.length; i++) { |
|
| 2786 | + final CourseArea courseArea = getBaseDomainFactory().getOrCreateCourseArea(courseAreaIds[i], courseAreaNames[i]); |
|
| 2787 | + final Event event = eventsById.get(eventId); |
|
| 2788 | + if (event == null) { |
|
| 2789 | + throw new IllegalArgumentException("No sailing event with ID " + eventId + " found."); |
|
| 2790 | + } |
|
| 2791 | + event.getVenue().addCourseArea(courseArea); |
|
| 2792 | + mongoObjectFactory.storeEvent(event); |
|
| 2793 | + result[i] = courseArea; |
|
| 2794 | + } |
|
| 2795 | + return result; |
|
| 2796 | + } |
|
| 2797 | + |
|
| 2798 | + @Override |
|
| 2799 | + public CourseArea[] removeCourseAreaWithoutReplication(UUID eventId, UUID[] courseAreaIds) { |
|
| 2800 | + final Event event = eventsById.get(eventId); |
|
| 2801 | + if (event == null) { |
|
| 2802 | + throw new IllegalArgumentException("No sailing event with ID " + eventId + " found."); |
|
| 2803 | + } |
|
| 2804 | + final CourseArea[] courseAreasRemoved = new CourseArea[courseAreaIds.length]; |
|
| 2805 | + int i=0; |
|
| 2806 | + for (final UUID courseAreaId : courseAreaIds) { |
|
| 2807 | + final CourseArea courseArea = getBaseDomainFactory().getExistingCourseAreaById(courseAreaId); |
|
| 2808 | + if (courseArea == null) { |
|
| 2809 | + throw new IllegalArgumentException("No course area with ID " + courseAreaId + " found."); |
|
| 2810 | + } |
|
| 2811 | + courseAreasRemoved[i++] = courseArea; |
|
| 2812 | + event.getVenue().removeCourseArea(courseArea); |
|
| 2813 | + mongoObjectFactory.storeEvent(event); |
|
| 2814 | + } |
|
| 2815 | + return courseAreasRemoved; |
|
| 2816 | + } |
|
| 2817 | + |
|
| 2818 | + @Override |
|
| 2819 | + public void mediaTrackAdded(MediaTrack mediaTrack) { |
|
| 2820 | + if (mediaTrack.dbId == null) { |
|
| 2821 | + mediaTrack.dbId = mediaDB.insertMediaTrack(mediaTrack.title, mediaTrack.url, mediaTrack.startTime, |
|
| 2822 | + mediaTrack.duration, mediaTrack.mimeType, mediaTrack.assignedRaces); |
|
| 2823 | + } |
|
| 2824 | + mediaLibrary.addMediaTrack(mediaTrack); |
|
| 2825 | + replicate(new AddMediaTrackOperation(mediaTrack)); |
|
| 2826 | + } |
|
| 2827 | + |
|
| 2828 | + @Override |
|
| 2829 | + public void mediaTracksAdded(Collection<MediaTrack> mediaTracks) { |
|
| 2830 | + mediaLibrary.addMediaTracks(mediaTracks); |
|
| 2831 | + } |
|
| 2832 | + |
|
| 2833 | + @Override |
|
| 2834 | + public void mediaTrackTitleChanged(MediaTrack mediaTrack) { |
|
| 2835 | + mediaDB.updateTitle(mediaTrack.dbId, mediaTrack.title); |
|
| 2836 | + mediaLibrary.titleChanged(mediaTrack); |
|
| 2837 | + replicate(new UpdateMediaTrackTitleOperation(mediaTrack)); |
|
| 2838 | + } |
|
| 2839 | + |
|
| 2840 | + @Override |
|
| 2841 | + public void mediaTrackUrlChanged(MediaTrack mediaTrack) { |
|
| 2842 | + mediaDB.updateUrl(mediaTrack.dbId, mediaTrack.url); |
|
| 2843 | + mediaLibrary.urlChanged(mediaTrack); |
|
| 2844 | + replicate(new UpdateMediaTrackUrlOperation(mediaTrack)); |
|
| 2845 | + } |
|
| 2846 | + |
|
| 2847 | + @Override |
|
| 2848 | + public void mediaTrackStartTimeChanged(MediaTrack mediaTrack) { |
|
| 2849 | + mediaDB.updateStartTime(mediaTrack.dbId, mediaTrack.startTime); |
|
| 2850 | + mediaLibrary.startTimeChanged(mediaTrack); |
|
| 2851 | + replicate(new UpdateMediaTrackStartTimeOperation(mediaTrack)); |
|
| 2852 | + } |
|
| 2853 | + |
|
| 2854 | + @Override |
|
| 2855 | + public void mediaTrackDurationChanged(MediaTrack mediaTrack) { |
|
| 2856 | + mediaDB.updateDuration(mediaTrack.dbId, mediaTrack.duration); |
|
| 2857 | + mediaLibrary.durationChanged(mediaTrack); |
|
| 2858 | + replicate(new UpdateMediaTrackDurationOperation(mediaTrack)); |
|
| 2859 | + } |
|
| 2860 | + |
|
| 2861 | + @Override |
|
| 2862 | + public void mediaTrackAssignedRacesChanged(MediaTrack mediaTrack) { |
|
| 2863 | + mediaDB.updateRace(mediaTrack.dbId, mediaTrack.assignedRaces); |
|
| 2864 | + mediaLibrary.assignedRacesChanged(mediaTrack); |
|
| 2865 | + replicate(new UpdateMediaTrackRacesOperation(mediaTrack)); |
|
| 2866 | + |
|
| 2867 | + } |
|
| 2868 | + |
|
| 2869 | + @Override |
|
| 2870 | + public void mediaTrackDeleted(MediaTrack mediaTrack) { |
|
| 2871 | + mediaDB.deleteMediaTrack(mediaTrack.dbId); |
|
| 2872 | + mediaLibrary.deleteMediaTrack(mediaTrack); |
|
| 2873 | + replicate(new RemoveMediaTrackOperation(mediaTrack)); |
|
| 2874 | + } |
|
| 2875 | + |
|
| 2876 | + @Override |
|
| 2877 | + public void mediaTracksImported(Collection<MediaTrack> mediaTracksToImport, boolean override) { |
|
| 2878 | + for (MediaTrack trackToImport : mediaTracksToImport) { |
|
| 2879 | + MediaTrack existingTrack = mediaLibrary.lookupMediaTrack(trackToImport); |
|
| 2880 | + if (existingTrack == null) { |
|
| 2881 | + mediaDB.insertMediaTrackWithId(trackToImport.dbId, trackToImport.title, trackToImport.url, |
|
| 2882 | + trackToImport.startTime, trackToImport.duration, trackToImport.mimeType, |
|
| 2883 | + trackToImport.assignedRaces); |
|
| 2884 | + mediaTrackAdded(trackToImport); |
|
| 2885 | + } else if (override) { |
|
| 2886 | + |
|
| 2887 | + // Using fine-grained update methods. |
|
| 2888 | + // Rationale: Changes on more than one track property are rare |
|
| 2889 | + // and don't justify the introduction of a new set |
|
| 2890 | + // of methods (including replication). |
|
| 2891 | + if (!Util.equalsWithNull(existingTrack.title, trackToImport.title)) { |
|
| 2892 | + mediaTrackTitleChanged(trackToImport); |
|
| 2893 | + } |
|
| 2894 | + if (!Util.equalsWithNull(existingTrack.url, trackToImport.url)) { |
|
| 2895 | + mediaTrackUrlChanged(trackToImport); |
|
| 2896 | + } |
|
| 2897 | + if (!Util.equalsWithNull(existingTrack.startTime, trackToImport.startTime)) { |
|
| 2898 | + mediaTrackStartTimeChanged(trackToImport); |
|
| 2899 | + } |
|
| 2900 | + if (!Util.equalsWithNull(existingTrack.duration, trackToImport.duration)) { |
|
| 2901 | + mediaTrackDurationChanged(trackToImport); |
|
| 2902 | + } |
|
| 2903 | + if (!Util.equalsWithNull(existingTrack.assignedRaces, trackToImport.assignedRaces)) { |
|
| 2904 | + mediaTrackAssignedRacesChanged(trackToImport); |
|
| 2905 | + } |
|
| 2906 | + } |
|
| 2907 | + } |
|
| 2908 | + } |
|
| 2909 | + |
|
| 2910 | + @Override |
|
| 2911 | + public Collection<MediaTrack> getMediaTracksForRace(RegattaAndRaceIdentifier regattaAndRaceIdentifier) { |
|
| 2912 | + return mediaLibrary.findMediaTracksForRace(regattaAndRaceIdentifier); |
|
| 2913 | + } |
|
| 2914 | + |
|
| 2915 | + @Override |
|
| 2916 | + public Collection<MediaTrack> getMediaTracksInTimeRange(RegattaAndRaceIdentifier regattaAndRaceIdentifier) { |
|
| 2917 | + TrackedRace trackedRace = getExistingTrackedRace(regattaAndRaceIdentifier); |
|
| 2918 | + if (trackedRace != null) { |
|
| 2919 | + if (trackedRace.isLive(MillisecondsTimePoint.now())) { |
|
| 2920 | + return mediaLibrary.findLiveMediaTracks(); |
|
| 2921 | + } else { |
|
| 2922 | + TimePoint raceStart = trackedRace.getStartOfRace() == null ? trackedRace.getStartOfTracking() |
|
| 2923 | + : trackedRace.getStartOfRace(); |
|
| 2924 | + TimePoint raceEnd = trackedRace.getEndOfRace() == null ? trackedRace.getEndOfTracking() : trackedRace |
|
| 2925 | + .getEndOfRace(); |
|
| 2926 | + return mediaLibrary.findMediaTracksInTimeRange(raceStart, raceEnd); |
|
| 2927 | + } |
|
| 2928 | + } else { |
|
| 2929 | + return Collections.emptyList(); |
|
| 2930 | + } |
|
| 2931 | + } |
|
| 2932 | + |
|
| 2933 | + @Override |
|
| 2934 | + public Collection<MediaTrack> getAllMediaTracks() { |
|
| 2935 | + return mediaLibrary.allTracks(); |
|
| 2936 | + } |
|
| 2937 | + |
|
| 2938 | + public String toString() { |
|
| 2939 | + return "RacingEventService: " + this.hashCode() + " Build: " + ServerInfo.getBuildVersion(); |
|
| 2940 | + } |
|
| 2941 | + |
|
| 2942 | + @Override |
|
| 2943 | + public void reloadRaceLog(String leaderboardName, String raceColumnName, String fleetName) { |
|
| 2944 | + Leaderboard leaderboard = getLeaderboardByName(leaderboardName); |
|
| 2945 | + if (leaderboard != null) { |
|
| 2946 | + RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); |
|
| 2947 | + if (raceColumn != null) { |
|
| 2948 | + Fleet fleetImpl = raceColumn.getFleetByName(fleetName); |
|
| 2949 | + RaceLog racelog = raceColumn.getRaceLog(fleetImpl); |
|
| 2950 | + if (racelog != null) { |
|
| 2951 | + raceColumn.reloadRaceLog(fleetImpl); |
|
| 2952 | + logger.info("Reloaded race log for fleet " + fleetImpl + " for race column " + raceColumn.getName() |
|
| 2953 | + + " for leaderboard " + leaderboard.getName()); |
|
| 2954 | + } |
|
| 2955 | + } |
|
| 2956 | + } |
|
| 2957 | + } |
|
| 2958 | + |
|
| 2959 | + @Override |
|
| 2960 | + public ConcurrentHashMap<String, Regatta> getPersistentRegattasForRaceIDs() { |
|
| 2961 | + return persistentRegattasForRaceIDs; |
|
| 2962 | + } |
|
| 2963 | + |
|
| 2964 | + @Override |
|
| 2965 | + public WindStore getWindStore() { |
|
| 2966 | + return windStore; |
|
| 2967 | + } |
|
| 2968 | + |
|
| 2969 | + @Override |
|
| 2970 | + public DeviceConfiguration getDeviceConfiguration(DeviceConfigurationIdentifier identifier) { |
|
| 2971 | + return configurationMap.getByMatch(identifier); |
|
| 2972 | + } |
|
| 2973 | + |
|
| 2974 | + @Override |
|
| 2975 | + public void createOrUpdateDeviceConfiguration(DeviceConfigurationMatcher matcher, DeviceConfiguration configuration) { |
|
| 2976 | + configurationMap.put(matcher, configuration); |
|
| 2977 | + mongoObjectFactory.storeDeviceConfiguration(matcher, configuration); |
|
| 2978 | + replicate(new CreateOrUpdateDeviceConfiguration(matcher, configuration)); |
|
| 2979 | + } |
|
| 2980 | + |
|
| 2981 | + @Override |
|
| 2982 | + public void removeDeviceConfiguration(DeviceConfigurationMatcher matcher) { |
|
| 2983 | + configurationMap.remove(matcher); |
|
| 2984 | + mongoObjectFactory.removeDeviceConfiguration(matcher); |
|
| 2985 | + replicate(new RemoveDeviceConfiguration(matcher)); |
|
| 2986 | + } |
|
| 2987 | + |
|
| 2988 | + @Override |
|
| 2989 | + public Map<DeviceConfigurationMatcher, DeviceConfiguration> getAllDeviceConfigurations() { |
|
| 2990 | + return new HashMap<DeviceConfigurationMatcher, DeviceConfiguration>(configurationMap); |
|
| 2991 | + } |
|
| 2992 | + |
|
| 2993 | + @Override |
|
| 2994 | + public TimePoint setStartTimeAndProcedure(String leaderboardName, String raceColumnName, String fleetName, |
|
| 2995 | + String authorName, int authorPriority, int passId, TimePoint logicalTimePoint, TimePoint startTime, |
|
| 2996 | + RacingProcedureType racingProcedure) { |
|
| 2997 | + RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName); |
|
| 2998 | + Leaderboard leaderboard = getLeaderboardByName(leaderboardName); |
|
| 2999 | + final TimePoint result; |
|
| 3000 | + if (leaderboard instanceof HasRegattaLike && raceLog != null) { |
|
| 3001 | + RaceState state = RaceStateImpl.create(/* race log resolver */ this, raceLog, new LogEventAuthorImpl(authorName, authorPriority)); |
|
| 3002 | + if (passId > raceLog.getCurrentPassId()) { |
|
| 3003 | + state.setAdvancePass(logicalTimePoint); |
|
| 3004 | + } |
|
| 3005 | + state.setRacingProcedure(logicalTimePoint, racingProcedure); |
|
| 3006 | + state.forceNewStartTime(logicalTimePoint, startTime); |
|
| 3007 | + result = state.getStartTime(); |
|
| 3008 | + } else { |
|
| 3009 | + result = null; |
|
| 3010 | + } |
|
| 3011 | + return result; |
|
| 3012 | + } |
|
| 3013 | + |
|
| 3014 | + public RaceLog getRaceLog(String leaderboardName, String raceColumnName, String fleetName) { |
|
| 3015 | + Leaderboard leaderboard = getLeaderboardByName(leaderboardName); |
|
| 3016 | + if (leaderboard != null) { |
|
| 3017 | + RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); |
|
| 3018 | + if (raceColumn != null) { |
|
| 3019 | + Fleet fleetImpl = raceColumn.getFleetByName(fleetName); |
|
| 3020 | + return raceColumn.getRaceLog(fleetImpl); |
|
| 3021 | + } |
|
| 3022 | + } |
|
| 3023 | + return null; |
|
| 3024 | + } |
|
| 3025 | + |
|
| 3026 | + @Override |
|
| 3027 | + public com.sap.sse.common.Util.Triple<TimePoint, Integer, RacingProcedureType> getStartTimeAndProcedure( |
|
| 3028 | + String leaderboardName, String raceColumnName, String fleetName) { |
|
| 3029 | + RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName); |
|
| 3030 | + Leaderboard leaderboard = getLeaderboardByName(leaderboardName); |
|
| 3031 | + final Triple<TimePoint, Integer, RacingProcedureType> result; |
|
| 3032 | + if (leaderboard instanceof HasRegattaLike && raceLog != null) { |
|
| 3033 | + ReadonlyRaceState state = ReadonlyRaceStateImpl.create(/* race log resolver */ this, raceLog); |
|
| 3034 | + result = new com.sap.sse.common.Util.Triple<TimePoint, Integer, RacingProcedureType>(state.getStartTime(), |
|
| 3035 | + raceLog.getCurrentPassId(), state.getRacingProcedure().getType()); |
|
| 3036 | + } else { |
|
| 3037 | + result = null; |
|
| 3038 | + } |
|
| 3039 | + return result; |
|
| 3040 | + } |
|
| 3041 | + |
|
| 3042 | + private Iterable<WindTrackerFactory> getWindTrackerFactories() { |
|
| 3043 | + final Set<WindTrackerFactory> result; |
|
| 3044 | + if (bundleContext == null) { |
|
| 3045 | + result = Collections.singleton((WindTrackerFactory) ExpeditionWindTrackerFactory.getInstance()); |
|
| 3046 | + } else { |
|
| 3047 | + ServiceTracker<WindTrackerFactory, WindTrackerFactory> tracker = new ServiceTracker<WindTrackerFactory, WindTrackerFactory>( |
|
| 3048 | + bundleContext, WindTrackerFactory.class.getName(), null); |
|
| 3049 | + tracker.open(); |
|
| 3050 | + result = new HashSet<>(); |
|
| 3051 | + for (WindTrackerFactory factory : tracker.getServices(new WindTrackerFactory[0])) { |
|
| 3052 | + result.add(factory); |
|
| 3053 | + } |
|
| 3054 | + } |
|
| 3055 | + return result; |
|
| 3056 | + } |
|
| 3057 | + |
|
| 3058 | + @Override |
|
| 3059 | + public GPSFixStore getGPSFixStore() { |
|
| 3060 | + return gpsFixStore; |
|
| 3061 | + } |
|
| 3062 | + |
|
| 3063 | + @Override |
|
| 3064 | + public RaceTracker getRaceTrackerById(Object id) { |
|
| 3065 | + return raceTrackersByID.get(id); |
|
| 3066 | + } |
|
| 3067 | + |
|
| 3068 | + @Override |
|
| 3069 | + public AbstractLogEventAuthor getServerAuthor() { |
|
| 3070 | + return raceLogEventAuthorForServer; |
|
| 3071 | + } |
|
| 3072 | + |
|
| 3073 | + @Override |
|
| 3074 | + public CompetitorStore getCompetitorStore() { |
|
| 3075 | + return competitorStore; |
|
| 3076 | + } |
|
| 3077 | + |
|
| 3078 | + @Override |
|
| 3079 | + public TypeBasedServiceFinderFactory getTypeBasedServiceFinderFactory() { |
|
| 3080 | + return serviceFinderFactory; |
|
| 3081 | + } |
|
| 3082 | + |
|
| 3083 | + @Override |
|
| 3084 | + public DataImportLockWithProgress getDataImportLock() { |
|
| 3085 | + return dataImportLock; |
|
| 3086 | + } |
|
| 3087 | + |
|
| 3088 | + @Override |
|
| 3089 | + public DataImportProgress createOrUpdateDataImportProgressWithReplication(UUID importOperationId, |
|
| 3090 | + double overallProgressPct, String subProgressName, double subProgressPct) { |
|
| 3091 | + // Create/Update locally |
|
| 3092 | + DataImportProgress progress = createOrUpdateDataImportProgressWithoutReplication(importOperationId, |
|
| 3093 | + overallProgressPct, subProgressName, subProgressPct); |
|
| 3094 | + // Create/Update on replicas |
|
| 3095 | + replicate(new CreateOrUpdateDataImportProgress(importOperationId, overallProgressPct, subProgressName, |
|
| 3096 | + subProgressPct)); |
|
| 3097 | + return progress; |
|
| 3098 | + } |
|
| 3099 | + |
|
| 3100 | + @Override |
|
| 3101 | + public DataImportProgress createOrUpdateDataImportProgressWithoutReplication(UUID importOperationId, |
|
| 3102 | + double overallProgressPct, String subProgressName, double subProgressPct) { |
|
| 3103 | + DataImportProgress progress = dataImportLock.getProgress(importOperationId); |
|
| 3104 | + boolean newObject = false; |
|
| 3105 | + if (progress == null) { |
|
| 3106 | + progress = new DataImportProgressImpl(importOperationId); |
|
| 3107 | + newObject = true; |
|
| 3108 | + } |
|
| 3109 | + progress.setOverAllProgressPct(overallProgressPct); |
|
| 3110 | + progress.setNameOfCurrentSubProgress(subProgressName); |
|
| 3111 | + progress.setCurrentSubProgressPct(subProgressPct); |
|
| 3112 | + if (newObject) { |
|
| 3113 | + dataImportLock.addProgress(importOperationId, progress); |
|
| 3114 | + } |
|
| 3115 | + return progress; |
|
| 3116 | + } |
|
| 3117 | + |
|
| 3118 | + @Override |
|
| 3119 | + public void setDataImportFailedWithoutReplication(UUID importOperationId, String errorMessage) { |
|
| 3120 | + DataImportProgress progress = dataImportLock.getProgress(importOperationId); |
|
| 3121 | + if (progress != null) { |
|
| 3122 | + progress.setFailed(); |
|
| 3123 | + progress.setErrorMessage(errorMessage); |
|
| 3124 | + } |
|
| 3125 | + } |
|
| 3126 | + |
|
| 3127 | + @Override |
|
| 3128 | + public void setDataImportFailedWithReplication(UUID importOperationId, String errorMessage) { |
|
| 3129 | + setDataImportFailedWithoutReplication(importOperationId, errorMessage); |
|
| 3130 | + replicate(new DataImportFailed(importOperationId, errorMessage)); |
|
| 3131 | + } |
|
| 3132 | + |
|
| 3133 | + @Override |
|
| 3134 | + public void setDataImportDeleteProgressFromMapTimerWithReplication(UUID importOperationId) { |
|
| 3135 | + setDataImportDeleteProgressFromMapTimerWithoutReplication(importOperationId); |
|
| 3136 | + replicate(new SetDataImportDeleteProgressFromMapTimer(importOperationId)); |
|
| 3137 | + } |
|
| 3138 | + |
|
| 3139 | + @Override |
|
| 3140 | + public void setDataImportDeleteProgressFromMapTimerWithoutReplication(UUID importOperationId) { |
|
| 3141 | + dataImportLock.setDeleteFromMapTimer(importOperationId); |
|
| 3142 | + } |
|
| 3143 | + |
|
| 3144 | + @Override |
|
| 3145 | + public Result<LeaderboardSearchResult> search(KeywordQuery query) { |
|
| 3146 | + long start = System.currentTimeMillis(); |
|
| 3147 | + logger.info("Searching local server for " + query); |
|
| 3148 | + Result<LeaderboardSearchResult> result = new RegattaByKeywordSearchService().search(this, query); |
|
| 3149 | + logger.fine("Search for " + query + " took " + (System.currentTimeMillis() - start) + "ms"); |
|
| 3150 | + return result; |
|
| 3151 | + } |
|
| 3152 | + |
|
| 3153 | + @Override |
|
| 3154 | + public Result<LeaderboardSearchResultBase> searchRemotely(String remoteServerReferenceName, KeywordQuery query) { |
|
| 3155 | + long start = System.currentTimeMillis(); |
|
| 3156 | + ResultImpl<LeaderboardSearchResultBase> result = null; |
|
| 3157 | + RemoteSailingServerReference remoteRef = remoteSailingServerSet |
|
| 3158 | + .getServerReferenceByName(remoteServerReferenceName); |
|
| 3159 | + if (remoteRef == null) { |
|
| 3160 | + result = null; |
|
| 3161 | + } else { |
|
| 3162 | + BufferedReader bufferedReader = null; |
|
| 3163 | + try { |
|
| 3164 | + try { |
|
| 3165 | + final URL eventsURL = new URL(remoteRef.getURL(), "sailingserver/api/v1/search?q=" |
|
| 3166 | + + URLEncoder.encode(query.toString(), "UTF-8")); |
|
| 3167 | + logger.info("Searching remote server " + remoteRef + " for " + query); |
|
| 3168 | + URLConnection urlConnection = eventsURL.openConnection(); |
|
| 3169 | + urlConnection.connect(); |
|
| 3170 | + bufferedReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "UTF-8")); |
|
| 3171 | + JSONParser parser = new JSONParser(); |
|
| 3172 | + Object eventsAsObject = parser.parse(bufferedReader); |
|
| 3173 | + final LeaderboardGroupBaseJsonDeserializer leaderboardGroupBaseJsonDeserializer = new LeaderboardGroupBaseJsonDeserializer(); |
|
| 3174 | + LeaderboardSearchResultBaseJsonDeserializer deserializer = new LeaderboardSearchResultBaseJsonDeserializer( |
|
| 3175 | + new EventBaseJsonDeserializer(new VenueJsonDeserializer(new CourseAreaJsonDeserializer( |
|
| 3176 | + DomainFactory.INSTANCE)), leaderboardGroupBaseJsonDeserializer), |
|
| 3177 | + leaderboardGroupBaseJsonDeserializer); |
|
| 3178 | + result = new ResultImpl<LeaderboardSearchResultBase>(query, |
|
| 3179 | + new LeaderboardSearchResultBaseRanker<LeaderboardSearchResultBase>()); |
|
| 3180 | + JSONArray hitsAsJsonArray = (JSONArray) eventsAsObject; |
|
| 3181 | + for (Object hitAsObject : hitsAsJsonArray) { |
|
| 3182 | + JSONObject hitAsJson = (JSONObject) hitAsObject; |
|
| 3183 | + LeaderboardSearchResultBase hit = deserializer.deserialize(hitAsJson); |
|
| 3184 | + result.addHit(hit); |
|
| 3185 | + } |
|
| 3186 | + } finally { |
|
| 3187 | + if (bufferedReader != null) { |
|
| 3188 | + bufferedReader.close(); |
|
| 3189 | + } |
|
| 3190 | + } |
|
| 3191 | + } catch (IOException | ParseException e) { |
|
| 3192 | + logger.log(Level.INFO, |
|
| 3193 | + "Exception trying to fetch events from remote server " + remoteRef + ": " + e.getMessage(), e); |
|
| 3194 | + } |
|
| 3195 | + } |
|
| 3196 | + logger.fine("Remote search on " + remoteRef + " for " + query + " took " + (System.currentTimeMillis() - start) |
|
| 3197 | + + "ms"); |
|
| 3198 | + return result; |
|
| 3199 | + } |
|
| 3200 | + |
|
| 3201 | + @Override |
|
| 3202 | + public ReplicationMasterDescriptor getMasterDescriptor() { |
|
| 3203 | + return replicatingFromMaster; |
|
| 3204 | + } |
|
| 3205 | + |
|
| 3206 | + @Override |
|
| 3207 | + public void startedReplicatingFrom(ReplicationMasterDescriptor master) { |
|
| 3208 | + this.replicatingFromMaster = master; |
|
| 3209 | + } |
|
| 3210 | + |
|
| 3211 | + @Override |
|
| 3212 | + public void stoppedReplicatingFrom(ReplicationMasterDescriptor master) { |
|
| 3213 | + this.replicatingFromMaster = null; |
|
| 3214 | + } |
|
| 3215 | + |
|
| 3216 | + @Override |
|
| 3217 | + public void addOperationSentToMasterForReplication( |
|
| 3218 | + OperationWithResultWithIdWrapper<RacingEventService, ?> operationWithResultWithIdWrapper) { |
|
| 3219 | + this.operationsSentToMasterForReplication.add(operationWithResultWithIdWrapper); |
|
| 3220 | + } |
|
| 3221 | + |
|
| 3222 | + @Override |
|
| 3223 | + public boolean hasSentOperationToMaster(OperationWithResult<RacingEventService, ?> operation) { |
|
| 3224 | + return this.operationsSentToMasterForReplication.remove(operation); |
|
| 3225 | + } |
|
| 3226 | + |
|
| 3227 | + @Override |
|
| 3228 | + public FileStorageManagementService getFileStorageManagementService() { |
|
| 3229 | + ServiceReference<FileStorageManagementService> ref = bundleContext |
|
| 3230 | + .getServiceReference(FileStorageManagementService.class); |
|
| 3231 | + if (ref == null) { |
|
| 3232 | + logger.warning("No file storage management service registered"); |
|
| 3233 | + return null; |
|
| 3234 | + } |
|
| 3235 | + return bundleContext.getService(ref); |
|
| 3236 | + } |
|
| 3237 | + |
|
| 3238 | + public void addMasterDataClassLoader(ClassLoader classLoader) { |
|
| 3239 | + masterDataClassLoaders.add(classLoader); |
|
| 3240 | + } |
|
| 3241 | + |
|
| 3242 | + public void removeMasterDataClassLoader(ClassLoader classLoader) { |
|
| 3243 | + masterDataClassLoaders.remove(classLoader); |
|
| 3244 | + } |
|
| 3245 | + |
|
| 3246 | + @Override |
|
| 3247 | + public ClassLoader getCombinedMasterDataClassLoader() { |
|
| 3248 | + JoinedClassLoader joinedClassLoader = new JoinedClassLoader(masterDataClassLoaders); |
|
| 3249 | + return joinedClassLoader; |
|
| 3250 | + } |
|
| 3251 | + |
|
| 3252 | + public void setPolarDataService(PolarDataService service) { |
|
| 3253 | + if (this.polarDataService == null && service != null) { |
|
| 3254 | + polarDataService = service; |
|
| 3255 | + polarDataService.registerDomainFactory(baseDomainFactory); |
|
| 3256 | + setPolarDataServiceOnAllTrackedRaces(service); |
|
| 3257 | + } |
|
| 3258 | + } |
|
| 3259 | + |
|
| 3260 | + private void setPolarDataServiceOnAllTrackedRaces(PolarDataService service) { |
|
| 3261 | + Iterable<Regatta> allRegattas = getAllRegattas(); |
|
| 3262 | + for (Regatta regatta : allRegattas) { |
|
| 3263 | + DynamicTrackedRegatta trackedRegatta = getTrackedRegatta(regatta); |
|
| 3264 | + if (trackedRegatta != null) { |
|
| 3265 | + trackedRegatta.lockTrackedRacesForRead(); |
|
| 3266 | + try { |
|
| 3267 | + Iterable<DynamicTrackedRace> trackedRaces = trackedRegatta.getTrackedRaces(); |
|
| 3268 | + for (TrackedRace trackedRace : trackedRaces) { |
|
| 3269 | + trackedRace.setPolarDataService(service); |
|
| 3270 | + service.insertExistingFixes(trackedRace); |
|
| 3271 | + } |
|
| 3272 | + } finally { |
|
| 3273 | + trackedRegatta.unlockTrackedRacesAfterRead(); |
|
| 3274 | + } |
|
| 3275 | + } |
|
| 3276 | + } |
|
| 3277 | + } |
|
| 3278 | + |
|
| 3279 | + public void unsetPolarDataService(PolarDataService service) { |
|
| 3280 | + if (polarDataService == service) { |
|
| 3281 | + polarDataService.unregisterDomainFactory(baseDomainFactory); |
|
| 3282 | + polarDataService = null; |
|
| 3283 | + setPolarDataService(null); |
|
| 3284 | + } |
|
| 3285 | + } |
|
| 3286 | + |
|
| 3287 | + @Override |
|
| 3288 | + public Iterable<Competitor> getCompetitorInOrderOfWindwardDistanceTraveledFarthestFirst(TrackedRace trackedRace, TimePoint timePoint) { |
|
| 3289 | + final RankingInfo rankingInfo = trackedRace.getRankingMetric().getRankingInfo(timePoint); |
|
| 3290 | + final List<Competitor> result = new ArrayList<>(); |
|
| 3291 | + final Map<Competitor, Distance> windwardDistanceSailedPerCompetitor = new HashMap<>(); |
|
| 3292 | + for (final Competitor competitor : trackedRace.getRace().getCompetitors()) { |
|
| 3293 | + result.add(competitor); |
|
| 3294 | + final CompetitorRankingInfo competitorRankingInfo = rankingInfo.getCompetitorRankingInfo().apply(competitor); |
|
| 3295 | + windwardDistanceSailedPerCompetitor.put(competitor, competitorRankingInfo == null ? null : competitorRankingInfo.getWindwardDistanceSailed()); |
|
| 3296 | + } |
|
| 3297 | + final Comparator<Distance> durationComparatorNullsLast = Comparator.nullsLast(Comparator.naturalOrder()); |
|
| 3298 | + result.sort((c1, c2) -> durationComparatorNullsLast.compare(windwardDistanceSailedPerCompetitor.get(c2), |
|
| 3299 | + windwardDistanceSailedPerCompetitor.get(c1))); |
|
| 3300 | + return result; |
|
| 3301 | + } |
|
| 3302 | + |
|
| 3303 | + /** |
|
| 3304 | + * A {@link SimpleRaceLogIdentifier} in particular has a {@link SimpleRaceLogIdentifier#getRegattaLikeParentName()} |
|
| 3305 | + * which identifies either a regatta by name or a flexible leaderboard by name. Here is why this can luckily be |
|
| 3306 | + * resolved unanimously: A regatta leaderboard always uses as its name the regatta name (see |
|
| 3307 | + * {@link RegattaImpl#getName()}). Trying to {@link RegattaLeaderboardImpl#setName(String) set} the regatta leaderboard's |
|
| 3308 | + * name can only update its {@link Leaderboard#getDisplayName() display name}. Therefore, regatta leaderboards are always |
|
| 3309 | + * keyed in {@link #leaderboardsByName} by their regatta's name. Thus, no flexible leaderboard can have a regatta's name |
|
| 3310 | + * as its name, and therefore leaderboard names <em>and</em> regatta names are unitedly unique. |
|
| 3311 | + */ |
|
| 3312 | + @Override |
|
| 3313 | + public RaceLog resolve(SimpleRaceLogIdentifier identifier) { |
|
| 3314 | + final RaceLog result; |
|
| 3315 | + final IsRegattaLike regattaLike; |
|
| 3316 | + final Regatta regatta = regattasByName.get(identifier.getRegattaLikeParentName()); |
|
| 3317 | + if (regatta != null) { |
|
| 3318 | + regattaLike = regatta; |
|
| 3319 | + } else { |
|
| 3320 | + final Leaderboard leaderboard = leaderboardsByName.get(identifier.getRegattaLikeParentName()); |
|
| 3321 | + if (leaderboard != null && leaderboard instanceof FlexibleLeaderboard) { |
|
| 3322 | + regattaLike = (FlexibleLeaderboard) leaderboard; |
|
| 3323 | + } else { |
|
| 3324 | + regattaLike = null; |
|
| 3325 | + } |
|
| 3326 | + } |
|
| 3327 | + if (regattaLike != null) { |
|
| 3328 | + final RaceColumn raceColumn = regattaLike.getRaceColumnByName(identifier.getRaceColumnName()); |
|
| 3329 | + if (raceColumn != null) { |
|
| 3330 | + final Fleet fleet = raceColumn.getFleetByName(identifier.getFleetName()); |
|
| 3331 | + if (fleet != null) { |
|
| 3332 | + result = raceColumn.getRaceLog(fleet); |
|
| 3333 | + } else { |
|
| 3334 | + result = null; |
|
| 3335 | + } |
|
| 3336 | + } else { |
|
| 3337 | + result = null; |
|
| 3338 | + } |
|
| 3339 | + } else { |
|
| 3340 | + result = null; |
|
| 3341 | + } |
|
| 3342 | + return result; |
|
| 3343 | + } |
|
| 3344 | +} |
java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/CreateEvent.java
| ... | ... | @@ -1,70 +1,70 @@ |
| 1 | -package com.sap.sailing.server.operationaltransformation;
|
|
| 2 | -
|
|
| 3 | -import java.net.URL;
|
|
| 4 | -import java.util.UUID;
|
|
| 5 | -
|
|
| 6 | -import com.sap.sailing.domain.base.Event;
|
|
| 7 | -import com.sap.sailing.server.RacingEventService;
|
|
| 8 | -import com.sap.sailing.server.RacingEventServiceOperation;
|
|
| 9 | -import com.sap.sse.common.TimePoint;
|
|
| 10 | -import com.sap.sse.common.media.ImageDescriptor;
|
|
| 11 | -import com.sap.sse.common.media.VideoDescriptor;
|
|
| 12 | -
|
|
| 13 | -/**
|
|
| 14 | - * Creates an {@link Event} in the server, with a new venue and an empty course area list.
|
|
| 15 | - * See the {@link AddCourseAreas} operation for adding course areas to the event's venue.
|
|
| 16 | - *
|
|
| 17 | - * @author Axel Uhl (d043530)
|
|
| 18 | - *
|
|
| 19 | - */
|
|
| 20 | -public class CreateEvent extends AbstractEventOperation<Event> {
|
|
| 21 | - private static final long serialVersionUID = 308389324918359960L;
|
|
| 22 | - private final String venue;
|
|
| 23 | - private final TimePoint startDate;
|
|
| 24 | - private final TimePoint endDate;
|
|
| 25 | - private final boolean isPublic;
|
|
| 26 | - private final String eventName;
|
|
| 27 | - private final String eventDescription;
|
|
| 28 | - private final Iterable<ImageDescriptor> images;
|
|
| 29 | - private final Iterable<VideoDescriptor> videos;
|
|
| 30 | - private final URL officialWebsiteURL;
|
|
| 31 | - private final URL sailorsInfoWebsiteURL;
|
|
| 32 | -
|
|
| 33 | - public CreateEvent(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venue,
|
|
| 34 | - boolean isPublic, UUID id, URL officialWebsiteURL, URL sailorsInfoWebsiteURL, Iterable<ImageDescriptor> images, Iterable<VideoDescriptor> videos) {
|
|
| 35 | - super(id);
|
|
| 36 | - this.eventName = eventName;
|
|
| 37 | - this.eventDescription = eventDescription;
|
|
| 38 | - this.startDate = startDate;
|
|
| 39 | - this.endDate = endDate;
|
|
| 40 | - this.venue = venue;
|
|
| 41 | - this.isPublic = isPublic;
|
|
| 42 | - this.officialWebsiteURL = officialWebsiteURL;
|
|
| 43 | - this.sailorsInfoWebsiteURL = sailorsInfoWebsiteURL;
|
|
| 44 | - this.images = images;
|
|
| 45 | - this.videos = videos;
|
|
| 46 | - }
|
|
| 47 | -
|
|
| 48 | - @Override
|
|
| 49 | - public RacingEventServiceOperation<?> transformClientOp(RacingEventServiceOperation<?> serverOp) {
|
|
| 50 | - // TODO Auto-generated method stub
|
|
| 51 | - return null;
|
|
| 52 | - }
|
|
| 53 | -
|
|
| 54 | - @Override
|
|
| 55 | - public RacingEventServiceOperation<?> transformServerOp(RacingEventServiceOperation<?> clientOp) {
|
|
| 56 | - // TODO Auto-generated method stub
|
|
| 57 | - return null;
|
|
| 58 | - }
|
|
| 59 | -
|
|
| 60 | - protected String getEventName() {
|
|
| 61 | - return eventName;
|
|
| 62 | - }
|
|
| 63 | -
|
|
| 64 | - @Override
|
|
| 65 | - public Event internalApplyTo(RacingEventService toState) {
|
|
| 66 | - return toState.createEventWithoutReplication(getEventName(), eventDescription, startDate, endDate, venue, isPublic,
|
|
| 67 | - getId(), officialWebsiteURL, sailorsInfoWebsiteURL, images, videos);
|
|
| 68 | - }
|
|
| 69 | -
|
|
| 70 | -}
|
|
| 1 | +package com.sap.sailing.server.operationaltransformation; |
|
| 2 | + |
|
| 3 | +import java.net.URL; |
|
| 4 | +import java.util.UUID; |
|
| 5 | + |
|
| 6 | +import com.sap.sailing.domain.base.Event; |
|
| 7 | +import com.sap.sailing.server.RacingEventService; |
|
| 8 | +import com.sap.sailing.server.RacingEventServiceOperation; |
|
| 9 | +import com.sap.sse.common.TimePoint; |
|
| 10 | +import com.sap.sse.shared.media.ImageDescriptor; |
|
| 11 | +import com.sap.sse.shared.media.VideoDescriptor; |
|
| 12 | + |
|
| 13 | +/** |
|
| 14 | + * Creates an {@link Event} in the server, with a new venue and an empty course area list. |
|
| 15 | + * See the {@link AddCourseAreas} operation for adding course areas to the event's venue. |
|
| 16 | + * |
|
| 17 | + * @author Axel Uhl (d043530) |
|
| 18 | + * |
|
| 19 | + */ |
|
| 20 | +public class CreateEvent extends AbstractEventOperation<Event> { |
|
| 21 | + private static final long serialVersionUID = 308389324918359960L; |
|
| 22 | + private final String venue; |
|
| 23 | + private final TimePoint startDate; |
|
| 24 | + private final TimePoint endDate; |
|
| 25 | + private final boolean isPublic; |
|
| 26 | + private final String eventName; |
|
| 27 | + private final String eventDescription; |
|
| 28 | + private final Iterable<ImageDescriptor> images; |
|
| 29 | + private final Iterable<VideoDescriptor> videos; |
|
| 30 | + private final URL officialWebsiteURL; |
|
| 31 | + private final URL sailorsInfoWebsiteURL; |
|
| 32 | + |
|
| 33 | + public CreateEvent(String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venue, |
|
| 34 | + boolean isPublic, UUID id, URL officialWebsiteURL, URL sailorsInfoWebsiteURL, Iterable<ImageDescriptor> images, Iterable<VideoDescriptor> videos) { |
|
| 35 | + super(id); |
|
| 36 | + this.eventName = eventName; |
|
| 37 | + this.eventDescription = eventDescription; |
|
| 38 | + this.startDate = startDate; |
|
| 39 | + this.endDate = endDate; |
|
| 40 | + this.venue = venue; |
|
| 41 | + this.isPublic = isPublic; |
|
| 42 | + this.officialWebsiteURL = officialWebsiteURL; |
|
| 43 | + this.sailorsInfoWebsiteURL = sailorsInfoWebsiteURL; |
|
| 44 | + this.images = images; |
|
| 45 | + this.videos = videos; |
|
| 46 | + } |
|
| 47 | + |
|
| 48 | + @Override |
|
| 49 | + public RacingEventServiceOperation<?> transformClientOp(RacingEventServiceOperation<?> serverOp) { |
|
| 50 | + // TODO Auto-generated method stub |
|
| 51 | + return null; |
|
| 52 | + } |
|
| 53 | + |
|
| 54 | + @Override |
|
| 55 | + public RacingEventServiceOperation<?> transformServerOp(RacingEventServiceOperation<?> clientOp) { |
|
| 56 | + // TODO Auto-generated method stub |
|
| 57 | + return null; |
|
| 58 | + } |
|
| 59 | + |
|
| 60 | + protected String getEventName() { |
|
| 61 | + return eventName; |
|
| 62 | + } |
|
| 63 | + |
|
| 64 | + @Override |
|
| 65 | + public Event internalApplyTo(RacingEventService toState) { |
|
| 66 | + return toState.createEventWithoutReplication(getEventName(), eventDescription, startDate, endDate, venue, isPublic, |
|
| 67 | + getId(), officialWebsiteURL, sailorsInfoWebsiteURL, images, videos); |
|
| 68 | + } |
|
| 69 | + |
|
| 70 | +} |
java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateEvent.java
| ... | ... | @@ -1,61 +1,61 @@ |
| 1 | -package com.sap.sailing.server.operationaltransformation;
|
|
| 2 | -
|
|
| 3 | -import java.net.URL;
|
|
| 4 | -import java.util.UUID;
|
|
| 5 | -
|
|
| 6 | -import com.sap.sailing.server.RacingEventService;
|
|
| 7 | -import com.sap.sailing.server.RacingEventServiceOperation;
|
|
| 8 | -import com.sap.sse.common.TimePoint;
|
|
| 9 | -import com.sap.sse.common.media.ImageDescriptor;
|
|
| 10 | -import com.sap.sse.common.media.VideoDescriptor;
|
|
| 11 | -
|
|
| 12 | -public class UpdateEvent extends AbstractEventOperation<Void> {
|
|
| 13 | - private static final long serialVersionUID = -8271559266421161532L;
|
|
| 14 | - private final String venueName;
|
|
| 15 | - private final TimePoint startDate;
|
|
| 16 | - private final TimePoint endDate;
|
|
| 17 | - private final boolean isPublic;
|
|
| 18 | - private final Iterable<UUID> leaderboardGroupIds;
|
|
| 19 | - private final String eventName;
|
|
| 20 | - private final String eventDescription;
|
|
| 21 | - private final URL officialWebsiteURL;
|
|
| 22 | - private final URL sailorsInfoWebsiteURL;
|
|
| 23 | - private final Iterable<ImageDescriptor> images;
|
|
| 24 | - private final Iterable<VideoDescriptor> videos;
|
|
| 25 | -
|
|
| 26 | - public UpdateEvent(UUID id, String eventName, String eventDescription, TimePoint startDate, TimePoint endDate,
|
|
| 27 | - String venueName, boolean isPublic, Iterable<UUID> leaderboardGroupIds, URL officialWebsiteURL, URL sailorsInfoWebsiteURL,
|
|
| 28 | - Iterable<ImageDescriptor> images, Iterable<VideoDescriptor> videos) {
|
|
| 29 | - super(id);
|
|
| 30 | - this.eventName = eventName;
|
|
| 31 | - this.eventDescription = eventDescription;
|
|
| 32 | - this.startDate = startDate;
|
|
| 33 | - this.endDate = endDate;
|
|
| 34 | - this.venueName = venueName;
|
|
| 35 | - this.isPublic = isPublic;
|
|
| 36 | - this.leaderboardGroupIds = leaderboardGroupIds;
|
|
| 37 | - this.officialWebsiteURL = officialWebsiteURL;
|
|
| 38 | - this.sailorsInfoWebsiteURL = sailorsInfoWebsiteURL;
|
|
| 39 | - this.images = images;
|
|
| 40 | - this.videos = videos;
|
|
| 41 | - }
|
|
| 42 | -
|
|
| 43 | - @Override
|
|
| 44 | - public RacingEventServiceOperation<?> transformClientOp(RacingEventServiceOperation<?> serverOp) {
|
|
| 45 | - // TODO Auto-generated method stub
|
|
| 46 | - return null;
|
|
| 47 | - }
|
|
| 48 | -
|
|
| 49 | - @Override
|
|
| 50 | - public RacingEventServiceOperation<?> transformServerOp(RacingEventServiceOperation<?> clientOp) {
|
|
| 51 | - // TODO Auto-generated method stub
|
|
| 52 | - return null;
|
|
| 53 | - }
|
|
| 54 | -
|
|
| 55 | - @Override
|
|
| 56 | - public Void internalApplyTo(RacingEventService toState) {
|
|
| 57 | - toState.updateEvent(getId(), eventName, eventDescription, startDate, endDate, venueName, isPublic,
|
|
| 58 | - leaderboardGroupIds, officialWebsiteURL, sailorsInfoWebsiteURL, images, videos);
|
|
| 59 | - return null;
|
|
| 60 | - }
|
|
| 61 | -}
|
|
| 1 | +package com.sap.sailing.server.operationaltransformation; |
|
| 2 | + |
|
| 3 | +import java.net.URL; |
|
| 4 | +import java.util.UUID; |
|
| 5 | + |
|
| 6 | +import com.sap.sailing.server.RacingEventService; |
|
| 7 | +import com.sap.sailing.server.RacingEventServiceOperation; |
|
| 8 | +import com.sap.sse.common.TimePoint; |
|
| 9 | +import com.sap.sse.shared.media.ImageDescriptor; |
|
| 10 | +import com.sap.sse.shared.media.VideoDescriptor; |
|
| 11 | + |
|
| 12 | +public class UpdateEvent extends AbstractEventOperation<Void> { |
|
| 13 | + private static final long serialVersionUID = -8271559266421161532L; |
|
| 14 | + private final String venueName; |
|
| 15 | + private final TimePoint startDate; |
|
| 16 | + private final TimePoint endDate; |
|
| 17 | + private final boolean isPublic; |
|
| 18 | + private final Iterable<UUID> leaderboardGroupIds; |
|
| 19 | + private final String eventName; |
|
| 20 | + private final String eventDescription; |
|
| 21 | + private final URL officialWebsiteURL; |
|
| 22 | + private final URL sailorsInfoWebsiteURL; |
|
| 23 | + private final Iterable<ImageDescriptor> images; |
|
| 24 | + private final Iterable<VideoDescriptor> videos; |
|
| 25 | + |
|
| 26 | + public UpdateEvent(UUID id, String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, |
|
| 27 | + String venueName, boolean isPublic, Iterable<UUID> leaderboardGroupIds, URL officialWebsiteURL, URL sailorsInfoWebsiteURL, |
|
| 28 | + Iterable<ImageDescriptor> images, Iterable<VideoDescriptor> videos) { |
|
| 29 | + super(id); |
|
| 30 | + this.eventName = eventName; |
|
| 31 | + this.eventDescription = eventDescription; |
|
| 32 | + this.startDate = startDate; |
|
| 33 | + this.endDate = endDate; |
|
| 34 | + this.venueName = venueName; |
|
| 35 | + this.isPublic = isPublic; |
|
| 36 | + this.leaderboardGroupIds = leaderboardGroupIds; |
|
| 37 | + this.officialWebsiteURL = officialWebsiteURL; |
|
| 38 | + this.sailorsInfoWebsiteURL = sailorsInfoWebsiteURL; |
|
| 39 | + this.images = images; |
|
| 40 | + this.videos = videos; |
|
| 41 | + } |
|
| 42 | + |
|
| 43 | + @Override |
|
| 44 | + public RacingEventServiceOperation<?> transformClientOp(RacingEventServiceOperation<?> serverOp) { |
|
| 45 | + // TODO Auto-generated method stub |
|
| 46 | + return null; |
|
| 47 | + } |
|
| 48 | + |
|
| 49 | + @Override |
|
| 50 | + public RacingEventServiceOperation<?> transformServerOp(RacingEventServiceOperation<?> clientOp) { |
|
| 51 | + // TODO Auto-generated method stub |
|
| 52 | + return null; |
|
| 53 | + } |
|
| 54 | + |
|
| 55 | + @Override |
|
| 56 | + public Void internalApplyTo(RacingEventService toState) { |
|
| 57 | + toState.updateEvent(getId(), eventName, eventDescription, startDate, endDate, venueName, isPublic, |
|
| 58 | + leaderboardGroupIds, officialWebsiteURL, sailorsInfoWebsiteURL, images, videos); |
|
| 59 | + return null; |
|
| 60 | + } |
|
| 61 | +} |
java/com.sap.sse.common/pom.xml
| ... | ... | @@ -11,30 +11,30 @@ |
| 11 | 11 | <packaging>eclipse-plugin</packaging> |
| 12 | 12 | |
| 13 | 13 | <build> |
| 14 | - <plugins> |
|
| 15 | - <plugin> |
|
| 16 | - <groupId>org.eclipse.tycho</groupId> |
|
| 17 | - <artifactId>tycho-compiler-plugin</artifactId> |
|
| 18 | - <version>${tycho-version}</version> |
|
| 19 | - <configuration> |
|
| 20 | - <source>1.7</source> |
|
| 21 | - <target>1.7</target> |
|
| 22 | - </configuration> |
|
| 23 | - </plugin> |
|
| 24 | - <plugin> |
|
| 25 | - <groupId>org.eclipse.tycho</groupId> |
|
| 26 | - <artifactId>tycho-source-plugin</artifactId> |
|
| 27 | - <version>${tycho-version}</version> |
|
| 28 | - <executions> |
|
| 29 | - <execution> |
|
| 30 | - <id>plugin-source</id> |
|
| 31 | - <phase>generate-sources</phase> |
|
| 32 | - <goals> |
|
| 33 | - <goal>plugin-source</goal> |
|
| 34 | - </goals> |
|
| 35 | - </execution> |
|
| 36 | - </executions> |
|
| 37 | - </plugin> |
|
| 38 | - </plugins> |
|
| 14 | + <plugins> |
|
| 15 | + <plugin> |
|
| 16 | + <groupId>org.eclipse.tycho</groupId> |
|
| 17 | + <artifactId>tycho-compiler-plugin</artifactId> |
|
| 18 | + <version>${tycho-version}</version> |
|
| 19 | + <configuration> |
|
| 20 | + <source>1.7</source> |
|
| 21 | + <target>1.7</target> |
|
| 22 | + </configuration> |
|
| 23 | + </plugin> |
|
| 24 | + <plugin> |
|
| 25 | + <groupId>org.eclipse.tycho</groupId> |
|
| 26 | + <artifactId>tycho-source-plugin</artifactId> |
|
| 27 | + <version>${tycho-version}</version> |
|
| 28 | + <executions> |
|
| 29 | + <execution> |
|
| 30 | + <id>plugin-source</id> |
|
| 31 | + <phase>generate-sources</phase> |
|
| 32 | + <goals> |
|
| 33 | + <goal>plugin-source</goal> |
|
| 34 | + </goals> |
|
| 35 | + </execution> |
|
| 36 | + </executions> |
|
| 37 | + </plugin> |
|
| 38 | + </plugins> |
|
| 39 | 39 | </build> |
| 40 | 40 | </project> |
java/com.sap.sse.common/src/com/sap/sse/common/media/AbstractMediaDescriptor.java
| ... | ... | @@ -1,136 +0,0 @@ |
| 1 | -package com.sap.sse.common.media; |
|
| 2 | - |
|
| 3 | -import java.io.Serializable; |
|
| 4 | -import java.net.URL; |
|
| 5 | -import java.util.LinkedHashSet; |
|
| 6 | -import java.util.Locale; |
|
| 7 | -import java.util.Set; |
|
| 8 | - |
|
| 9 | -import com.sap.sse.common.TimePoint; |
|
| 10 | -import com.sap.sse.common.Util; |
|
| 11 | - |
|
| 12 | -/** |
|
| 13 | - * Common media data for media items |
|
| 14 | - * |
|
| 15 | - * @author pgtaboada |
|
| 16 | - * |
|
| 17 | - */ |
|
| 18 | -public abstract class AbstractMediaDescriptor implements MediaDescriptor, Serializable { |
|
| 19 | - private static final long serialVersionUID = -6671425870632517274L; |
|
| 20 | - |
|
| 21 | - protected String title; |
|
| 22 | - |
|
| 23 | - protected String subtitle; |
|
| 24 | - |
|
| 25 | - protected TimePoint createdAtDate; |
|
| 26 | - |
|
| 27 | - protected String copyright; |
|
| 28 | - |
|
| 29 | - protected MimeType mimeType; |
|
| 30 | - |
|
| 31 | - protected Set<String> tags = new LinkedHashSet<String>(); |
|
| 32 | - |
|
| 33 | - protected URL url; |
|
| 34 | - |
|
| 35 | - protected Locale locale; |
|
| 36 | - |
|
| 37 | - /** |
|
| 38 | - * Media item with minimal set of information |
|
| 39 | - * @param url |
|
| 40 | - * @param mimeType |
|
| 41 | - */ |
|
| 42 | - public AbstractMediaDescriptor(URL url, MimeType mimeType, TimePoint createdAtDate) { |
|
| 43 | - this.mimeType = mimeType; |
|
| 44 | - this.url = url; |
|
| 45 | - this.createdAtDate = createdAtDate; |
|
| 46 | - } |
|
| 47 | - |
|
| 48 | - @Override |
|
| 49 | - public MimeType getMimeType() { |
|
| 50 | - return mimeType; |
|
| 51 | - } |
|
| 52 | - |
|
| 53 | - @Override |
|
| 54 | - public URL getURL() { |
|
| 55 | - return url; |
|
| 56 | - } |
|
| 57 | - |
|
| 58 | - @Override |
|
| 59 | - public String getTitle() { |
|
| 60 | - return title; |
|
| 61 | - } |
|
| 62 | - |
|
| 63 | - @Override |
|
| 64 | - public void setTitle(String title) { |
|
| 65 | - this.title = title; |
|
| 66 | - } |
|
| 67 | - |
|
| 68 | - @Override |
|
| 69 | - public Set<String> getTags() { |
|
| 70 | - return tags; |
|
| 71 | - } |
|
| 72 | - |
|
| 73 | - @Override |
|
| 74 | - public void setTags(Iterable<String> tags) { |
|
| 75 | - this.tags.clear(); |
|
| 76 | - if (tags != null) { |
|
| 77 | - Util.addAll(tags, this.tags); |
|
| 78 | - } |
|
| 79 | - } |
|
| 80 | - |
|
| 81 | - |
|
| 82 | - @Override |
|
| 83 | - public boolean addTag(String tagName) { |
|
| 84 | - return tags.add(tagName); |
|
| 85 | - } |
|
| 86 | - |
|
| 87 | - @Override |
|
| 88 | - public boolean removeTag(String tagName) { |
|
| 89 | - return tags.remove(tagName); |
|
| 90 | - } |
|
| 91 | - |
|
| 92 | - @Override |
|
| 93 | - public String getSubtitle() { |
|
| 94 | - return subtitle; |
|
| 95 | - } |
|
| 96 | - |
|
| 97 | - @Override |
|
| 98 | - public void setSubtitle(String subtitle) { |
|
| 99 | - this.subtitle = subtitle; |
|
| 100 | - } |
|
| 101 | - |
|
| 102 | - @Override |
|
| 103 | - public TimePoint getCreatedAtDate() { |
|
| 104 | - return createdAtDate; |
|
| 105 | - } |
|
| 106 | - |
|
| 107 | - @Override |
|
| 108 | - public void setCreatedAtDate(TimePoint createdAtDate) { |
|
| 109 | - this.createdAtDate = createdAtDate; |
|
| 110 | - } |
|
| 111 | - |
|
| 112 | - @Override |
|
| 113 | - public String getCopyright() { |
|
| 114 | - return copyright; |
|
| 115 | - } |
|
| 116 | - |
|
| 117 | - @Override |
|
| 118 | - public void setCopyright(String copyright) { |
|
| 119 | - this.copyright = copyright; |
|
| 120 | - } |
|
| 121 | - |
|
| 122 | - @Override |
|
| 123 | - public Locale getLocale() { |
|
| 124 | - return locale; |
|
| 125 | - } |
|
| 126 | - |
|
| 127 | - @Override |
|
| 128 | - public void setLocale(Locale locale) { |
|
| 129 | - this.locale = locale; |
|
| 130 | - } |
|
| 131 | - |
|
| 132 | - @Override |
|
| 133 | - public boolean hasTag(String tagName) { |
|
| 134 | - return tags.contains(tagName); |
|
| 135 | - } |
|
| 136 | -} |
java/com.sap.sse.common/src/com/sap/sse/common/media/ImageDescriptor.java
| ... | ... | @@ -1,14 +0,0 @@ |
| 1 | -package com.sap.sse.common.media; |
|
| 2 | - |
|
| 3 | -import com.sap.sse.common.Util.Pair; |
|
| 4 | - |
|
| 5 | -public interface ImageDescriptor extends MediaDescriptor { |
|
| 6 | - void setSize(Pair<Integer, Integer> size); |
|
| 7 | - void setSize(Integer widthInPx, Integer heightInPx); |
|
| 8 | - |
|
| 9 | - Integer getWidthInPx(); |
|
| 10 | - Integer getHeightInPx(); |
|
| 11 | - |
|
| 12 | - boolean hasSize(); |
|
| 13 | - int getArea(); |
|
| 14 | -} |
java/com.sap.sse.common/src/com/sap/sse/common/media/ImageDescriptorImpl.java
| ... | ... | @@ -1,62 +0,0 @@ |
| 1 | -package com.sap.sse.common.media; |
|
| 2 | - |
|
| 3 | -import java.net.URL; |
|
| 4 | - |
|
| 5 | -import com.sap.sse.common.TimePoint; |
|
| 6 | -import com.sap.sse.common.Util.Pair; |
|
| 7 | - |
|
| 8 | - |
|
| 9 | -public class ImageDescriptorImpl extends AbstractMediaDescriptor implements ImageDescriptor { |
|
| 10 | - private static final long serialVersionUID = -702731462768602331L; |
|
| 11 | - |
|
| 12 | - private Integer widthInPx; |
|
| 13 | - private Integer heightInPx; |
|
| 14 | - |
|
| 15 | - /** |
|
| 16 | - * @param imageURL |
|
| 17 | - * @param size |
|
| 18 | - */ |
|
| 19 | - public ImageDescriptorImpl(URL imageURL, TimePoint createdAtDate) { |
|
| 20 | - super(imageURL, MimeType.image, createdAtDate); |
|
| 21 | - } |
|
| 22 | - |
|
| 23 | - @Override |
|
| 24 | - public Integer getWidthInPx() { |
|
| 25 | - return widthInPx; |
|
| 26 | - } |
|
| 27 | - |
|
| 28 | - @Override |
|
| 29 | - public Integer getHeightInPx() { |
|
| 30 | - return heightInPx; |
|
| 31 | - } |
|
| 32 | - |
|
| 33 | - @Override |
|
| 34 | - public void setSize(Pair<Integer, Integer> size) { |
|
| 35 | - if (size != null) { |
|
| 36 | - this.widthInPx = size.getA(); |
|
| 37 | - this.heightInPx = size.getB(); |
|
| 38 | - } else { |
|
| 39 | - this.widthInPx = null; |
|
| 40 | - this.heightInPx = null; |
|
| 41 | - } |
|
| 42 | - } |
|
| 43 | - |
|
| 44 | - @Override |
|
| 45 | - public void setSize(Integer widthInPx, Integer heightInPx) { |
|
| 46 | - this.widthInPx = widthInPx; |
|
| 47 | - this.heightInPx = heightInPx; |
|
| 48 | - } |
|
| 49 | - |
|
| 50 | - @Override |
|
| 51 | - public boolean hasSize() { |
|
| 52 | - return widthInPx != null && heightInPx != null; |
|
| 53 | - } |
|
| 54 | - |
|
| 55 | - @Override |
|
| 56 | - public int getArea() { |
|
| 57 | - if(hasSize()) { |
|
| 58 | - return widthInPx * heightInPx; |
|
| 59 | - } |
|
| 60 | - return 0; |
|
| 61 | - } |
|
| 62 | -} |
java/com.sap.sse.common/src/com/sap/sse/common/media/MediaDescriptor.java
| ... | ... | @@ -1,36 +0,0 @@ |
| 1 | -package com.sap.sse.common.media; |
|
| 2 | - |
|
| 3 | -import java.io.Serializable; |
|
| 4 | -import java.net.URL; |
|
| 5 | -import java.util.Locale; |
|
| 6 | - |
|
| 7 | -import com.sap.sse.common.TimePoint; |
|
| 8 | - |
|
| 9 | -/** |
|
| 10 | - * A common media interface for all kinds of media like images or videos. |
|
| 11 | - */ |
|
| 12 | -public interface MediaDescriptor extends Serializable { |
|
| 13 | - MimeType getMimeType(); |
|
| 14 | - URL getURL(); |
|
| 15 | - |
|
| 16 | - String getTitle(); |
|
| 17 | - void setTitle(String title); |
|
| 18 | - |
|
| 19 | - Iterable<String> getTags(); |
|
| 20 | - void setTags(Iterable<String> tags); |
|
| 21 | - boolean addTag(String tagName); |
|
| 22 | - boolean removeTag(String tagName); |
|
| 23 | - boolean hasTag(String tagName); |
|
| 24 | - |
|
| 25 | - String getSubtitle(); |
|
| 26 | - void setSubtitle(String subtitle); |
|
| 27 | - |
|
| 28 | - TimePoint getCreatedAtDate(); |
|
| 29 | - void setCreatedAtDate(TimePoint createdAtDate); |
|
| 30 | - |
|
| 31 | - String getCopyright(); |
|
| 32 | - void setCopyright(String copyright); |
|
| 33 | - |
|
| 34 | - Locale getLocale(); |
|
| 35 | - void setLocale(Locale locale); |
|
| 36 | -} |
java/com.sap.sse.common/src/com/sap/sse/common/media/MediaUtils.java
| ... | ... | @@ -1,109 +0,0 @@ |
| 1 | -package com.sap.sse.common.media; |
|
| 2 | - |
|
| 3 | -import java.io.IOException; |
|
| 4 | -import java.net.URL; |
|
| 5 | -import java.net.URLConnection; |
|
| 6 | -import java.util.Iterator; |
|
| 7 | -import java.util.concurrent.Callable; |
|
| 8 | -import java.util.concurrent.ExecutorService; |
|
| 9 | -import java.util.concurrent.Executors; |
|
| 10 | -import java.util.concurrent.Future; |
|
| 11 | -import java.util.logging.Level; |
|
| 12 | -import java.util.logging.Logger; |
|
| 13 | -import java.util.regex.Pattern; |
|
| 14 | - |
|
| 15 | -import javax.imageio.ImageIO; |
|
| 16 | -import javax.imageio.ImageReader; |
|
| 17 | -import javax.imageio.stream.ImageInputStream; |
|
| 18 | - |
|
| 19 | -import com.sap.sse.common.Util; |
|
| 20 | -import com.sap.sse.common.Util.Pair; |
|
| 21 | - |
|
| 22 | -public class MediaUtils { |
|
| 23 | - private static final Logger logger = Logger.getLogger(MediaUtils.class.getName()); |
|
| 24 | - |
|
| 25 | - /** |
|
| 26 | - * Youtube regex detection from: |
|
| 27 | - * http://stackoverflow.com/questions/3452546/javascript-regex-how-to-get-youtube-video-id-from-url, mantish Mar 4 |
|
| 28 | - * at 15:33 |
|
| 29 | - */ |
|
| 30 | - private static final Pattern YOUTUBE_ID_REGEX = Pattern |
|
| 31 | - .compile("^.*(youtu.be/|v/|u/\\w/|embed/|watch\\?v=|\\&v=)([^#\\&\\?]+).*$"); |
|
| 32 | - |
|
| 33 | - private static final Pattern VIMEO_REGEX = Pattern.compile("^.*(vimeo\\.com\\/).*"); |
|
| 34 | - |
|
| 35 | - private static final Pattern MP4_REGEX = Pattern.compile(".*\\.mp4$"); |
|
| 36 | - |
|
| 37 | - /** |
|
| 38 | - * Detect mimetype for given url. |
|
| 39 | - * |
|
| 40 | - * @param url |
|
| 41 | - * the source pointing to the video mediafile |
|
| 42 | - * @return mimetype detected or MimeType.unknown |
|
| 43 | - */ |
|
| 44 | - public static MimeType detectMimeTypeFromUrl(String url) { |
|
| 45 | - |
|
| 46 | - if (YOUTUBE_ID_REGEX.matcher(url).matches()) { |
|
| 47 | - return MimeType.youtube; |
|
| 48 | - } else if (VIMEO_REGEX.matcher(url).matches()) { |
|
| 49 | - return MimeType.vimeo; |
|
| 50 | - } else if (MP4_REGEX.matcher(url).matches()) { |
|
| 51 | - return MimeType.mp4; |
|
| 52 | - } else { |
|
| 53 | - return MimeType.unknown; |
|
| 54 | - } |
|
| 55 | - } |
|
| 56 | - |
|
| 57 | - public static Pair<Integer, Integer> getImageDimensions(URL imageURL) { |
|
| 58 | - Future<Pair<Integer, Integer>> imageSizeCalculator = getOrCreateImageSizeCalculator(imageURL); |
|
| 59 | - try { |
|
| 60 | - return imageSizeCalculator.get(); |
|
| 61 | - } catch (Exception e) { |
|
| 62 | - return null; |
|
| 63 | - } |
|
| 64 | - } |
|
| 65 | - |
|
| 66 | - private static final ExecutorService executor = Executors.newCachedThreadPool(); |
|
| 67 | - |
|
| 68 | - private static Future<Pair<Integer, Integer>> getOrCreateImageSizeCalculator(final URL imageURL) { |
|
| 69 | - Future<Pair<Integer, Integer>> imageSizeFetcher = executor.submit(new Callable<Pair<Integer, Integer>>() { |
|
| 70 | - @Override |
|
| 71 | - public Pair<Integer, Integer> call() throws IOException { |
|
| 72 | - Pair<Integer, Integer> result = null; |
|
| 73 | - ImageInputStream in = null; |
|
| 74 | - try { |
|
| 75 | - URLConnection conn = imageURL.openConnection(); |
|
| 76 | - in = ImageIO.createImageInputStream(conn.getInputStream()); |
|
| 77 | - final Iterator<ImageReader> readers = ImageIO.getImageReaders(in); |
|
| 78 | - if (readers.hasNext()) { |
|
| 79 | - ImageReader reader = readers.next(); |
|
| 80 | - try { |
|
| 81 | - reader.setInput(in); |
|
| 82 | - result = new Pair<>(reader.getWidth(0), reader.getHeight(0)); |
|
| 83 | - } finally { |
|
| 84 | - reader.dispose(); |
|
| 85 | - } |
|
| 86 | - } |
|
| 87 | - } catch (IOException ioe) { |
|
| 88 | - logger.log(Level.SEVERE, "Stale image URL: "+imageURL, ioe); |
|
| 89 | - throw ioe; |
|
| 90 | - } finally { |
|
| 91 | - if (in != null) { |
|
| 92 | - in.close(); |
|
| 93 | - } |
|
| 94 | - } |
|
| 95 | - return result; |
|
| 96 | - } |
|
| 97 | - }); |
|
| 98 | - return imageSizeFetcher; |
|
| 99 | - } |
|
| 100 | - |
|
| 101 | - public static Util.Pair<Integer, Integer> fitImageSizeToBox(int boxWidth, int boxHeight, int imageWidth, int imageHeight, boolean neverScaleUp) { |
|
| 102 | - double scale = Math.min((double) boxWidth / (double) imageWidth, (double) boxHeight / (double) imageHeight); |
|
| 103 | - |
|
| 104 | - int h = (int) (!neverScaleUp || scale < 1.0 ? scale * imageHeight : imageHeight); |
|
| 105 | - int w = (int) (!neverScaleUp || scale < 1.0 ? scale * imageWidth : imageWidth); |
|
| 106 | - |
|
| 107 | - return new Util.Pair<Integer, Integer>(w,h); |
|
| 108 | - } |
|
| 109 | -} |
java/com.sap.sse.common/src/com/sap/sse/common/media/VideoDescriptor.java
| ... | ... | @@ -1,11 +0,0 @@ |
| 1 | -package com.sap.sse.common.media; |
|
| 2 | - |
|
| 3 | -import java.net.URL; |
|
| 4 | - |
|
| 5 | -public interface VideoDescriptor extends MediaDescriptor { |
|
| 6 | - Integer getLengthInSeconds(); |
|
| 7 | - void setLengthInSeconds(Integer lengthInSeconds); |
|
| 8 | - |
|
| 9 | - URL getThumbnailURL(); |
|
| 10 | - void setThumbnailURL(URL thumbnailURL); |
|
| 11 | -} |
java/com.sap.sse.common/src/com/sap/sse/common/media/VideoDescriptorImpl.java
| ... | ... | @@ -1,40 +0,0 @@ |
| 1 | -package com.sap.sse.common.media; |
|
| 2 | - |
|
| 3 | -import java.net.URL; |
|
| 4 | - |
|
| 5 | -import com.sap.sse.common.TimePoint; |
|
| 6 | - |
|
| 7 | -public class VideoDescriptorImpl extends AbstractMediaDescriptor implements VideoDescriptor { |
|
| 8 | - private static final long serialVersionUID = 2651747912466590862L; |
|
| 9 | - |
|
| 10 | - private Integer lengthInSeconds; |
|
| 11 | - |
|
| 12 | - /** |
|
| 13 | - * URL to thumbnail image. This information works as override for youtube videos or as missing thumbnail |
|
| 14 | - * information for other formats. It can be either a link to thumbnail or even be a data/url contaning the base64 |
|
| 15 | - * encoded image. |
|
| 16 | - */ |
|
| 17 | - private URL thumbnailURL; |
|
| 18 | - |
|
| 19 | - public VideoDescriptorImpl(URL url, MimeType mimeType, TimePoint createdAtDate) { |
|
| 20 | - super(url, mimeType, createdAtDate); |
|
| 21 | - } |
|
| 22 | - |
|
| 23 | - @Override |
|
| 24 | - public Integer getLengthInSeconds() { |
|
| 25 | - return lengthInSeconds; |
|
| 26 | - } |
|
| 27 | - |
|
| 28 | - public void setLengthInSeconds(Integer lengthInSeconds) { |
|
| 29 | - this.lengthInSeconds = lengthInSeconds; |
|
| 30 | - } |
|
| 31 | - |
|
| 32 | - @Override |
|
| 33 | - public URL getThumbnailURL() { |
|
| 34 | - return thumbnailURL; |
|
| 35 | - } |
|
| 36 | - |
|
| 37 | - public void setThumbnailURL(URL thumbnailURL) { |
|
| 38 | - this.thumbnailURL = thumbnailURL; |
|
| 39 | - } |
|
| 40 | -} |
java/com.sap.sse.common/src/com/sap/sse/common/media/WithImages.java
| ... | ... | @@ -1,25 +0,0 @@ |
| 1 | -package com.sap.sse.common.media; |
|
| 2 | - |
|
| 3 | -public interface WithImages { |
|
| 4 | - /** |
|
| 5 | - * Returns a non-<code>null</code> live but unmodifiable collection of image resources that can be |
|
| 6 | - * used to represent the event, e.g., on a web page. |
|
| 7 | - * |
|
| 8 | - * @return a non-<code>null</code> value which may be empty |
|
| 9 | - */ |
|
| 10 | - Iterable<ImageDescriptor> getImages(); |
|
| 11 | - |
|
| 12 | - /** |
|
| 13 | - * Replaces the {@link #getImages() current contents of the image sequence by the images in |
|
| 14 | - * <code>images</code>. |
|
| 15 | - * |
|
| 16 | - * @param images |
|
| 17 | - * if <code>null</code>, the internal sequence of images is cleared but remains valid (non- |
|
| 18 | - * <code>null</code>) |
|
| 19 | - */ |
|
| 20 | - void setImages(Iterable<ImageDescriptor> images); |
|
| 21 | - |
|
| 22 | - void addImage(ImageDescriptor image); |
|
| 23 | - |
|
| 24 | - void removeImage(ImageDescriptor image); |
|
| 25 | -} |
java/com.sap.sse.common/src/com/sap/sse/common/media/WithMedia.java
| ... | ... | @@ -1,12 +0,0 @@ |
| 1 | -package com.sap.sse.common.media; |
|
| 2 | - |
|
| 3 | -import java.util.List; |
|
| 4 | - |
|
| 5 | -public interface WithMedia extends WithImages, WithVideos { |
|
| 6 | - ImageDescriptor findImageWithTag(String tagName); |
|
| 7 | - List<ImageDescriptor> findImagesWithTag(String tagName); |
|
| 8 | - boolean hasImageWithTag(String tagName); |
|
| 9 | - |
|
| 10 | - VideoDescriptor findVideoWithTag(String tagName); |
|
| 11 | - List<VideoDescriptor> findVideosWithTag(String tagName); |
|
| 12 | -} |
java/com.sap.sse.common/src/com/sap/sse/common/media/WithVideos.java
| ... | ... | @@ -1,26 +0,0 @@ |
| 1 | -package com.sap.sse.common.media; |
|
| 2 | - |
|
| 3 | -public interface WithVideos { |
|
| 4 | - |
|
| 5 | - /** |
|
| 6 | - * Returns a non-<code>null</code> live but unmodifiable collection of video resources that can be |
|
| 7 | - * used to represent the event, e.g., on a web page. |
|
| 8 | - * |
|
| 9 | - * @return a non-<code>null</code> value which may be empty |
|
| 10 | - */ |
|
| 11 | - Iterable<VideoDescriptor> getVideos(); |
|
| 12 | - |
|
| 13 | - /** |
|
| 14 | - * Replaces the {@link #getVideos() current contents of the video sequence by the videos in |
|
| 15 | - * <code>videos</code>. |
|
| 16 | - * |
|
| 17 | - * @param videos |
|
| 18 | - * if <code>null</code>, the internal sequence of videos is cleared but remains valid (non- |
|
| 19 | - * <code>null</code>) |
|
| 20 | - */ |
|
| 21 | - void setVideos(Iterable<VideoDescriptor> videos); |
|
| 22 | - |
|
| 23 | - void addVideo(VideoDescriptor video); |
|
| 24 | - |
|
| 25 | - void removeVideo(VideoDescriptor video); |
|
| 26 | -} |
java/com.sap.sse.datamining.annotations/pom.xml
| ... | ... | @@ -1,31 +1,31 @@ |
| 1 | 1 | <?xml version="1.0" encoding="UTF-8"?> |
| 2 | 2 | <project |
| 3 | - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" |
|
| 4 | - xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
|
| 5 | - <modelVersion>4.0.0</modelVersion> |
|
| 6 | - <parent> |
|
| 7 | - <artifactId>root</artifactId> |
|
| 8 | - <groupId>com.sap.sailing</groupId> |
|
| 9 | - <version>1.0.0-SNAPSHOT</version> |
|
| 10 | - </parent> |
|
| 11 | - <artifactId>com.sap.sse.datamining.annotations</artifactId> |
|
| 12 | - <packaging>eclipse-plugin</packaging> |
|
| 13 | - <build> |
|
| 14 | - <plugins> |
|
| 15 | - <plugin> |
|
| 16 | - <groupId>org.eclipse.tycho</groupId> |
|
| 17 | - <artifactId>tycho-source-plugin</artifactId> |
|
| 18 | - <version>${tycho-version}</version> |
|
| 19 | - <executions> |
|
| 20 | - <execution> |
|
| 21 | - <id>plugin-source</id> |
|
| 22 | - <phase>generate-sources</phase> |
|
| 23 | - <goals> |
|
| 24 | - <goal>plugin-source</goal> |
|
| 25 | - </goals> |
|
| 26 | - </execution> |
|
| 27 | - </executions> |
|
| 28 | - </plugin> |
|
| 29 | - </plugins> |
|
| 30 | - </build> |
|
| 3 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" |
|
| 4 | + xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
|
| 5 | + <modelVersion>4.0.0</modelVersion> |
|
| 6 | + <parent> |
|
| 7 | + <artifactId>root</artifactId> |
|
| 8 | + <groupId>com.sap.sailing</groupId> |
|
| 9 | + <version>1.0.0-SNAPSHOT</version> |
|
| 10 | + </parent> |
|
| 11 | + <artifactId>com.sap.sse.datamining.annotations</artifactId> |
|
| 12 | + <packaging>eclipse-plugin</packaging> |
|
| 13 | + <build> |
|
| 14 | + <plugins> |
|
| 15 | + <plugin> |
|
| 16 | + <groupId>org.eclipse.tycho</groupId> |
|
| 17 | + <artifactId>tycho-source-plugin</artifactId> |
|
| 18 | + <version>${tycho-version}</version> |
|
| 19 | + <executions> |
|
| 20 | + <execution> |
|
| 21 | + <id>plugin-source</id> |
|
| 22 | + <phase>generate-sources</phase> |
|
| 23 | + <goals> |
|
| 24 | + <goal>plugin-source</goal> |
|
| 25 | + </goals> |
|
| 26 | + </execution> |
|
| 27 | + </executions> |
|
| 28 | + </plugin> |
|
| 29 | + </plugins> |
|
| 30 | + </build> |
|
| 31 | 31 | </project> |
java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/IntegerBox.java
| ... | ... | @@ -7,6 +7,9 @@ import com.google.gwt.text.client.IntegerParser; |
| 7 | 7 | import com.google.gwt.text.shared.Renderer; |
| 8 | 8 | import com.google.gwt.user.client.ui.ValueBox; |
| 9 | 9 | |
| 10 | +/** |
|
| 11 | + * Renders integers in their box without any separators, non-localized, plain. |
|
| 12 | + */ |
|
| 10 | 13 | public class IntegerBox extends ValueBox<Integer> { |
| 11 | 14 | private static final Renderer<Integer> RENDERER = new Renderer<Integer>() { |
| 12 | 15 | public String render(Integer object) { |
java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/SliderBar.java
| ... | ... | @@ -1,1256 +1,1256 @@ |
| 1 | -package com.sap.sse.gwt.client.controls.slider;
|
|
| 2 | -
|
|
| 3 | -/*
|
|
| 4 | - * Copyright 2008 Google Inc.
|
|
| 5 | - *
|
|
| 6 | - * Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
| 7 | - * use this file except in compliance with the License. You may obtain a copy of
|
|
| 8 | - * the License at
|
|
| 9 | - *
|
|
| 10 | - * http://www.apache.org/licenses/LICENSE-2.0
|
|
| 11 | - *
|
|
| 12 | - * Unless required by applicable law or agreed to in writing, software
|
|
| 13 | - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
| 14 | - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
| 15 | - * License for the specific language governing permissions and limitations under
|
|
| 16 | - * the License.
|
|
| 17 | - */
|
|
| 18 | -
|
|
| 19 | -import java.util.ArrayList;
|
|
| 20 | -import java.util.Iterator;
|
|
| 21 | -import java.util.List;
|
|
| 22 | -
|
|
| 23 | -import com.google.gwt.core.client.GWT;
|
|
| 24 | -import com.google.gwt.core.client.Scheduler;
|
|
| 25 | -import com.google.gwt.core.client.Scheduler.ScheduledCommand;
|
|
| 26 | -import com.google.gwt.dom.client.Element;
|
|
| 27 | -import com.google.gwt.dom.client.Style.Display;
|
|
| 28 | -import com.google.gwt.dom.client.Style.Position;
|
|
| 29 | -import com.google.gwt.dom.client.Style.Unit;
|
|
| 30 | -import com.google.gwt.dom.client.Style.Visibility;
|
|
| 31 | -import com.google.gwt.event.dom.client.KeyCodes;
|
|
| 32 | -import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
|
|
| 33 | -import com.google.gwt.event.logical.shared.ValueChangeEvent;
|
|
| 34 | -import com.google.gwt.event.logical.shared.ValueChangeHandler;
|
|
| 35 | -import com.google.gwt.event.shared.HandlerRegistration;
|
|
| 36 | -import com.google.gwt.resources.client.ClientBundle;
|
|
| 37 | -import com.google.gwt.resources.client.CssResource;
|
|
| 38 | -import com.google.gwt.resources.client.CssResource.NotStrict;
|
|
| 39 | -import com.google.gwt.resources.client.ImageResource;
|
|
| 40 | -import com.google.gwt.user.client.DOM;
|
|
| 41 | -import com.google.gwt.user.client.Event;
|
|
| 42 | -import com.google.gwt.user.client.Timer;
|
|
| 43 | -import com.google.gwt.user.client.Window;
|
|
| 44 | -import com.google.gwt.user.client.ui.FocusPanel;
|
|
| 45 | -import com.google.gwt.user.client.ui.HasValue;
|
|
| 46 | -import com.google.gwt.user.client.ui.Image;
|
|
| 47 | -import com.google.gwt.user.client.ui.RequiresResize;
|
|
| 48 | -import com.sap.sse.common.Util;
|
|
| 49 | -
|
|
| 50 | -/**
|
|
| 51 | - * A widget that allows the user to select a value within a range of possible values using a sliding bar that responds
|
|
| 52 | - * to mouse events.
|
|
| 53 | - *
|
|
| 54 | - * <h3>Keyboard Events</h3>
|
|
| 55 | - * <p>
|
|
| 56 | - * SliderBar listens for the following key events. Holding down a key will repeat the action until the key is released.
|
|
| 57 | - * <ul class='css'>
|
|
| 58 | - * <li>left arrow - shift left one step</li>
|
|
| 59 | - * <li>right arrow - shift right one step</li>
|
|
| 60 | - * <li>ctrl+left arrow - jump left 10% of the distance</li>
|
|
| 61 | - * <li>ctrl+right arrow - jump right 10% of the distance</li>
|
|
| 62 | - * <li>home - jump to min value</li>
|
|
| 63 | - * <li>end - jump to max value</li>
|
|
| 64 | - * <li>space - jump to middle value</li>
|
|
| 65 | - * </ul>
|
|
| 66 | - * </p>
|
|
| 67 | - *
|
|
| 68 | - * <h3>CSS Style Rules</h3>
|
|
| 69 | - * <ul class='css'>
|
|
| 70 | - * <li>.gwt-SliderBar-shell { primary style }</li>
|
|
| 71 | - * <li>.gwt-SliderBar-shell-focused { primary style when focused }</li>
|
|
| 72 | - * <li>.gwt-SliderBar-shell gwt-SliderBar-line { the line that the knob moves along }</li>
|
|
| 73 | - * <li>.gwt-SliderBar-shell gwt-SliderBar-line-sliding { the line that the knob moves along when sliding }</li>
|
|
| 74 | - * <li>.gwt-SliderBar-shell .gwt-SliderBar-knob { the sliding knob }</li>
|
|
| 75 | - * <li>.gwt-SliderBar-shell .gwt-SliderBar-knob-sliding { the sliding knob when sliding }</li>
|
|
| 76 | - * <li>
|
|
| 77 | - * .gwt-SliderBar-shell .gwt-SliderBar-tick { the ticks along the line }</li>
|
|
| 78 | - * <li>.gwt-SliderBar-shell .gwt-SliderBar-ticklabel { the text labels along the line }</li>
|
|
| 79 | - * </ul>
|
|
| 80 | - */
|
|
| 81 | -public class SliderBar extends FocusPanel implements RequiresResize, HasValue<Double>, HasValueChangeHandlers<Double> {
|
|
| 82 | - /**
|
|
| 83 | - * A formatter used to format the labels displayed in the widget.
|
|
| 84 | - */
|
|
| 85 | - public static interface LabelFormatter {
|
|
| 86 | - /**
|
|
| 87 | - * Generate the text to display in each label based on the label's value.
|
|
| 88 | - *
|
|
| 89 | - * Override this method to change the text displayed within the SliderBar.
|
|
| 90 | - *
|
|
| 91 | - * @param slider
|
|
| 92 | - * the Slider bar
|
|
| 93 | - * @param value
|
|
| 94 | - * the value the label displays
|
|
| 95 | - * @return the text to display for the label
|
|
| 96 | - */
|
|
| 97 | - String formatLabel(SliderBar slider, Double value, Double previousValue);
|
|
| 98 | - }
|
|
| 99 | -
|
|
| 100 | - /**
|
|
| 101 | - * A {@link ClientBundle} that provides images for {@link SliderBar}.
|
|
| 102 | - */
|
|
| 103 | - public static interface SliderBarImages extends ClientBundle {
|
|
| 104 | - public static final SliderBarImages INSTANCE = GWT.create(SliderBarImages.class);
|
|
| 105 | -
|
|
| 106 | - /**
|
|
| 107 | - * An image used for the sliding knob.
|
|
| 108 | - *
|
|
| 109 | - * @return a prototype of this image
|
|
| 110 | - */
|
|
| 111 | - @Source("slider_blue.png")
|
|
| 112 | - ImageResource slider();
|
|
| 113 | -
|
|
| 114 | - /**
|
|
| 115 | - * An image used for the disabled sliding knob.
|
|
| 116 | - *
|
|
| 117 | - * @return a prototype of this image
|
|
| 118 | - */
|
|
| 119 | - @Source("slider_blue.png")
|
|
| 120 | - ImageResource sliderDisabled();
|
|
| 121 | -
|
|
| 122 | - /**
|
|
| 123 | - * An image used for the sliding knob while sliding.
|
|
| 124 | - *
|
|
| 125 | - * @return a prototype of this image
|
|
| 126 | - */
|
|
| 127 | - @Source("slider_blue.png")
|
|
| 128 | - ImageResource sliderSliding();
|
|
| 129 | -
|
|
| 130 | - @NotStrict
|
|
| 131 | - @Source("SliderBar.css")
|
|
| 132 | - CssResource sliderBarCss();
|
|
| 133 | - }
|
|
| 134 | -
|
|
| 135 | - /**
|
|
| 136 | - * The timer used to continue to shift the knob as the user holds down one of the left/right arrow keys. Only IE
|
|
| 137 | - * auto-repeats, so we just keep catching the events.
|
|
| 138 | - */
|
|
| 139 | - private class KeyTimer extends Timer {
|
|
| 140 | - /**
|
|
| 141 | - * A bit indicating that this is the first run.
|
|
| 142 | - */
|
|
| 143 | - private boolean firstRun = true;
|
|
| 144 | -
|
|
| 145 | - /**
|
|
| 146 | - * The delay between shifts, which shortens as the user holds down the button.
|
|
| 147 | - */
|
|
| 148 | - private int repeatDelay = 30;
|
|
| 149 | -
|
|
| 150 | - /**
|
|
| 151 | - * A bit indicating whether we are shifting to a higher or lower value.
|
|
| 152 | - */
|
|
| 153 | - private boolean shiftRight = false;
|
|
| 154 | -
|
|
| 155 | - /**
|
|
| 156 | - * The number of steps to shift with each press.
|
|
| 157 | - */
|
|
| 158 | - private int multiplier = 1;
|
|
| 159 | -
|
|
| 160 | - /**
|
|
| 161 | - * This method will be called when a timer fires. Override it to implement the timer's logic.
|
|
| 162 | - */
|
|
| 163 | - @Override
|
|
| 164 | - public void run() {
|
|
| 165 | - // Highlight the knob on first run
|
|
| 166 | - if (firstRun) {
|
|
| 167 | - firstRun = false;
|
|
| 168 | - startSliding(true, false);
|
|
| 169 | - }
|
|
| 170 | -
|
|
| 171 | - // Slide the slider bar
|
|
| 172 | - if (shiftRight) {
|
|
| 173 | - setCurrentValue(curValue + multiplier * stepSize);
|
|
| 174 | - } else {
|
|
| 175 | - setCurrentValue(curValue - multiplier * stepSize);
|
|
| 176 | - }
|
|
| 177 | -
|
|
| 178 | - // Repeat this timer until cancelled by keyup event
|
|
| 179 | - schedule(repeatDelay);
|
|
| 180 | - }
|
|
| 181 | -
|
|
| 182 | - /**
|
|
| 183 | - * Schedules a timer to elapse in the future.
|
|
| 184 | - *
|
|
| 185 | - * @param delayMillis
|
|
| 186 | - * how long to wait before the timer elapses, in milliseconds
|
|
| 187 | - * @param shiftRight
|
|
| 188 | - * whether to shift up or not
|
|
| 189 | - * @param multiplier
|
|
| 190 | - * the number of steps to shift
|
|
| 191 | - */
|
|
| 192 | - public void schedule(int delayMillis, boolean shiftRight, int multiplier) {
|
|
| 193 | - firstRun = true;
|
|
| 194 | - this.shiftRight = shiftRight;
|
|
| 195 | - this.multiplier = multiplier;
|
|
| 196 | - super.schedule(delayMillis);
|
|
| 197 | - }
|
|
| 198 | - }
|
|
| 199 | -
|
|
| 200 | - /**
|
|
| 201 | - * The current value.
|
|
| 202 | - */
|
|
| 203 | - protected Double curValue;
|
|
| 204 | -
|
|
| 205 | - /**
|
|
| 206 | - * The knob that slides across the line.
|
|
| 207 | - */
|
|
| 208 | - protected Image knobImage = new Image();
|
|
| 209 | -
|
|
| 210 | - /**
|
|
| 211 | - * The timer used to continue to shift the knob if the user holds down a key.
|
|
| 212 | - */
|
|
| 213 | - private KeyTimer keyTimer = new KeyTimer();
|
|
| 214 | -
|
|
| 215 | - /**
|
|
| 216 | - * The elements used to display labels above the ticks.
|
|
| 217 | - */
|
|
| 218 | - protected List<Element> tickLabelElements = new ArrayList<Element>();
|
|
| 219 | -
|
|
| 220 | - /**
|
|
| 221 | - * The elements used to display the marker labels.
|
|
| 222 | - */
|
|
| 223 | - protected List<Element> markerLabelElements = new ArrayList<Element>();
|
|
| 224 | -
|
|
| 225 | - /**
|
|
| 226 | - * The formatter used to generate label text.
|
|
| 227 | - */
|
|
| 228 | - protected LabelFormatter tickLabelFormatter;
|
|
| 229 | -
|
|
| 230 | - /**
|
|
| 231 | - * The line that the knob moves over.
|
|
| 232 | - */
|
|
| 233 | - protected Element lineElement;
|
|
| 234 | -
|
|
| 235 | - /**
|
|
| 236 | - * The offset between the edge of the shell and the line.
|
|
| 237 | - */
|
|
| 238 | - protected int lineLeftOffset = 0;
|
|
| 239 | -
|
|
| 240 | - /**
|
|
| 241 | - * The maximum slider value.
|
|
| 242 | - */
|
|
| 243 | - protected Double maxValue;
|
|
| 244 | -
|
|
| 245 | - /**
|
|
| 246 | - * The minimum slider value.
|
|
| 247 | - */
|
|
| 248 | - protected Double minValue;
|
|
| 249 | -
|
|
| 250 | - /**
|
|
| 251 | - * The number of labels to show.
|
|
| 252 | - */
|
|
| 253 | - private int numTickLabels = 0;
|
|
| 254 | -
|
|
| 255 | - /**
|
|
| 256 | - * The number of tick marks to show.
|
|
| 257 | - */
|
|
| 258 | - protected int numTicks = 0;
|
|
| 259 | -
|
|
| 260 | - /**
|
|
| 261 | - * A bit indicating whether or not we are currently sliding the slider bar due to keyboard events.
|
|
| 262 | - */
|
|
| 263 | - private boolean slidingKeyboard = false;
|
|
| 264 | -
|
|
| 265 | - /**
|
|
| 266 | - * A bit indicating whether or not we are currently sliding the slider bar due to mouse events.
|
|
| 267 | - */
|
|
| 268 | - private boolean slidingMouse = false;
|
|
| 269 | -
|
|
| 270 | - /**
|
|
| 271 | - * A bit indicating whether or not the slider is enabled
|
|
| 272 | - */
|
|
| 273 | - protected boolean enabled = true;
|
|
| 274 | -
|
|
| 275 | - /**
|
|
| 276 | - * The images used with the sliding bar.
|
|
| 277 | - */
|
|
| 278 | - private SliderBarImages images;
|
|
| 279 | -
|
|
| 280 | - /**
|
|
| 281 | - * The size of the increments between knob positions.
|
|
| 282 | - */
|
|
| 283 | - protected double stepSize;
|
|
| 284 | -
|
|
| 285 | - /**
|
|
| 286 | - * The elements used to display tick marks, which are the vertical lines along the slider bar.
|
|
| 287 | - */
|
|
| 288 | - protected List<Element> tickElements = new ArrayList<Element>();
|
|
| 289 | -
|
|
| 290 | - /**
|
|
| 291 | - * The elements used to display additional markers on the slider bar.
|
|
| 292 | - */
|
|
| 293 | - protected List<Element> markerElements = new ArrayList<Element>();
|
|
| 294 | -
|
|
| 295 | - private List<Marker> markers = new ArrayList<Marker>();
|
|
| 296 | -
|
|
| 297 | - private class Marker {
|
|
| 298 | - String name;
|
|
| 299 | - Double position;
|
|
| 300 | -
|
|
| 301 | - public Marker(String name, Double position) {
|
|
| 302 | - super();
|
|
| 303 | - this.name = name;
|
|
| 304 | - this.position = position;
|
|
| 305 | - }
|
|
| 306 | - }
|
|
| 307 | -
|
|
| 308 | - /**
|
|
| 309 | - * Create a slider bar.
|
|
| 310 | - */
|
|
| 311 | - public SliderBar() {
|
|
| 312 | - this(null, null, null);
|
|
| 313 | - }
|
|
| 314 | -
|
|
| 315 | - /**
|
|
| 316 | - * Create a slider bar.
|
|
| 317 | - *
|
|
| 318 | - * @param minValue
|
|
| 319 | - * the minimum value in the range
|
|
| 320 | - * @param maxValue
|
|
| 321 | - * the maximum value in the range
|
|
| 322 | - */
|
|
| 323 | - public SliderBar(double minValue, double maxValue) {
|
|
| 324 | - this(minValue, maxValue, null);
|
|
| 325 | - }
|
|
| 326 | -
|
|
| 327 | - /**
|
|
| 328 | - * Create a slider bar.
|
|
| 329 | - *
|
|
| 330 | - * @param minValue
|
|
| 331 | - * the minimum value in the range
|
|
| 332 | - * @param maxValue
|
|
| 333 | - * the maximum value in the range
|
|
| 334 | - * @param tickLabelFormatter
|
|
| 335 | - * the label formatter
|
|
| 336 | - */
|
|
| 337 | - public SliderBar(Double minValue, Double maxValue, LabelFormatter tickLabelFormatter) {
|
|
| 338 | - this(minValue, maxValue, tickLabelFormatter, SliderBarImages.INSTANCE);
|
|
| 339 | - }
|
|
| 340 | -
|
|
| 341 | - /**
|
|
| 342 | - * Create a slider bar.
|
|
| 343 | - *
|
|
| 344 | - * @param minValue
|
|
| 345 | - * the minimum value in the range
|
|
| 346 | - * @param maxValue
|
|
| 347 | - * the maximum value in the range
|
|
| 348 | - * @param tickLabelFormatter
|
|
| 349 | - * the label formatter
|
|
| 350 | - * @param images
|
|
| 351 | - * the images to use for the slider
|
|
| 352 | - */
|
|
| 353 | - public SliderBar(Double minValue, Double maxValue, LabelFormatter tickLabelFormatter, SliderBarImages images) {
|
|
| 354 | - super();
|
|
| 355 | - images.sliderBarCss().ensureInjected();
|
|
| 356 | - this.minValue = minValue;
|
|
| 357 | - this.maxValue = maxValue;
|
|
| 358 | - this.images = images;
|
|
| 359 | - setLabelFormatter(tickLabelFormatter);
|
|
| 360 | -
|
|
| 361 | - // Create the outer shell
|
|
| 362 | - getElement().getStyle().setPosition(Position.RELATIVE);
|
|
| 363 | - setStyleName("gwt-SliderBar-shell");
|
|
| 364 | -
|
|
| 365 | - // Create the line
|
|
| 366 | - lineElement = DOM.createDiv();
|
|
| 367 | - DOM.appendChild(getElement(), lineElement);
|
|
| 368 | - lineElement.getStyle().setPosition(Position.ABSOLUTE);
|
|
| 369 | - lineElement.setPropertyString("className", "gwt-SliderBar-line");
|
|
| 370 | -
|
|
| 371 | - // Create the knob
|
|
| 372 | - knobImage.setResource(images.slider());
|
|
| 373 | - Element knobElement = knobImage.getElement();
|
|
| 374 | - DOM.appendChild(getElement(), knobElement);
|
|
| 375 | - knobElement.getStyle().setPosition(Position.ABSOLUTE);
|
|
| 376 | - knobElement.setPropertyString("className", "gwt-SliderBar-knob");
|
|
| 377 | -
|
|
| 378 | - sinkEvents(Event.MOUSEEVENTS | Event.KEYEVENTS | Event.FOCUSEVENTS);
|
|
| 379 | -
|
|
| 380 | - // workaround to render properly when parent Widget does not
|
|
| 381 | - // implement ProvidesResize since DOM doesn't provide element
|
|
| 382 | - // height and width until onModuleLoad() finishes.
|
|
| 383 | - Scheduler.get().scheduleDeferred(new ScheduledCommand() {
|
|
| 384 | - @Override
|
|
| 385 | - public void execute() {
|
|
| 386 | - onResize();
|
|
| 387 | - }
|
|
| 388 | - });
|
|
| 389 | - }
|
|
| 390 | -
|
|
| 391 | - public boolean isMinMaxInitialized() {
|
|
| 392 | - return minValue != null && maxValue != null;
|
|
| 393 | - }
|
|
| 394 | -
|
|
| 395 | - public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Double> handler) {
|
|
| 396 | - return addHandler(handler, ValueChangeEvent.getType());
|
|
| 397 | - }
|
|
| 398 | -
|
|
| 399 | - /**
|
|
| 400 | - * Return the current value.
|
|
| 401 | - *
|
|
| 402 | - * @return the current value
|
|
| 403 | - */
|
|
| 404 | - public Double getCurrentValue() {
|
|
| 405 | - return curValue;
|
|
| 406 | - }
|
|
| 407 | -
|
|
| 408 | - /**
|
|
| 409 | - * Return the label formatter.
|
|
| 410 | - *
|
|
| 411 | - * @return the label formatter
|
|
| 412 | - */
|
|
| 413 | - public LabelFormatter getLabelFormatter() {
|
|
| 414 | - return tickLabelFormatter;
|
|
| 415 | - }
|
|
| 416 | -
|
|
| 417 | - /**
|
|
| 418 | - * Return the max value.
|
|
| 419 | - *
|
|
| 420 | - * @return the max value
|
|
| 421 | - */
|
|
| 422 | - public Double getMaxValue() {
|
|
| 423 | - return maxValue;
|
|
| 424 | - }
|
|
| 425 | -
|
|
| 426 | - /**
|
|
| 427 | - * Return the minimum value.
|
|
| 428 | - *
|
|
| 429 | - * @return the minimum value
|
|
| 430 | - */
|
|
| 431 | - public Double getMinValue() {
|
|
| 432 | - return minValue;
|
|
| 433 | - }
|
|
| 434 | -
|
|
| 435 | - /**
|
|
| 436 | - * Return the number of labels.
|
|
| 437 | - *
|
|
| 438 | - * @return the number of labels
|
|
| 439 | - */
|
|
| 440 | - public int getNumLabels() {
|
|
| 441 | - return numTickLabels;
|
|
| 442 | - }
|
|
| 443 | -
|
|
| 444 | - /**
|
|
| 445 | - * Return the number of ticks.
|
|
| 446 | - *
|
|
| 447 | - * @return the number of ticks
|
|
| 448 | - */
|
|
| 449 | - public int getNumTicks() {
|
|
| 450 | - return numTicks;
|
|
| 451 | - }
|
|
| 452 | -
|
|
| 453 | - /**
|
|
| 454 | - * Return the step size.
|
|
| 455 | - *
|
|
| 456 | - * @return the step size
|
|
| 457 | - */
|
|
| 458 | - public double getStepSize() {
|
|
| 459 | - return stepSize;
|
|
| 460 | - }
|
|
| 461 | -
|
|
| 462 | - /**
|
|
| 463 | - * Return the total range between the minimum and maximum values.
|
|
| 464 | - *
|
|
| 465 | - * @return the total range
|
|
| 466 | - */
|
|
| 467 | - public double getTotalRange() {
|
|
| 468 | - if (minValue == null || maxValue == null || minValue > maxValue) {
|
|
| 469 | - return 0;
|
|
| 470 | - } else {
|
|
| 471 | - return maxValue - minValue;
|
|
| 472 | - }
|
|
| 473 | - }
|
|
| 474 | -
|
|
| 475 | - public Double getValue() {
|
|
| 476 | - return curValue;
|
|
| 477 | - }
|
|
| 478 | -
|
|
| 479 | - /**
|
|
| 480 | - * @return Gets whether this widget is enabled
|
|
| 481 | - */
|
|
| 482 | - public boolean isEnabled() {
|
|
| 483 | - return enabled;
|
|
| 484 | - }
|
|
| 485 | -
|
|
| 486 | - /**
|
|
| 487 | - * Listen for events that will move the knob.
|
|
| 488 | - *
|
|
| 489 | - * @param event
|
|
| 490 | - * the event that occurred
|
|
| 491 | - */
|
|
| 492 | - @Override
|
|
| 493 | - public void onBrowserEvent(Event event) {
|
|
| 494 | - super.onBrowserEvent(event);
|
|
| 495 | - if (enabled) {
|
|
| 496 | - switch (DOM.eventGetType(event)) {
|
|
| 497 | - // Unhighlight and cancel keyboard events
|
|
| 498 | - case Event.ONBLUR:
|
|
| 499 | - keyTimer.cancel();
|
|
| 500 | - if (slidingMouse) {
|
|
| 501 | - DOM.releaseCapture(getElement());
|
|
| 502 | - slidingMouse = false;
|
|
| 503 | - slideKnob(event);
|
|
| 504 | - stopSliding(true, true);
|
|
| 505 | - } else if (slidingKeyboard) {
|
|
| 506 | - slidingKeyboard = false;
|
|
| 507 | - stopSliding(true, true);
|
|
| 508 | - }
|
|
| 509 | - unhighlight();
|
|
| 510 | - break;
|
|
| 511 | -
|
|
| 512 | - // Highlight on focus
|
|
| 513 | - case Event.ONFOCUS:
|
|
| 514 | - highlight();
|
|
| 515 | - break;
|
|
| 516 | -
|
|
| 517 | - // Mousewheel events
|
|
| 518 | - case Event.ONMOUSEWHEEL:
|
|
| 519 | - int velocityY = event.getMouseWheelVelocityY();
|
|
| 520 | - event.preventDefault();
|
|
| 521 | - if (velocityY > 0) {
|
|
| 522 | - shiftRight(1);
|
|
| 523 | - } else {
|
|
| 524 | - shiftLeft(1);
|
|
| 525 | - }
|
|
| 526 | - break;
|
|
| 527 | -
|
|
| 528 | - // Shift left or right on key press
|
|
| 529 | - case Event.ONKEYDOWN:
|
|
| 530 | - if (!slidingKeyboard) {
|
|
| 531 | - int multiplier = 1;
|
|
| 532 | - if (event.getCtrlKey()) {
|
|
| 533 | - multiplier = (int) (getTotalRange() / stepSize / 10);
|
|
| 534 | - }
|
|
| 535 | -
|
|
| 536 | - switch (event.getKeyCode()) {
|
|
| 537 | - case KeyCodes.KEY_HOME:
|
|
| 538 | - event.preventDefault();
|
|
| 539 | - setCurrentValue(minValue);
|
|
| 540 | - break;
|
|
| 541 | - case KeyCodes.KEY_END:
|
|
| 542 | - event.preventDefault();
|
|
| 543 | - setCurrentValue(maxValue);
|
|
| 544 | - break;
|
|
| 545 | - case KeyCodes.KEY_LEFT:
|
|
| 546 | - event.preventDefault();
|
|
| 547 | - slidingKeyboard = true;
|
|
| 548 | - startSliding(false, true);
|
|
| 549 | - shiftLeft(multiplier);
|
|
| 550 | - keyTimer.schedule(400, false, multiplier);
|
|
| 551 | - break;
|
|
| 552 | - case KeyCodes.KEY_RIGHT:
|
|
| 553 | - event.preventDefault();
|
|
| 554 | - slidingKeyboard = true;
|
|
| 555 | - startSliding(false, true);
|
|
| 556 | - shiftRight(multiplier);
|
|
| 557 | - keyTimer.schedule(400, true, multiplier);
|
|
| 558 | - break;
|
|
| 559 | - case 32:
|
|
| 560 | - event.preventDefault();
|
|
| 561 | - setCurrentValue(minValue + getTotalRange() / 2);
|
|
| 562 | - break;
|
|
| 563 | - }
|
|
| 564 | - }
|
|
| 565 | - break;
|
|
| 566 | - // Stop shifting on key up
|
|
| 567 | - case Event.ONKEYUP:
|
|
| 568 | - keyTimer.cancel();
|
|
| 569 | - if (slidingKeyboard) {
|
|
| 570 | - slidingKeyboard = false;
|
|
| 571 | - stopSliding(true, true);
|
|
| 572 | - }
|
|
| 573 | - break;
|
|
| 574 | -
|
|
| 575 | - // Mouse Events
|
|
| 576 | - case Event.ONMOUSEDOWN:
|
|
| 577 | - setFocus(true);
|
|
| 578 | - slidingMouse = true;
|
|
| 579 | - DOM.setCapture(getElement());
|
|
| 580 | - startSliding(true, true);
|
|
| 581 | - event.preventDefault();
|
|
| 582 | - slideKnob(event);
|
|
| 583 | - break;
|
|
| 584 | - case Event.ONMOUSEUP:
|
|
| 585 | - if (slidingMouse) {
|
|
| 586 | - DOM.releaseCapture(getElement());
|
|
| 587 | - slidingMouse = false;
|
|
| 588 | - slideKnob(event);
|
|
| 589 | - stopSliding(true, true);
|
|
| 590 | - }
|
|
| 591 | - break;
|
|
| 592 | - case Event.ONMOUSEMOVE:
|
|
| 593 | - if (slidingMouse) {
|
|
| 594 | - slideKnob(event);
|
|
| 595 | - }
|
|
| 596 | - break;
|
|
| 597 | - }
|
|
| 598 | - }
|
|
| 599 | - }
|
|
| 600 | -
|
|
| 601 | - /**
|
|
| 602 | - * This method is called when the dimensions of the parent element change. Subclasses should override this method as
|
|
| 603 | - * needed.
|
|
| 604 | - *
|
|
| 605 | - * @param width
|
|
| 606 | - * the new client width of the element
|
|
| 607 | - * @param height
|
|
| 608 | - * the new client height of the element
|
|
| 609 | - */
|
|
| 610 | - public void onResize(int width, int height) {
|
|
| 611 | - // Center the line in the shell
|
|
| 612 | - int lineWidth = lineElement.getOffsetWidth();
|
|
| 613 | - lineLeftOffset = (width / 2) - (lineWidth / 2);
|
|
| 614 | - lineElement.getStyle().setLeft(lineLeftOffset, Unit.PX);
|
|
| 615 | -
|
|
| 616 | - // Draw the other components
|
|
| 617 | - drawTickLabels();
|
|
| 618 | - drawTicks();
|
|
| 619 | - drawMarkers();
|
|
| 620 | - drawMarkerLabels();
|
|
| 621 | - drawKnob();
|
|
| 622 | - }
|
|
| 623 | -
|
|
| 624 | - /**
|
|
| 625 | - * Redraw the progress bar when something changes the layout.
|
|
| 626 | - */
|
|
| 627 | - public void redraw() {
|
|
| 628 | - if (isAttached()) {
|
|
| 629 | - int width = getElement().getClientWidth();
|
|
| 630 | - int height = getElement().getClientHeight();
|
|
| 631 | - onResize(width, height);
|
|
| 632 | - }
|
|
| 633 | - }
|
|
| 634 | -
|
|
| 635 | - /**
|
|
| 636 | - * Set the current value and fire the onValueChange event.
|
|
| 637 | - *
|
|
| 638 | - * @param curValue
|
|
| 639 | - * the current value
|
|
| 640 | - */
|
|
| 641 | - public synchronized void setCurrentValue(Double curValue) {
|
|
| 642 | - setCurrentValue(curValue, true);
|
|
| 643 | - }
|
|
| 644 | -
|
|
| 645 | - /**
|
|
| 646 | - * Set the current value and optionally fire the onValueChange event.
|
|
| 647 | - *
|
|
| 648 | - * @param curValue
|
|
| 649 | - * the current value
|
|
| 650 | - * @param fireEvent
|
|
| 651 | - * fire the onValue change event if true
|
|
| 652 | - */
|
|
| 653 | - public synchronized void setCurrentValue(Double curValue, boolean fireEvent) {
|
|
| 654 | - // Confine the value to the range
|
|
| 655 | - if (!isMinMaxInitialized() || curValue == null) {
|
|
| 656 | - return;
|
|
| 657 | - }
|
|
| 658 | - this.curValue = Math.max(minValue, Math.min(maxValue, curValue));
|
|
| 659 | - double remainder = (this.curValue - minValue) % stepSize;
|
|
| 660 | - this.curValue -= remainder;
|
|
| 661 | - // Go to next step if more than halfway there
|
|
| 662 | - if ((remainder > (stepSize / 2)) && ((this.curValue + stepSize) <= maxValue)) {
|
|
| 663 | - this.curValue += stepSize;
|
|
| 664 | - }
|
|
| 665 | - // Redraw the knob
|
|
| 666 | - drawKnob();
|
|
| 667 | - // Fire the ValueChangeEvent if the value actually changed
|
|
| 668 | - if (fireEvent && !curValue.equals(this.curValue)) {
|
|
| 669 | - ValueChangeEvent.fire(this, this.curValue);
|
|
| 670 | - }
|
|
| 671 | - }
|
|
| 672 | -
|
|
| 673 | - /**
|
|
| 674 | - * Sets whether this widget is enabled.
|
|
| 675 | - *
|
|
| 676 | - * @param enabled
|
|
| 677 | - * true to enable the widget, false to disable it
|
|
| 678 | - */
|
|
| 679 | - public void setEnabled(boolean enabled) {
|
|
| 680 | - this.enabled = enabled;
|
|
| 681 | - if (enabled) {
|
|
| 682 | - knobImage.setResource(images.slider());
|
|
| 683 | - lineElement.setPropertyString("className", "gwt-SliderBar-line");
|
|
| 684 | - } else {
|
|
| 685 | - knobImage.setResource(images.sliderDisabled());
|
|
| 686 | - lineElement.setPropertyString("className", "gwt-SliderBar-line gwt-SliderBar-line-disabled");
|
|
| 687 | - }
|
|
| 688 | - redraw();
|
|
| 689 | - }
|
|
| 690 | -
|
|
| 691 | - /**
|
|
| 692 | - * Set the label formatter.
|
|
| 693 | - *
|
|
| 694 | - * @param labelFormatter
|
|
| 695 | - * the label formatter
|
|
| 696 | - */
|
|
| 697 | - public void setLabelFormatter(LabelFormatter labelFormatter) {
|
|
| 698 | - this.tickLabelFormatter = labelFormatter;
|
|
| 699 | - }
|
|
| 700 | -
|
|
| 701 | - /**
|
|
| 702 | - * Set the max value.
|
|
| 703 | - *
|
|
| 704 | - * @param maxValue
|
|
| 705 | - * the current value
|
|
| 706 | - */
|
|
| 707 | - public void setMaxValue(Double maxValue, boolean fireEvent) {
|
|
| 708 | - if (!Util.equalsWithNull(maxValue, this.maxValue)) {
|
|
| 709 | - this.maxValue = maxValue;
|
|
| 710 | - onMinMaxValueChanged(fireEvent);
|
|
| 711 | - }
|
|
| 712 | - }
|
|
| 713 | -
|
|
| 714 | - /**
|
|
| 715 | - * Set the minimum value.
|
|
| 716 | - *
|
|
| 717 | - * @param minValue
|
|
| 718 | - * the current value
|
|
| 719 | - */
|
|
| 720 | - public void setMinValue(Double minValue, boolean fireEvent) {
|
|
| 721 | - if (!Util.equalsWithNull(minValue, this.minValue)) {
|
|
| 722 | - this.minValue = minValue;
|
|
| 723 | - onMinMaxValueChanged(fireEvent);
|
|
| 724 | - }
|
|
| 725 | - }
|
|
| 726 | -
|
|
| 727 | - /**
|
|
| 728 | - * Set the minimum and maximum value
|
|
| 729 | - *
|
|
| 730 | - * @param minValue
|
|
| 731 | - * the current value for min
|
|
| 732 | - * @param maxValue
|
|
| 733 | - * the current valuefor max
|
|
| 734 | - */
|
|
| 735 | - public void setMinAndMaxValue(Double minValue, Double maxValue, boolean fireEvent) {
|
|
| 736 | - boolean changed = false;
|
|
| 737 | - if (!Util.equalsWithNull(minValue, this.minValue)) {
|
|
| 738 | - this.minValue = minValue;
|
|
| 739 | - changed = true;
|
|
| 740 | - }
|
|
| 741 | - if (!Util.equalsWithNull(maxValue, this.maxValue)) {
|
|
| 742 | - this.maxValue = maxValue;
|
|
| 743 | - changed = true;
|
|
| 744 | - }
|
|
| 745 | - if (changed) {
|
|
| 746 | - onMinMaxValueChanged(fireEvent);
|
|
| 747 | - }
|
|
| 748 | - }
|
|
| 749 | -
|
|
| 750 | - /**
|
|
| 751 | - * Handle value changes of min and/or max value
|
|
| 752 | - */
|
|
| 753 | - protected void onMinMaxValueChanged(boolean fireEvent) {
|
|
| 754 | - redraw();
|
|
| 755 | - resetCurrentValue(fireEvent);
|
|
| 756 | - }
|
|
| 757 | -
|
|
| 758 | - /**
|
|
| 759 | - * Set the number of tick labels to show on the line. Tick labels indicate the value of the slider at that point.
|
|
| 760 | - * Use this method to enable tick labels.
|
|
| 761 | - *
|
|
| 762 | - * If you set the number of tick labels equal to the total range divided by the step size, you will get a properly
|
|
| 763 | - * aligned "jumping" effect where the knob jumps between tick labels.
|
|
| 764 | - *
|
|
| 765 | - * Note that the number of tick labels displayed will be one more than the number you specify, so specify 1 labels
|
|
| 766 | - * to show labels on either end of the line. In other words, numTickLabels is really the number of slots between the
|
|
| 767 | - * labels.
|
|
| 768 | - *
|
|
| 769 | - * setNumTickLabels(0) will disable the labels.
|
|
| 770 | - *
|
|
| 771 | - * @param numTickLabels
|
|
| 772 | - * the number of tick labels to show
|
|
| 773 | - */
|
|
| 774 | - public void setNumTickLabels(int numTickLabels) {
|
|
| 775 | - this.numTickLabels = numTickLabels;
|
|
| 776 | - drawTickLabels();
|
|
| 777 | - }
|
|
| 778 | -
|
|
| 779 | - /**
|
|
| 780 | - * Set the number of ticks to show on the line. A tick is a vertical line that represents a division of the overall
|
|
| 781 | - * line. Use this method to enable ticks.
|
|
| 782 | - *
|
|
| 783 | - * If you set the number of ticks equal to the total range divided by the step size, you will get a properly aligned
|
|
| 784 | - * "jumping" effect where the knob jumps between ticks.
|
|
| 785 | - *
|
|
| 786 | - * Note that the number of ticks displayed will be one more than the number you specify, so specify 1 tick to show
|
|
| 787 | - * ticks on either end of the line. In other words, numTicks is really the number of slots between the ticks.
|
|
| 788 | - *
|
|
| 789 | - * setNumTicks(0) will disable ticks.
|
|
| 790 | - *
|
|
| 791 | - * @param numTicks
|
|
| 792 | - * the number of ticks to show
|
|
| 793 | - */
|
|
| 794 | - public void setNumTicks(int numTicks) {
|
|
| 795 | - this.numTicks = numTicks;
|
|
| 796 | - drawTicks();
|
|
| 797 | - }
|
|
| 798 | -
|
|
| 799 | - /**
|
|
| 800 | - * Set the step size.
|
|
| 801 | - *
|
|
| 802 | - * @param stepSize
|
|
| 803 | - * the current value
|
|
| 804 | - */
|
|
| 805 | - public void setStepSize(double stepSize, boolean fireEvent) {
|
|
| 806 | - this.stepSize = stepSize;
|
|
| 807 | - resetCurrentValue(fireEvent);
|
|
| 808 | - }
|
|
| 809 | -
|
|
| 810 | - public void setValue(Double value) {
|
|
| 811 | - setCurrentValue(value, false);
|
|
| 812 | - }
|
|
| 813 | -
|
|
| 814 | - public void setValue(Double value, boolean fireEvent) {
|
|
| 815 | - setCurrentValue(value, fireEvent);
|
|
| 816 | - }
|
|
| 817 | -
|
|
| 818 | - /**
|
|
| 819 | - * Shift to the left (smaller value).
|
|
| 820 | - *
|
|
| 821 | - * @param numSteps
|
|
| 822 | - * the number of steps to shift
|
|
| 823 | - */
|
|
| 824 | - public void shiftLeft(int numSteps) {
|
|
| 825 | - setCurrentValue(getCurrentValue() - numSteps * stepSize);
|
|
| 826 | - }
|
|
| 827 | -
|
|
| 828 | - /**
|
|
| 829 | - * Shift to the right (greater value).
|
|
| 830 | - *
|
|
| 831 | - * @param numSteps
|
|
| 832 | - * the number of steps to shift
|
|
| 833 | - */
|
|
| 834 | - public void shiftRight(int numSteps) {
|
|
| 835 | - setCurrentValue(getCurrentValue() + numSteps * stepSize);
|
|
| 836 | - }
|
|
| 837 | -
|
|
| 838 | - /**
|
|
| 839 | - * Format the label to display above the ticks
|
|
| 840 | - *
|
|
| 841 | - * Override this method in a subclass to customize the format. By default, this method returns the integer portion
|
|
| 842 | - * of the value.
|
|
| 843 | - *
|
|
| 844 | - * @param value
|
|
| 845 | - * the value at the label
|
|
| 846 | - * @return the text to put in the label
|
|
| 847 | - */
|
|
| 848 | - protected String formatTickLabel(Double value, Double previousValue) {
|
|
| 849 | - if (tickLabelFormatter != null) {
|
|
| 850 | - return tickLabelFormatter.formatLabel(this, value, previousValue);
|
|
| 851 | - } else {
|
|
| 852 | - return (int) (10 * value) / 10.0 + "";
|
|
| 853 | - }
|
|
| 854 | - }
|
|
| 855 | -
|
|
| 856 | - /**
|
|
| 857 | - * Get the percentage of the knob's position relative to the size of the line. The return value will be between 0.0
|
|
| 858 | - * and 1.0.
|
|
| 859 | - *
|
|
| 860 | - * @return the current percent complete
|
|
| 861 | - */
|
|
| 862 | - protected double getKnobPercent() {
|
|
| 863 | - // If we have no range
|
|
| 864 | - if (maxValue <= minValue) {
|
|
| 865 | - return 0;
|
|
| 866 | - }
|
|
| 867 | -
|
|
| 868 | - // Calculate the relative progress
|
|
| 869 | - double percent = (curValue - minValue) / (maxValue - minValue);
|
|
| 870 | - return Math.max(0.0, Math.min(1.0, percent));
|
|
| 871 | - }
|
|
| 872 | -
|
|
| 873 | - /**
|
|
| 874 | - * This method is called immediately after a widget becomes attached to the browser's document.
|
|
| 875 | - */
|
|
| 876 | - @Override
|
|
| 877 | - protected void onLoad() {
|
|
| 878 | - // Reset the position attribute of the parent element
|
|
| 879 | - getElement().getStyle().setPosition(Position.RELATIVE);
|
|
| 880 | - }
|
|
| 881 | -
|
|
| 882 | - /**
|
|
| 883 | - * Draw the knob where it is supposed to be relative to the line.
|
|
| 884 | - */
|
|
| 885 | - protected void drawKnob() {
|
|
| 886 | - if (isAttached() && isMinMaxInitialized()) {
|
|
| 887 | - // Move the knob to the correct position
|
|
| 888 | - Element knobElement = knobImage.getElement();
|
|
| 889 | - int lineWidth = lineElement.getOffsetWidth();
|
|
| 890 | - int knobWidth = knobElement.getOffsetWidth();
|
|
| 891 | - int knobLeftOffset = (int) (lineLeftOffset + (getKnobPercent() * lineWidth) - (knobWidth / 2));
|
|
| 892 | - knobLeftOffset = Math.min(knobLeftOffset, lineLeftOffset + lineWidth - (knobWidth / 2) - 1);
|
|
| 893 | - knobElement.getStyle().setLeft(knobLeftOffset, Unit.PX);
|
|
| 894 | - }
|
|
| 895 | - }
|
|
| 896 | -
|
|
| 897 | - /**
|
|
| 898 | - * Draw the labels along the line.
|
|
| 899 | - */
|
|
| 900 | - protected void drawTickLabels() {
|
|
| 901 | - if (isAttached() && isMinMaxInitialized()) {
|
|
| 902 | - // Draw the tick labels
|
|
| 903 | - int lineWidth = lineElement.getOffsetWidth();
|
|
| 904 | - if (numTickLabels > 0) {
|
|
| 905 | - // Create the labels or make them visible
|
|
| 906 | - Double previousValue = null;
|
|
| 907 | - for (int i = 0; i <= numTickLabels; i++) {
|
|
| 908 | - Element label = null;
|
|
| 909 | - if (i < tickLabelElements.size()) {
|
|
| 910 | - label = tickLabelElements.get(i);
|
|
| 911 | - } else { // Create the new label
|
|
| 912 | - label = DOM.createDiv();
|
|
| 913 | - label.getStyle().setPosition(Position.ABSOLUTE);
|
|
| 914 | - label.getStyle().setDisplay(Display.NONE);
|
|
| 915 | - if (enabled) {
|
|
| 916 | - label.setPropertyString("className", "gwt-SliderBar-ticklabel");
|
|
| 917 | - } else {
|
|
| 918 | - label.setPropertyString("className", "gwt-SliderBar-ticklabel-disabled");
|
|
| 919 | - }
|
|
| 920 | - DOM.appendChild(getElement(), label);
|
|
| 921 | - tickLabelElements.add(label);
|
|
| 922 | - }
|
|
| 923 | -
|
|
| 924 | - // Set the label text
|
|
| 925 | - double value = minValue + (getTotalRange() * i / numTickLabels);
|
|
| 926 | - label.getStyle().setVisibility(Visibility.HIDDEN);
|
|
| 927 | - label.getStyle().setProperty("display", "");
|
|
| 928 | - label.setPropertyString("innerHTML", formatTickLabel(value, previousValue));
|
|
| 929 | -
|
|
| 930 | - // Move to the left so the label width is not clipped by the shell
|
|
| 931 | - label.getStyle().setLeft(0, Unit.PX);
|
|
| 932 | -
|
|
| 933 | - // Position the label and make it visible
|
|
| 934 | - int labelWidth = label.getOffsetWidth();
|
|
| 935 | - int labelLeftOffset = lineLeftOffset + (lineWidth * i / numTickLabels) - (labelWidth / 2);
|
|
| 936 | - labelLeftOffset = Math.min(labelLeftOffset, lineLeftOffset + lineWidth - labelWidth);
|
|
| 937 | - labelLeftOffset = Math.max(labelLeftOffset, lineLeftOffset);
|
|
| 938 | - label.getStyle().setLeft(labelLeftOffset, Unit.PX);
|
|
| 939 | - label.getStyle().setVisibility(Visibility.VISIBLE);
|
|
| 940 | -
|
|
| 941 | - previousValue = value;
|
|
| 942 | - }
|
|
| 943 | -
|
|
| 944 | - // Hide unused labels
|
|
| 945 | - for (int i = (numTickLabels + 1); i < tickLabelElements.size(); i++) {
|
|
| 946 | - tickLabelElements.get(i).getStyle().setDisplay(Display.NONE);
|
|
| 947 | - }
|
|
| 948 | - } else { // Hide all labels
|
|
| 949 | - for (Element elem : tickLabelElements) {
|
|
| 950 | - elem.getStyle().setDisplay(Display.NONE);
|
|
| 951 | - }
|
|
| 952 | - }
|
|
| 953 | - }
|
|
| 954 | - }
|
|
| 955 | -
|
|
| 956 | - /**
|
|
| 957 | - * Draw the tick along the line.
|
|
| 958 | - */
|
|
| 959 | - protected void drawTicks() {
|
|
| 960 | - if (isAttached() && isMinMaxInitialized()) {
|
|
| 961 | - // Draw the ticks
|
|
| 962 | - int lineWidth = lineElement.getOffsetWidth();
|
|
| 963 | - if (numTicks > 0) {
|
|
| 964 | - // Create the ticks or make them visible
|
|
| 965 | - for (int i = 0; i <= numTicks; i++) {
|
|
| 966 | - Element tick = null;
|
|
| 967 | - if (i < tickElements.size()) {
|
|
| 968 | - tick = tickElements.get(i);
|
|
| 969 | - } else { // Create the new tick
|
|
| 970 | - tick = DOM.createDiv();
|
|
| 971 | - tick.getStyle().setPosition(Position.ABSOLUTE);
|
|
| 972 | - tick.getStyle().setDisplay(Display.NONE);
|
|
| 973 | - DOM.appendChild(getElement(), tick);
|
|
| 974 | - tickElements.add(tick);
|
|
| 975 | - }
|
|
| 976 | - if (enabled) {
|
|
| 977 | - tick.setPropertyString("className", "gwt-SliderBar-tick");
|
|
| 978 | - } else {
|
|
| 979 | - tick.setPropertyString("className", "gwt-SliderBar-tick gwt-SliderBar-tick-disabled");
|
|
| 980 | - }
|
|
| 981 | - // Position the tick and make it visible
|
|
| 982 | - tick.getStyle().setVisibility(Visibility.HIDDEN);
|
|
| 983 | - tick.getStyle().setProperty("display", "");
|
|
| 984 | - int tickWidth = tick.getOffsetWidth();
|
|
| 985 | - int tickLeftOffset = lineLeftOffset + (lineWidth * i / numTicks) - (tickWidth / 2);
|
|
| 986 | - tickLeftOffset = Math.min(tickLeftOffset, lineLeftOffset + lineWidth - tickWidth);
|
|
| 987 | - tick.getStyle().setLeft(tickLeftOffset, Unit.PX);
|
|
| 988 | - tick.getStyle().setVisibility(Visibility.VISIBLE);
|
|
| 989 | - }
|
|
| 990 | -
|
|
| 991 | - // Hide unused ticks
|
|
| 992 | - for (int i = (numTicks + 1); i < tickElements.size(); i++) {
|
|
| 993 | - tickElements.get(i).getStyle().setDisplay(Display.NONE);
|
|
| 994 | - }
|
|
| 995 | - } else { // Hide all ticks
|
|
| 996 | - for (Element elem : tickElements) {
|
|
| 997 | - elem.getStyle().setDisplay(Display.NONE);
|
|
| 998 | - }
|
|
| 999 | - }
|
|
| 1000 | - }
|
|
| 1001 | - }
|
|
| 1002 | -
|
|
| 1003 | - /**
|
|
| 1004 | - * Draw the markers.
|
|
| 1005 | - */
|
|
| 1006 | - private void drawMarkers() {
|
|
| 1007 | - if (isAttached() && isMinMaxInitialized()) {
|
|
| 1008 | - int numMarkers = markers.size();
|
|
| 1009 | - // Draw the markers
|
|
| 1010 | - int lineWidth = lineElement.getOffsetWidth();
|
|
| 1011 | - if (numMarkers > 0) {
|
|
| 1012 | - // Create the markers or make them visible
|
|
| 1013 | - Element lastMarkerElement = null;
|
|
| 1014 | - int lastMarkerOffsetLeft = 0;
|
|
| 1015 | - for (int i = 0; i < numMarkers; i++) {
|
|
| 1016 | - Marker marker = markers.get(i);
|
|
| 1017 | - Element markerElem = null;
|
|
| 1018 | - if (i < markerElements.size()) {
|
|
| 1019 | - markerElem = markerElements.get(i);
|
|
| 1020 | - } else { // Create the new markes
|
|
| 1021 | - markerElem = DOM.createDiv();
|
|
| 1022 | - markerElem.getStyle().setPosition(Position.ABSOLUTE);
|
|
| 1023 | - markerElem.getStyle().setDisplay(Display.NONE);
|
|
| 1024 | - DOM.appendChild(getElement(), markerElem);
|
|
| 1025 | - markerElements.add(markerElem);
|
|
| 1026 | - }
|
|
| 1027 | - if (enabled) {
|
|
| 1028 | - markerElem.setPropertyString("className", "gwt-SliderBar-mark");
|
|
| 1029 | - } else {
|
|
| 1030 | - markerElem.setPropertyString("className", "gwt-SliderBar-mark gwt-SliderBar-mark-disabled");
|
|
| 1031 | - }
|
|
| 1032 | - // Position the marker and make it visible
|
|
| 1033 | - markerElem.getStyle().setVisibility(Visibility.HIDDEN);
|
|
| 1034 | - markerElem.getStyle().setProperty("display", "");
|
|
| 1035 | - double markerLinePosition = (marker.position - minValue) * lineWidth / getTotalRange();
|
|
| 1036 | - int markerWidth = markerElem.getOffsetWidth();
|
|
| 1037 | - int markerLeftOffset = lineLeftOffset + (int) markerLinePosition - (markerWidth / 2);
|
|
| 1038 | - markerLeftOffset = Math.min(markerLeftOffset, lineLeftOffset + lineWidth - markerWidth);
|
|
| 1039 | - markerElem.getStyle().setLeft(markerLeftOffset, Unit.PX);
|
|
| 1040 | - markerElem.getStyle().setVisibility(Visibility.VISIBLE);
|
|
| 1041 | - // change class of last marker if it is too close to the one just created
|
|
| 1042 | - if (lastMarkerOffsetLeft > 0 && (markerLeftOffset-lastMarkerOffsetLeft) <= markerWidth) {
|
|
| 1043 | - if (lastMarkerElement != null) {
|
|
| 1044 | - lastMarkerElement.setPropertyString("className", "gwt-SliderBar-mark-tooClose");
|
|
| 1045 | - lastMarkerElement.getStyle().setLeft(markerLeftOffset-10, Unit.PX);
|
|
| 1046 | - }
|
|
| 1047 | - }
|
|
| 1048 | - lastMarkerElement = markerElem;
|
|
| 1049 | - lastMarkerOffsetLeft = markerLeftOffset;
|
|
| 1050 | - }
|
|
| 1051 | -
|
|
| 1052 | - // Hide unused markers
|
|
| 1053 | - for (int i = numMarkers; i < markerElements.size(); i++) {
|
|
| 1054 | - markerElements.get(i).getStyle().setDisplay(Display.NONE);
|
|
| 1055 | - }
|
|
| 1056 | - } else { // Hide all markers
|
|
| 1057 | - for (Element elem : markerElements) {
|
|
| 1058 | - elem.getStyle().setDisplay(Display.NONE);
|
|
| 1059 | - }
|
|
| 1060 | - }
|
|
| 1061 | - }
|
|
| 1062 | - }
|
|
| 1063 | -
|
|
| 1064 | - /**
|
|
| 1065 | - * Draw the marker labels.
|
|
| 1066 | - */
|
|
| 1067 | - private void drawMarkerLabels() {
|
|
| 1068 | - if (isAttached() && isMinMaxInitialized()) {
|
|
| 1069 | - int numMarkers = markers.size();
|
|
| 1070 | - // Draw the marker labels
|
|
| 1071 | - int lineWidth = lineElement.getOffsetWidth();
|
|
| 1072 | - if (numMarkers > 0) {
|
|
| 1073 | - Element lastMarkerLabel = null;
|
|
| 1074 | - Marker lastMarker = null;
|
|
| 1075 | - int lastMarkerLabelOffsetLeft = 0;
|
|
| 1076 | - // Create the labels or make them visible
|
|
| 1077 | - for (int i = 0; i < numMarkers; i++) {
|
|
| 1078 | - Marker marker = markers.get(i);
|
|
| 1079 | - Element label = null;
|
|
| 1080 | - if (i < markerLabelElements.size()) {
|
|
| 1081 | - label = markerLabelElements.get(i);
|
|
| 1082 | - } else { // Create the new label
|
|
| 1083 | - label = DOM.createDiv();
|
|
| 1084 | - label.getStyle().setPosition(Position.ABSOLUTE);
|
|
| 1085 | - label.getStyle().setDisplay(Display.NONE);
|
|
| 1086 | - if (enabled) {
|
|
| 1087 | - label.setPropertyString("className", "gwt-SliderBar-markerlabel");
|
|
| 1088 | - } else {
|
|
| 1089 | - label.setPropertyString("className", "gwt-SliderBar-markerlabel-disabled");
|
|
| 1090 | - }
|
|
| 1091 | - DOM.appendChild(getElement(), label);
|
|
| 1092 | - markerLabelElements.add(label);
|
|
| 1093 | - }
|
|
| 1094 | -
|
|
| 1095 | - // Set the marker label text
|
|
| 1096 | - label.getStyle().setVisibility(Visibility.HIDDEN);
|
|
| 1097 | - label.getStyle().setProperty("display", "");
|
|
| 1098 | - label.setPropertyString("innerHTML", marker.name);
|
|
| 1099 | -
|
|
| 1100 | - // Move to the left so the label width is not clipped by the shell
|
|
| 1101 | - label.getStyle().setLeft(0, Unit.PX);
|
|
| 1102 | -
|
|
| 1103 | - // Position the label and make it visible
|
|
| 1104 | - double markerLinePosition = (marker.position - minValue) * lineWidth / getTotalRange();
|
|
| 1105 | - int labelWidth = label.getOffsetWidth();
|
|
| 1106 | - int labelLeftOffset = lineLeftOffset + (int) markerLinePosition - (labelWidth / 2);
|
|
| 1107 | - labelLeftOffset = Math.min(labelLeftOffset, lineLeftOffset + lineWidth - labelWidth);
|
|
| 1108 | -
|
|
| 1109 | - label.getStyle().setLeft(labelLeftOffset, Unit.PX);
|
|
| 1110 | - label.getStyle().setVisibility(Visibility.VISIBLE);
|
|
| 1111 | -
|
|
| 1112 | - // hide last marker label if it is too close to the one just created
|
|
| 1113 | - if (lastMarkerLabelOffsetLeft > 0 && (labelLeftOffset-lastMarkerLabelOffsetLeft) <= 20) {
|
|
| 1114 | - if (lastMarkerLabel != null && lastMarker != null) {
|
|
| 1115 | - lastMarkerLabel.setPropertyString("className", "gwt-SliderBar-markerlabel-tooClose");
|
|
| 1116 | - lastMarkerLabel.setPropertyString("innerHTML", lastMarker.name); // no space for more
|
|
| 1117 | - int currentMarkerOffsetLeft = labelLeftOffset;
|
|
| 1118 | - try {
|
|
| 1119 | - currentMarkerOffsetLeft = Integer.parseInt(markerElements.get(i).getStyle().getLeft().replace("px", ""));
|
|
| 1120 | - } catch (NumberFormatException ex) {
|
|
| 1121 | - // ignore, can't do anything
|
|
| 1122 | - }
|
|
| 1123 | - lastMarkerLabel.getStyle().setLeft(currentMarkerOffsetLeft-8, Unit.PX);
|
|
| 1124 | - }
|
|
| 1125 | - }
|
|
| 1126 | - lastMarker = marker;
|
|
| 1127 | - lastMarkerLabel = label;
|
|
| 1128 | - lastMarkerLabelOffsetLeft = labelLeftOffset;
|
|
| 1129 | - }
|
|
| 1130 | -
|
|
| 1131 | - // Hide unused labels
|
|
| 1132 | - for (int i = (numMarkers + 1); i < markerLabelElements.size(); i++) {
|
|
| 1133 | - markerLabelElements.get(i).getStyle().setDisplay(Display.NONE);
|
|
| 1134 | - }
|
|
| 1135 | - } else { // Hide all labels
|
|
| 1136 | - for (Element elem : markerLabelElements) {
|
|
| 1137 | - elem.getStyle().setDisplay(Display.NONE);
|
|
| 1138 | - }
|
|
| 1139 | - }
|
|
| 1140 | - }
|
|
| 1141 | - }
|
|
| 1142 | -
|
|
| 1143 | - /**
|
|
| 1144 | - * Highlight this widget.
|
|
| 1145 | - */
|
|
| 1146 | - private void highlight() {
|
|
| 1147 | - String styleName = getStylePrimaryName();
|
|
| 1148 | - getElement().setPropertyString("className", styleName + " " + styleName + "-focused");
|
|
| 1149 | - }
|
|
| 1150 | -
|
|
| 1151 | - /**
|
|
| 1152 | - * Reset the progress to constrain the progress to the current range and redraw the knob as needed.
|
|
| 1153 | - */
|
|
| 1154 | - private synchronized void resetCurrentValue(boolean fireEvent) {
|
|
| 1155 | - setCurrentValue(getCurrentValue(), fireEvent);
|
|
| 1156 | - }
|
|
| 1157 | -
|
|
| 1158 | - /**
|
|
| 1159 | - * Slide the knob to a new location.
|
|
| 1160 | - *
|
|
| 1161 | - * @param event
|
|
| 1162 | - * the mouse event
|
|
| 1163 | - */
|
|
| 1164 | - private void slideKnob(Event event) {
|
|
| 1165 | - // Adding scrollLeft to adjust the position, if the user had scrolled with the lower scroll bar
|
|
| 1166 | - int x = event.getClientX() + Window.getScrollLeft();
|
|
| 1167 | - if (x > 0) {
|
|
| 1168 | - int lineWidth = lineElement.getOffsetWidth();
|
|
| 1169 | - int lineLeft = lineElement.getAbsoluteLeft();
|
|
| 1170 | - double percent = (double) (x - lineLeft) / lineWidth * 1.0;
|
|
| 1171 | - setCurrentValue(getTotalRange() * percent + minValue, true);
|
|
| 1172 | - }
|
|
| 1173 | - }
|
|
| 1174 | -
|
|
| 1175 | - /**
|
|
| 1176 | - * Start sliding the knob.
|
|
| 1177 | - *
|
|
| 1178 | - * @param highlight
|
|
| 1179 | - * true to change the style
|
|
| 1180 | - * @param fireEvent
|
|
| 1181 | - * true to fire the event
|
|
| 1182 | - */
|
|
| 1183 | - private void startSliding(boolean highlight, boolean fireEvent) {
|
|
| 1184 | - if (highlight) {
|
|
| 1185 | - lineElement.setPropertyString("className", "gwt-SliderBar-line gwt-SliderBar-line-sliding");
|
|
| 1186 | - knobImage.getElement().setPropertyString("className", "gwt-SliderBar-knob gwt-SliderBar-knob-sliding");
|
|
| 1187 | - knobImage.setResource(images.sliderSliding());
|
|
| 1188 | - }
|
|
| 1189 | - }
|
|
| 1190 | -
|
|
| 1191 | - /**
|
|
| 1192 | - * Stop sliding the knob.
|
|
| 1193 | - *
|
|
| 1194 | - * @param unhighlight
|
|
| 1195 | - * true to change the style
|
|
| 1196 | - * @param fireEvent
|
|
| 1197 | - * true to fire the event
|
|
| 1198 | - */
|
|
| 1199 | - private void stopSliding(boolean unhighlight, boolean fireEvent) {
|
|
| 1200 | - if (unhighlight) {
|
|
| 1201 | - lineElement.setPropertyString("className", "gwt-SliderBar-line");
|
|
| 1202 | - knobImage.getElement().setPropertyString("className", "gwt-SliderBar-knob");
|
|
| 1203 | - knobImage.setResource(images.slider());
|
|
| 1204 | - }
|
|
| 1205 | - }
|
|
| 1206 | -
|
|
| 1207 | - /**
|
|
| 1208 | - * Unhighlight this widget.
|
|
| 1209 | - */
|
|
| 1210 | - private void unhighlight() {
|
|
| 1211 | - getElement().setPropertyString("className", getStylePrimaryName());
|
|
| 1212 | - }
|
|
| 1213 | -
|
|
| 1214 | - @Override
|
|
| 1215 | - public void onResize() {
|
|
| 1216 | - redraw();
|
|
| 1217 | - }
|
|
| 1218 | -
|
|
| 1219 | - public void clearMarkers() {
|
|
| 1220 | - markers.clear();
|
|
| 1221 | - }
|
|
| 1222 | -
|
|
| 1223 | - public boolean addMarker(String markerName, Double markerPosition) {
|
|
| 1224 | - return markers.add(new Marker(markerName, markerPosition));
|
|
| 1225 | - }
|
|
| 1226 | -
|
|
| 1227 | - public boolean setMarker(String markerName, Double markerPosition) {
|
|
| 1228 | - Marker marker = findMarkerByName(markerName);
|
|
| 1229 | - if (marker != null) {
|
|
| 1230 | - marker.position = markerPosition;
|
|
| 1231 | - return true;
|
|
| 1232 | - }
|
|
| 1233 | - return false;
|
|
| 1234 | - }
|
|
| 1235 | -
|
|
| 1236 | - public boolean removeMarker(String markerName) {
|
|
| 1237 | - Marker marker = findMarkerByName(markerName);
|
|
| 1238 | - if (marker != null) {
|
|
| 1239 | - return markers.remove(marker);
|
|
| 1240 | - }
|
|
| 1241 | - return false;
|
|
| 1242 | - }
|
|
| 1243 | -
|
|
| 1244 | - public Iterator<Marker> getMarkers() {
|
|
| 1245 | - return markers.iterator();
|
|
| 1246 | - }
|
|
| 1247 | -
|
|
| 1248 | - private Marker findMarkerByName(String markerName) {
|
|
| 1249 | - for (Marker marker : markers) {
|
|
| 1250 | - if (marker.name.equals(markerName)) {
|
|
| 1251 | - return marker;
|
|
| 1252 | - }
|
|
| 1253 | - }
|
|
| 1254 | - return null;
|
|
| 1255 | - }
|
|
| 1256 | -}
|
|
| 1 | +package com.sap.sse.gwt.client.controls.slider; |
|
| 2 | + |
|
| 3 | +/* |
|
| 4 | + * Copyright 2008 Google Inc. |
|
| 5 | + * |
|
| 6 | + * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
|
| 7 | + * use this file except in compliance with the License. You may obtain a copy of |
|
| 8 | + * the License at |
|
| 9 | + * |
|
| 10 | + * http://www.apache.org/licenses/LICENSE-2.0 |
|
| 11 | + * |
|
| 12 | + * Unless required by applicable law or agreed to in writing, software |
|
| 13 | + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
|
| 14 | + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
|
| 15 | + * License for the specific language governing permissions and limitations under |
|
| 16 | + * the License. |
|
| 17 | + */ |
|
| 18 | + |
|
| 19 | +import java.util.ArrayList; |
|
| 20 | +import java.util.Iterator; |
|
| 21 | +import java.util.List; |
|
| 22 | + |
|
| 23 | +import com.google.gwt.core.client.GWT; |
|
| 24 | +import com.google.gwt.core.client.Scheduler; |
|
| 25 | +import com.google.gwt.core.client.Scheduler.ScheduledCommand; |
|
| 26 | +import com.google.gwt.dom.client.Element; |
|
| 27 | +import com.google.gwt.dom.client.Style.Display; |
|
| 28 | +import com.google.gwt.dom.client.Style.Position; |
|
| 29 | +import com.google.gwt.dom.client.Style.Unit; |
|
| 30 | +import com.google.gwt.dom.client.Style.Visibility; |
|
| 31 | +import com.google.gwt.event.dom.client.KeyCodes; |
|
| 32 | +import com.google.gwt.event.logical.shared.HasValueChangeHandlers; |
|
| 33 | +import com.google.gwt.event.logical.shared.ValueChangeEvent; |
|
| 34 | +import com.google.gwt.event.logical.shared.ValueChangeHandler; |
|
| 35 | +import com.google.gwt.event.shared.HandlerRegistration; |
|
| 36 | +import com.google.gwt.resources.client.ClientBundle; |
|
| 37 | +import com.google.gwt.resources.client.CssResource; |
|
| 38 | +import com.google.gwt.resources.client.CssResource.NotStrict; |
|
| 39 | +import com.google.gwt.resources.client.ImageResource; |
|
| 40 | +import com.google.gwt.user.client.DOM; |
|
| 41 | +import com.google.gwt.user.client.Event; |
|
| 42 | +import com.google.gwt.user.client.Timer; |
|
| 43 | +import com.google.gwt.user.client.Window; |
|
| 44 | +import com.google.gwt.user.client.ui.FocusPanel; |
|
| 45 | +import com.google.gwt.user.client.ui.HasValue; |
|
| 46 | +import com.google.gwt.user.client.ui.Image; |
|
| 47 | +import com.google.gwt.user.client.ui.RequiresResize; |
|
| 48 | +import com.sap.sse.common.Util; |
|
| 49 | + |
|
| 50 | +/** |
|
| 51 | + * A widget that allows the user to select a value within a range of possible values using a sliding bar that responds |
|
| 52 | + * to mouse events. |
|
| 53 | + * |
|
| 54 | + * <h3>Keyboard Events</h3> |
|
| 55 | + * <p> |
|
| 56 | + * SliderBar listens for the following key events. Holding down a key will repeat the action until the key is released. |
|
| 57 | + * <ul class='css'> |
|
| 58 | + * <li>left arrow - shift left one step</li> |
|
| 59 | + * <li>right arrow - shift right one step</li> |
|
| 60 | + * <li>ctrl+left arrow - jump left 10% of the distance</li> |
|
| 61 | + * <li>ctrl+right arrow - jump right 10% of the distance</li> |
|
| 62 | + * <li>home - jump to min value</li> |
|
| 63 | + * <li>end - jump to max value</li> |
|
| 64 | + * <li>space - jump to middle value</li> |
|
| 65 | + * </ul> |
|
| 66 | + * </p> |
|
| 67 | + * |
|
| 68 | + * <h3>CSS Style Rules</h3> |
|
| 69 | + * <ul class='css'> |
|
| 70 | + * <li>.gwt-SliderBar-shell { primary style }</li> |
|
| 71 | + * <li>.gwt-SliderBar-shell-focused { primary style when focused }</li> |
|
| 72 | + * <li>.gwt-SliderBar-shell gwt-SliderBar-line { the line that the knob moves along }</li> |
|
| 73 | + * <li>.gwt-SliderBar-shell gwt-SliderBar-line-sliding { the line that the knob moves along when sliding }</li> |
|
| 74 | + * <li>.gwt-SliderBar-shell .gwt-SliderBar-knob { the sliding knob }</li> |
|
| 75 | + * <li>.gwt-SliderBar-shell .gwt-SliderBar-knob-sliding { the sliding knob when sliding }</li> |
|
| 76 | + * <li> |
|
| 77 | + * .gwt-SliderBar-shell .gwt-SliderBar-tick { the ticks along the line }</li> |
|
| 78 | + * <li>.gwt-SliderBar-shell .gwt-SliderBar-ticklabel { the text labels along the line }</li> |
|
| 79 | + * </ul> |
|
| 80 | + */ |
|
| 81 | +public class SliderBar extends FocusPanel implements RequiresResize, HasValue<Double>, HasValueChangeHandlers<Double> { |
|
| 82 | + /** |
|
| 83 | + * A formatter used to format the labels displayed in the widget. |
|
| 84 | + */ |
|
| 85 | + public static interface LabelFormatter { |
|
| 86 | + /** |
|
| 87 | + * Generate the text to display in each label based on the label's value. |
|
| 88 | + * |
|
| 89 | + * Override this method to change the text displayed within the SliderBar. |
|
| 90 | + * |
|
| 91 | + * @param slider |
|
| 92 | + * the Slider bar |
|
| 93 | + * @param value |
|
| 94 | + * the value the label displays |
|
| 95 | + * @return the text to display for the label |
|
| 96 | + */ |
|
| 97 | + String formatLabel(SliderBar slider, Double value, Double previousValue); |
|
| 98 | + } |
|
| 99 | + |
|
| 100 | + /** |
|
| 101 | + * A {@link ClientBundle} that provides images for {@link SliderBar}. |
|
| 102 | + */ |
|
| 103 | + public static interface SliderBarImages extends ClientBundle { |
|
| 104 | + public static final SliderBarImages INSTANCE = GWT.create(SliderBarImages.class); |
|
| 105 | + |
|
| 106 | + /** |
|
| 107 | + * An image used for the sliding knob. |
|
| 108 | + * |
|
| 109 | + * @return a prototype of this image |
|
| 110 | + */ |
|
| 111 | + @Source("slider_blue.png") |
|
| 112 | + ImageResource slider(); |
|
| 113 | + |
|
| 114 | + /** |
|
| 115 | + * An image used for the disabled sliding knob. |
|
| 116 | + * |
|
| 117 | + * @return a prototype of this image |
|
| 118 | + */ |
|
| 119 | + @Source("slider_blue.png") |
|
| 120 | + ImageResource sliderDisabled(); |
|
| 121 | + |
|
| 122 | + /** |
|
| 123 | + * An image used for the sliding knob while sliding. |
|
| 124 | + * |
|
| 125 | + * @return a prototype of this image |
|
| 126 | + */ |
|
| 127 | + @Source("slider_blue.png") |
|
| 128 | + ImageResource sliderSliding(); |
|
| 129 | + |
|
| 130 | + @NotStrict |
|
| 131 | + @Source("SliderBar.css") |
|
| 132 | + CssResource sliderBarCss(); |
|
| 133 | + } |
|
| 134 | + |
|
| 135 | + /** |
|
| 136 | + * The timer used to continue to shift the knob as the user holds down one of the left/right arrow keys. Only IE |
|
| 137 | + * auto-repeats, so we just keep catching the events. |
|
| 138 | + */ |
|
| 139 | + private class KeyTimer extends Timer { |
|
| 140 | + /** |
|
| 141 | + * A bit indicating that this is the first run. |
|
| 142 | + */ |
|
| 143 | + private boolean firstRun = true; |
|
| 144 | + |
|
| 145 | + /** |
|
| 146 | + * The delay between shifts, which shortens as the user holds down the button. |
|
| 147 | + */ |
|
| 148 | + private int repeatDelay = 30; |
|
| 149 | + |
|
| 150 | + /** |
|
| 151 | + * A bit indicating whether we are shifting to a higher or lower value. |
|
| 152 | + */ |
|
| 153 | + private boolean shiftRight = false; |
|
| 154 | + |
|
| 155 | + /** |
|
| 156 | + * The number of steps to shift with each press. |
|
| 157 | + */ |
|
| 158 | + private int multiplier = 1; |
|
| 159 | + |
|
| 160 | + /** |
|
| 161 | + * This method will be called when a timer fires. Override it to implement the timer's logic. |
|
| 162 | + */ |
|
| 163 | + @Override |
|
| 164 | + public void run() { |
|
| 165 | + // Highlight the knob on first run |
|
| 166 | + if (firstRun) { |
|
| 167 | + firstRun = false; |
|
| 168 | + startSliding(true, false); |
|
| 169 | + } |
|
| 170 | + |
|
| 171 | + // Slide the slider bar |
|
| 172 | + if (shiftRight) { |
|
| 173 | + setCurrentValue(curValue + multiplier * stepSize); |
|
| 174 | + } else { |
|
| 175 | + setCurrentValue(curValue - multiplier * stepSize); |
|
| 176 | + } |
|
| 177 | + |
|
| 178 | + // Repeat this timer until cancelled by keyup event |
|
| 179 | + schedule(repeatDelay); |
|
| 180 | + } |
|
| 181 | + |
|
| 182 | + /** |
|
| 183 | + * Schedules a timer to elapse in the future. |
|
| 184 | + * |
|
| 185 | + * @param delayMillis |
|
| 186 | + * how long to wait before the timer elapses, in milliseconds |
|
| 187 | + * @param shiftRight |
|
| 188 | + * whether to shift up or not |
|
| 189 | + * @param multiplier |
|
| 190 | + * the number of steps to shift |
|
| 191 | + */ |
|
| 192 | + public void schedule(int delayMillis, boolean shiftRight, int multiplier) { |
|
| 193 | + firstRun = true; |
|
| 194 | + this.shiftRight = shiftRight; |
|
| 195 | + this.multiplier = multiplier; |
|
| 196 | + super.schedule(delayMillis); |
|
| 197 | + } |
|
| 198 | + } |
|
| 199 | + |
|
| 200 | + /** |
|
| 201 | + * The current value. |
|
| 202 | + */ |
|
| 203 | + protected Double curValue; |
|
| 204 | + |
|
| 205 | + /** |
|
| 206 | + * The knob that slides across the line. |
|
| 207 | + */ |
|
| 208 | + protected Image knobImage = new Image(); |
|
| 209 | + |
|
| 210 | + /** |
|
| 211 | + * The timer used to continue to shift the knob if the user holds down a key. |
|
| 212 | + */ |
|
| 213 | + private KeyTimer keyTimer = new KeyTimer(); |
|
| 214 | + |
|
| 215 | + /** |
|
| 216 | + * The elements used to display labels above the ticks. |
|
| 217 | + */ |
|
| 218 | + protected List<Element> tickLabelElements = new ArrayList<Element>(); |
|
| 219 | + |
|
| 220 | + /** |
|
| 221 | + * The elements used to display the marker labels. |
|
| 222 | + */ |
|
| 223 | + protected List<Element> markerLabelElements = new ArrayList<Element>(); |
|
| 224 | + |
|
| 225 | + /** |
|
| 226 | + * The formatter used to generate label text. |
|
| 227 | + */ |
|
| 228 | + protected LabelFormatter tickLabelFormatter; |
|
| 229 | + |
|
| 230 | + /** |
|
| 231 | + * The line that the knob moves over. |
|
| 232 | + */ |
|
| 233 | + protected Element lineElement; |
|
| 234 | + |
|
| 235 | + /** |
|
| 236 | + * The offset between the edge of the shell and the line. |
|
| 237 | + */ |
|
| 238 | + protected int lineLeftOffset = 0; |
|
| 239 | + |
|
| 240 | + /** |
|
| 241 | + * The maximum slider value. |
|
| 242 | + */ |
|
| 243 | + protected Double maxValue; |
|
| 244 | + |
|
| 245 | + /** |
|
| 246 | + * The minimum slider value. |
|
| 247 | + */ |
|
| 248 | + protected Double minValue; |
|
| 249 | + |
|
| 250 | + /** |
|
| 251 | + * The number of labels to show. |
|
| 252 | + */ |
|
| 253 | + private int numTickLabels = 0; |
|
| 254 | + |
|
| 255 | + /** |
|
| 256 | + * The number of tick marks to show. |
|
| 257 | + */ |
|
| 258 | + protected int numTicks = 0; |
|
| 259 | + |
|
| 260 | + /** |
|
| 261 | + * A bit indicating whether or not we are currently sliding the slider bar due to keyboard events. |
|
| 262 | + */ |
|
| 263 | + private boolean slidingKeyboard = false; |
|
| 264 | + |
|
| 265 | + /** |
|
| 266 | + * A bit indicating whether or not we are currently sliding the slider bar due to mouse events. |
|
| 267 | + */ |
|
| 268 | + private boolean slidingMouse = false; |
|
| 269 | + |
|
| 270 | + /** |
|
| 271 | + * A bit indicating whether or not the slider is enabled |
|
| 272 | + */ |
|
| 273 | + protected boolean enabled = true; |
|
| 274 | + |
|
| 275 | + /** |
|
| 276 | + * The images used with the sliding bar. |
|
| 277 | + */ |
|
| 278 | + private SliderBarImages images; |
|
| 279 | + |
|
| 280 | + /** |
|
| 281 | + * The size of the increments between knob positions. |
|
| 282 | + */ |
|
| 283 | + protected double stepSize; |
|
| 284 | + |
|
| 285 | + /** |
|
| 286 | + * The elements used to display tick marks, which are the vertical lines along the slider bar. |
|
| 287 | + */ |
|
| 288 | + protected List<Element> tickElements = new ArrayList<Element>(); |
|
| 289 | + |
|
| 290 | + /** |
|
| 291 | + * The elements used to display additional markers on the slider bar. |
|
| 292 | + */ |
|
| 293 | + protected List<Element> markerElements = new ArrayList<Element>(); |
|
| 294 | + |
|
| 295 | + private List<Marker> markers = new ArrayList<Marker>(); |
|
| 296 | + |
|
| 297 | + private class Marker { |
|
| 298 | + String name; |
|
| 299 | + Double position; |
|
| 300 | + |
|
| 301 | + public Marker(String name, Double position) { |
|
| 302 | + super(); |
|
| 303 | + this.name = name; |
|
| 304 | + this.position = position; |
|
| 305 | + } |
|
| 306 | + } |
|
| 307 | + |
|
| 308 | + /** |
|
| 309 | + * Create a slider bar. |
|
| 310 | + */ |
|
| 311 | + public SliderBar() { |
|
| 312 | + this(null, null, null); |
|
| 313 | + } |
|
| 314 | + |
|
| 315 | + /** |
|
| 316 | + * Create a slider bar. |
|
| 317 | + * |
|
| 318 | + * @param minValue |
|
| 319 | + * the minimum value in the range |
|
| 320 | + * @param maxValue |
|
| 321 | + * the maximum value in the range |
|
| 322 | + */ |
|
| 323 | + public SliderBar(double minValue, double maxValue) { |
|
| 324 | + this(minValue, maxValue, null); |
|
| 325 | + } |
|
| 326 | + |
|
| 327 | + /** |
|
| 328 | + * Create a slider bar. |
|
| 329 | + * |
|
| 330 | + * @param minValue |
|
| 331 | + * the minimum value in the range |
|
| 332 | + * @param maxValue |
|
| 333 | + * the maximum value in the range |
|
| 334 | + * @param tickLabelFormatter |
|
| 335 | + * the label formatter |
|
| 336 | + */ |
|
| 337 | + public SliderBar(Double minValue, Double maxValue, LabelFormatter tickLabelFormatter) { |
|
| 338 | + this(minValue, maxValue, tickLabelFormatter, SliderBarImages.INSTANCE); |
|
| 339 | + } |
|
| 340 | + |
|
| 341 | + /** |
|
| 342 | + * Create a slider bar. |
|
| 343 | + * |
|
| 344 | + * @param minValue |
|
| 345 | + * the minimum value in the range |
|
| 346 | + * @param maxValue |
|
| 347 | + * the maximum value in the range |
|
| 348 | + * @param tickLabelFormatter |
|
| 349 | + * the label formatter |
|
| 350 | + * @param images |
|
| 351 | + * the images to use for the slider |
|
| 352 | + */ |
|
| 353 | + public SliderBar(Double minValue, Double maxValue, LabelFormatter tickLabelFormatter, SliderBarImages images) { |
|
| 354 | + super(); |
|
| 355 | + images.sliderBarCss().ensureInjected(); |
|
| 356 | + this.minValue = minValue; |
|
| 357 | + this.maxValue = maxValue; |
|
| 358 | + this.images = images; |
|
| 359 | + setLabelFormatter(tickLabelFormatter); |
|
| 360 | + |
|
| 361 | + // Create the outer shell |
|
| 362 | + getElement().getStyle().setPosition(Position.RELATIVE); |
|
| 363 | + setStyleName("gwt-SliderBar-shell"); |
|
| 364 | + |
|
| 365 | + // Create the line |
|
| 366 | + lineElement = DOM.createDiv(); |
|
| 367 | + DOM.appendChild(getElement(), lineElement); |
|
| 368 | + lineElement.getStyle().setPosition(Position.ABSOLUTE); |
|
| 369 | + lineElement.setPropertyString("className", "gwt-SliderBar-line"); |
|
| 370 | + |
|
| 371 | + // Create the knob |
|
| 372 | + knobImage.setResource(images.slider()); |
|
| 373 | + Element knobElement = knobImage.getElement(); |
|
| 374 | + DOM.appendChild(getElement(), knobElement); |
|
| 375 | + knobElement.getStyle().setPosition(Position.ABSOLUTE); |
|
| 376 | + knobElement.setPropertyString("className", "gwt-SliderBar-knob"); |
|
| 377 | + |
|
| 378 | + sinkEvents(Event.MOUSEEVENTS | Event.KEYEVENTS | Event.FOCUSEVENTS); |
|
| 379 | + |
|
| 380 | + // workaround to render properly when parent Widget does not |
|
| 381 | + // implement ProvidesResize since DOM doesn't provide element |
|
| 382 | + // height and width until onModuleLoad() finishes. |
|
| 383 | + Scheduler.get().scheduleDeferred(new ScheduledCommand() { |
|
| 384 | + @Override |
|
| 385 | + public void execute() { |
|
| 386 | + onResize(); |
|
| 387 | + } |
|
| 388 | + }); |
|
| 389 | + } |
|
| 390 | + |
|
| 391 | + public boolean isMinMaxInitialized() { |
|
| 392 | + return minValue != null && maxValue != null; |
|
| 393 | + } |
|
| 394 | + |
|
| 395 | + public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Double> handler) { |
|
| 396 | + return addHandler(handler, ValueChangeEvent.getType()); |
|
| 397 | + } |
|
| 398 | + |
|
| 399 | + /** |
|
| 400 | + * Return the current value. |
|
| 401 | + * |
|
| 402 | + * @return the current value |
|
| 403 | + */ |
|
| 404 | + public Double getCurrentValue() { |
|
| 405 | + return curValue; |
|
| 406 | + } |
|
| 407 | + |
|
| 408 | + /** |
|
| 409 | + * Return the label formatter. |
|
| 410 | + * |
|
| 411 | + * @return the label formatter |
|
| 412 | + */ |
|
| 413 | + public LabelFormatter getLabelFormatter() { |
|
| 414 | + return tickLabelFormatter; |
|
| 415 | + } |
|
| 416 | + |
|
| 417 | + /** |
|
| 418 | + * Return the max value. |
|
| 419 | + * |
|
| 420 | + * @return the max value |
|
| 421 | + */ |
|
| 422 | + public Double getMaxValue() { |
|
| 423 | + return maxValue; |
|
| 424 | + } |
|
| 425 | + |
|
| 426 | + /** |
|
| 427 | + * Return the minimum value. |
|
| 428 | + * |
|
| 429 | + * @return the minimum value |
|
| 430 | + */ |
|
| 431 | + public Double getMinValue() { |
|
| 432 | + return minValue; |
|
| 433 | + } |
|
| 434 | + |
|
| 435 | + /** |
|
| 436 | + * Return the number of labels. |
|
| 437 | + * |
|
| 438 | + * @return the number of labels |
|
| 439 | + */ |
|
| 440 | + public int getNumLabels() { |
|
| 441 | + return numTickLabels; |
|
| 442 | + } |
|
| 443 | + |
|
| 444 | + /** |
|
| 445 | + * Return the number of ticks. |
|
| 446 | + * |
|
| 447 | + * @return the number of ticks |
|
| 448 | + */ |
|
| 449 | + public int getNumTicks() { |
|
| 450 | + return numTicks; |
|
| 451 | + } |
|
| 452 | + |
|
| 453 | + /** |
|
| 454 | + * Return the step size. |
|
| 455 | + * |
|
| 456 | + * @return the step size |
|
| 457 | + */ |
|
| 458 | + public double getStepSize() { |
|
| 459 | + return stepSize; |
|
| 460 | + } |
|
| 461 | + |
|
| 462 | + /** |
|
| 463 | + * Return the total range between the minimum and maximum values. |
|
| 464 | + * |
|
| 465 | + * @return the total range |
|
| 466 | + */ |
|
| 467 | + public double getTotalRange() { |
|
| 468 | + if (minValue == null || maxValue == null || minValue > maxValue) { |
|
| 469 | + return 0; |
|
| 470 | + } else { |
|
| 471 | + return maxValue - minValue; |
|
| 472 | + } |
|
| 473 | + } |
|
| 474 | + |
|
| 475 | + public Double getValue() { |
|
| 476 | + return curValue; |
|
| 477 | + } |
|
| 478 | + |
|
| 479 | + /** |
|
| 480 | + * @return Gets whether this widget is enabled |
|
| 481 | + */ |
|
| 482 | + public boolean isEnabled() { |
|
| 483 | + return enabled; |
|
| 484 | + } |
|
| 485 | + |
|
| 486 | + /** |
|
| 487 | + * Listen for events that will move the knob. |
|
| 488 | + * |
|
| 489 | + * @param event |
|
| 490 | + * the event that occurred |
|
| 491 | + */ |
|
| 492 | + @Override |
|
| 493 | + public void onBrowserEvent(Event event) { |
|
| 494 | + super.onBrowserEvent(event); |
|
| 495 | + if (enabled) { |
|
| 496 | + switch (DOM.eventGetType(event)) { |
|
| 497 | + // Unhighlight and cancel keyboard events |
|
| 498 | + case Event.ONBLUR: |
|
| 499 | + keyTimer.cancel(); |
|
| 500 | + if (slidingMouse) { |
|
| 501 | + DOM.releaseCapture(getElement()); |
|
| 502 | + slidingMouse = false; |
|
| 503 | + slideKnob(event); |
|
| 504 | + stopSliding(true, true); |
|
| 505 | + } else if (slidingKeyboard) { |
|
| 506 | + slidingKeyboard = false; |
|
| 507 | + stopSliding(true, true); |
|
| 508 | + } |
|
| 509 | + unhighlight(); |
|
| 510 | + break; |
|
| 511 | + |
|
| 512 | + // Highlight on focus |
|
| 513 | + case Event.ONFOCUS: |
|
| 514 | + highlight(); |
|
| 515 | + break; |
|
| 516 | + |
|
| 517 | + // Mousewheel events |
|
| 518 | + case Event.ONMOUSEWHEEL: |
|
| 519 | + int velocityY = event.getMouseWheelVelocityY(); |
|
| 520 | + event.preventDefault(); |
|
| 521 | + if (velocityY > 0) { |
|
| 522 | + shiftRight(1); |
|
| 523 | + } else { |
|
| 524 | + shiftLeft(1); |
|
| 525 | + } |
|
| 526 | + break; |
|
| 527 | + |
|
| 528 | + // Shift left or right on key press |
|
| 529 | + case Event.ONKEYDOWN: |
|
| 530 | + if (!slidingKeyboard) { |
|
| 531 | + int multiplier = 1; |
|
| 532 | + if (event.getCtrlKey()) { |
|
| 533 | + multiplier = (int) (getTotalRange() / stepSize / 10); |
|
| 534 | + } |
|
| 535 | + |
|
| 536 | + switch (event.getKeyCode()) { |
|
| 537 | + case KeyCodes.KEY_HOME: |
|
| 538 | + event.preventDefault(); |
|
| 539 | + setCurrentValue(minValue); |
|
| 540 | + break; |
|
| 541 | + case KeyCodes.KEY_END: |
|
| 542 | + event.preventDefault(); |
|
| 543 | + setCurrentValue(maxValue); |
|
| 544 | + break; |
|
| 545 | + case KeyCodes.KEY_LEFT: |
|
| 546 | + event.preventDefault(); |
|
| 547 | + slidingKeyboard = true; |
|
| 548 | + startSliding(false, true); |
|
| 549 | + shiftLeft(multiplier); |
|
| 550 | + keyTimer.schedule(400, false, multiplier); |
|
| 551 | + break; |
|
| 552 | + case KeyCodes.KEY_RIGHT: |
|
| 553 | + event.preventDefault(); |
|
| 554 | + slidingKeyboard = true; |
|
| 555 | + startSliding(false, true); |
|
| 556 | + shiftRight(multiplier); |
|
| 557 | + keyTimer.schedule(400, true, multiplier); |
|
| 558 | + break; |
|
| 559 | + case 32: |
|
| 560 | + event.preventDefault(); |
|
| 561 | + setCurrentValue(minValue + getTotalRange() / 2); |
|
| 562 | + break; |
|
| 563 | + } |
|
| 564 | + } |
|
| 565 | + break; |
|
| 566 | + // Stop shifting on key up |
|
| 567 | + case Event.ONKEYUP: |
|
| 568 | + keyTimer.cancel(); |
|
| 569 | + if (slidingKeyboard) { |
|
| 570 | + slidingKeyboard = false; |
|
| 571 | + stopSliding(true, true); |
|
| 572 | + } |
|
| 573 | + break; |
|
| 574 | + |
|
| 575 | + // Mouse Events |
|
| 576 | + case Event.ONMOUSEDOWN: |
|
| 577 | + setFocus(true); |
|
| 578 | + slidingMouse = true; |
|
| 579 | + DOM.setCapture(getElement()); |
|
| 580 | + startSliding(true, true); |
|
| 581 | + event.preventDefault(); |
|
| 582 | + slideKnob(event); |
|
| 583 | + break; |
|
| 584 | + case Event.ONMOUSEUP: |
|
| 585 | + if (slidingMouse) { |
|
| 586 | + DOM.releaseCapture(getElement()); |
|
| 587 | + slidingMouse = false; |
|
| 588 | + slideKnob(event); |
|
| 589 | + stopSliding(true, true); |
|
| 590 | + } |
|
| 591 | + break; |
|
| 592 | + case Event.ONMOUSEMOVE: |
|
| 593 | + if (slidingMouse) { |
|
| 594 | + slideKnob(event); |
|
| 595 | + } |
|
| 596 | + break; |
|
| 597 | + } |
|
| 598 | + } |
|
| 599 | + } |
|
| 600 | + |
|
| 601 | + /** |
|
| 602 | + * This method is called when the dimensions of the parent element change. Subclasses should override this method as |
|
| 603 | + * needed. |
|
| 604 | + * |
|
| 605 | + * @param width |
|
| 606 | + * the new client width of the element |
|
| 607 | + * @param height |
|
| 608 | + * the new client height of the element |
|
| 609 | + */ |
|
| 610 | + public void onResize(int width, int height) { |
|
| 611 | + // Center the line in the shell |
|
| 612 | + int lineWidth = lineElement.getOffsetWidth(); |
|
| 613 | + lineLeftOffset = (width / 2) - (lineWidth / 2); |
|
| 614 | + lineElement.getStyle().setLeft(lineLeftOffset, Unit.PX); |
|
| 615 | + |
|
| 616 | + // Draw the other components |
|
| 617 | + drawTickLabels(); |
|
| 618 | + drawTicks(); |
|
| 619 | + drawMarkers(); |
|
| 620 | + drawMarkerLabels(); |
|
| 621 | + drawKnob(); |
|
| 622 | + } |
|
| 623 | + |
|
| 624 | + /** |
|
| 625 | + * Redraw the progress bar when something changes the layout. |
|
| 626 | + */ |
|
| 627 | + public void redraw() { |
|
| 628 | + if (isAttached()) { |
|
| 629 | + int width = getElement().getClientWidth(); |
|
| 630 | + int height = getElement().getClientHeight(); |
|
| 631 | + onResize(width, height); |
|
| 632 | + } |
|
| 633 | + } |
|
| 634 | + |
|
| 635 | + /** |
|
| 636 | + * Set the current value and fire the onValueChange event. |
|
| 637 | + * |
|
| 638 | + * @param curValue |
|
| 639 | + * the current value |
|
| 640 | + */ |
|
| 641 | + public synchronized void setCurrentValue(Double curValue) { |
|
| 642 | + setCurrentValue(curValue, true); |
|
| 643 | + } |
|
| 644 | + |
|
| 645 | + /** |
|
| 646 | + * Set the current value and optionally fire the onValueChange event. |
|
| 647 | + * |
|
| 648 | + * @param curValue |
|
| 649 | + * the current value |
|
| 650 | + * @param fireEvent |
|
| 651 | + * fire the onValue change event if true |
|
| 652 | + */ |
|
| 653 | + public synchronized void setCurrentValue(Double curValue, boolean fireEvent) { |
|
| 654 | + // Confine the value to the range |
|
| 655 | + if (!isMinMaxInitialized() || curValue == null) { |
|
| 656 | + return; |
|
| 657 | + } |
|
| 658 | + this.curValue = Math.max(minValue, Math.min(maxValue, curValue)); |
|
| 659 | + double remainder = (this.curValue - minValue) % stepSize; |
|
| 660 | + this.curValue -= remainder; |
|
| 661 | + // Go to next step if more than halfway there |
|
| 662 | + if ((remainder > (stepSize / 2)) && ((this.curValue + stepSize) <= maxValue)) { |
|
| 663 | + this.curValue += stepSize; |
|
| 664 | + } |
|
| 665 | + // Redraw the knob |
|
| 666 | + drawKnob(); |
|
| 667 | + // Fire the ValueChangeEvent if the value actually changed |
|
| 668 | + if (fireEvent && !curValue.equals(this.curValue)) { |
|
| 669 | + ValueChangeEvent.fire(this, this.curValue); |
|
| 670 | + } |
|
| 671 | + } |
|
| 672 | + |
|
| 673 | + /** |
|
| 674 | + * Sets whether this widget is enabled. |
|
| 675 | + * |
|
| 676 | + * @param enabled |
|
| 677 | + * true to enable the widget, false to disable it |
|
| 678 | + */ |
|
| 679 | + public void setEnabled(boolean enabled) { |
|
| 680 | + this.enabled = enabled; |
|
| 681 | + if (enabled) { |
|
| 682 | + knobImage.setResource(images.slider()); |
|
| 683 | + lineElement.setPropertyString("className", "gwt-SliderBar-line"); |
|
| 684 | + } else { |
|
| 685 | + knobImage.setResource(images.sliderDisabled()); |
|
| 686 | + lineElement.setPropertyString("className", "gwt-SliderBar-line gwt-SliderBar-line-disabled"); |
|
| 687 | + } |
|
| 688 | + redraw(); |
|
| 689 | + } |
|
| 690 | + |
|
| 691 | + /** |
|
| 692 | + * Set the label formatter. |
|
| 693 | + * |
|
| 694 | + * @param labelFormatter |
|
| 695 | + * the label formatter |
|
| 696 | + */ |
|
| 697 | + public void setLabelFormatter(LabelFormatter labelFormatter) { |
|
| 698 | + this.tickLabelFormatter = labelFormatter; |
|
| 699 | + } |
|
| 700 | + |
|
| 701 | + /** |
|
| 702 | + * Set the max value. |
|
| 703 | + * |
|
| 704 | + * @param maxValue |
|
| 705 | + * the current value |
|
| 706 | + */ |
|
| 707 | + public void setMaxValue(Double maxValue, boolean fireEvent) { |
|
| 708 | + if (!Util.equalsWithNull(maxValue, this.maxValue)) { |
|
| 709 | + this.maxValue = maxValue; |
|
| 710 | + onMinMaxValueChanged(fireEvent); |
|
| 711 | + } |
|
| 712 | + } |
|
| 713 | + |
|
| 714 | + /** |
|
| 715 | + * Set the minimum value. |
|
| 716 | + * |
|
| 717 | + * @param minValue |
|
| 718 | + * the current value |
|
| 719 | + */ |
|
| 720 | + public void setMinValue(Double minValue, boolean fireEvent) { |
|
| 721 | + if (!Util.equalsWithNull(minValue, this.minValue)) { |
|
| 722 | + this.minValue = minValue; |
|
| 723 | + onMinMaxValueChanged(fireEvent); |
|
| 724 | + } |
|
| 725 | + } |
|
| 726 | + |
|
| 727 | + /** |
|
| 728 | + * Set the minimum and maximum value |
|
| 729 | + * |
|
| 730 | + * @param minValue |
|
| 731 | + * the current value for min |
|
| 732 | + * @param maxValue |
|
| 733 | + * the current valuefor max |
|
| 734 | + */ |
|
| 735 | + public void setMinAndMaxValue(Double minValue, Double maxValue, boolean fireEvent) { |
|
| 736 | + boolean changed = false; |
|
| 737 | + if (!Util.equalsWithNull(minValue, this.minValue)) { |
|
| 738 | + this.minValue = minValue; |
|
| 739 | + changed = true; |
|
| 740 | + } |
|
| 741 | + if (!Util.equalsWithNull(maxValue, this.maxValue)) { |
|
| 742 | + this.maxValue = maxValue; |
|
| 743 | + changed = true; |
|
| 744 | + } |
|
| 745 | + if (changed) { |
|
| 746 | + onMinMaxValueChanged(fireEvent); |
|
| 747 | + } |
|
| 748 | + } |
|
| 749 | + |
|
| 750 | + /** |
|
| 751 | + * Handle value changes of min and/or max value |
|
| 752 | + */ |
|
| 753 | + protected void onMinMaxValueChanged(boolean fireEvent) { |
|
| 754 | + redraw(); |
|
| 755 | + resetCurrentValue(fireEvent); |
|
| 756 | + } |
|
| 757 | + |
|
| 758 | + /** |
|
| 759 | + * Set the number of tick labels to show on the line. Tick labels indicate the value of the slider at that point. |
|
| 760 | + * Use this method to enable tick labels. |
|
| 761 | + * |
|
| 762 | + * If you set the number of tick labels equal to the total range divided by the step size, you will get a properly |
|
| 763 | + * aligned "jumping" effect where the knob jumps between tick labels. |
|
| 764 | + * |
|
| 765 | + * Note that the number of tick labels displayed will be one more than the number you specify, so specify 1 labels |
|
| 766 | + * to show labels on either end of the line. In other words, numTickLabels is really the number of slots between the |
|
| 767 | + * labels. |
|
| 768 | + * |
|
| 769 | + * setNumTickLabels(0) will disable the labels. |
|
| 770 | + * |
|
| 771 | + * @param numTickLabels |
|
| 772 | + * the number of tick labels to show |
|
| 773 | + */ |
|
| 774 | + public void setNumTickLabels(int numTickLabels) { |
|
| 775 | + this.numTickLabels = numTickLabels; |
|
| 776 | + drawTickLabels(); |
|
| 777 | + } |
|
| 778 | + |
|
| 779 | + /** |
|
| 780 | + * Set the number of ticks to show on the line. A tick is a vertical line that represents a division of the overall |
|
| 781 | + * line. Use this method to enable ticks. |
|
| 782 | + * |
|
| 783 | + * If you set the number of ticks equal to the total range divided by the step size, you will get a properly aligned |
|
| 784 | + * "jumping" effect where the knob jumps between ticks. |
|
| 785 | + * |
|
| 786 | + * Note that the number of ticks displayed will be one more than the number you specify, so specify 1 tick to show |
|
| 787 | + * ticks on either end of the line. In other words, numTicks is really the number of slots between the ticks. |
|
| 788 | + * |
|
| 789 | + * setNumTicks(0) will disable ticks. |
|
| 790 | + * |
|
| 791 | + * @param numTicks |
|
| 792 | + * the number of ticks to show |
|
| 793 | + */ |
|
| 794 | + public void setNumTicks(int numTicks) { |
|
| 795 | + this.numTicks = numTicks; |
|
| 796 | + drawTicks(); |
|
| 797 | + } |
|
| 798 | + |
|
| 799 | + /** |
|
| 800 | + * Set the step size. |
|
| 801 | + * |
|
| 802 | + * @param stepSize |
|
| 803 | + * the current value |
|
| 804 | + */ |
|
| 805 | + public void setStepSize(double stepSize, boolean fireEvent) { |
|
| 806 | + this.stepSize = stepSize; |
|
| 807 | + resetCurrentValue(fireEvent); |
|
| 808 | + } |
|
| 809 | + |
|
| 810 | + public void setValue(Double value) { |
|
| 811 | + setCurrentValue(value, false); |
|
| 812 | + } |
|
| 813 | + |
|
| 814 | + public void setValue(Double value, boolean fireEvent) { |
|
| 815 | + setCurrentValue(value, fireEvent); |
|
| 816 | + } |
|
| 817 | + |
|
| 818 | + /** |
|
| 819 | + * Shift to the left (smaller value). |
|
| 820 | + * |
|
| 821 | + * @param numSteps |
|
| 822 | + * the number of steps to shift |
|
| 823 | + */ |
|
| 824 | + public void shiftLeft(int numSteps) { |
|
| 825 | + setCurrentValue(getCurrentValue() - numSteps * stepSize); |
|
| 826 | + } |
|
| 827 | + |
|
| 828 | + /** |
|
| 829 | + * Shift to the right (greater value). |
|
| 830 | + * |
|
| 831 | + * @param numSteps |
|
| 832 | + * the number of steps to shift |
|
| 833 | + */ |
|
| 834 | + public void shiftRight(int numSteps) { |
|
| 835 | + setCurrentValue(getCurrentValue() + numSteps * stepSize); |
|
| 836 | + } |
|
| 837 | + |
|
| 838 | + /** |
|
| 839 | + * Format the label to display above the ticks |
|
| 840 | + * |
|
| 841 | + * Override this method in a subclass to customize the format. By default, this method returns the integer portion |
|
| 842 | + * of the value. |
|
| 843 | + * |
|
| 844 | + * @param value |
|
| 845 | + * the value at the label |
|
| 846 | + * @return the text to put in the label |
|
| 847 | + */ |
|
| 848 | + protected String formatTickLabel(Double value, Double previousValue) { |
|
| 849 | + if (tickLabelFormatter != null) { |
|
| 850 | + return tickLabelFormatter.formatLabel(this, value, previousValue); |
|
| 851 | + } else { |
|
| 852 | + return (int) (10 * value) / 10.0 + ""; |
|
| 853 | + } |
|
| 854 | + } |
|
| 855 | + |
|
| 856 | + /** |
|
| 857 | + * Get the percentage of the knob's position relative to the size of the line. The return value will be between 0.0 |
|
| 858 | + * and 1.0. |
|
| 859 | + * |
|
| 860 | + * @return the current percent complete |
|
| 861 | + */ |
|
| 862 | + protected double getKnobPercent() { |
|
| 863 | + // If we have no range |
|
| 864 | + if (maxValue <= minValue) { |
|
| 865 | + return 0; |
|
| 866 | + } |
|
| 867 | + |
|
| 868 | + // Calculate the relative progress |
|
| 869 | + double percent = (curValue - minValue) / (maxValue - minValue); |
|
| 870 | + return Math.max(0.0, Math.min(1.0, percent)); |
|
| 871 | + } |
|
| 872 | + |
|
| 873 | + /** |
|
| 874 | + * This method is called immediately after a widget becomes attached to the browser's document. |
|
| 875 | + */ |
|
| 876 | + @Override |
|
| 877 | + protected void onLoad() { |
|
| 878 | + // Reset the position attribute of the parent element |
|
| 879 | + getElement().getStyle().setPosition(Position.RELATIVE); |
|
| 880 | + } |
|
| 881 | + |
|
| 882 | + /** |
|
| 883 | + * Draw the knob where it is supposed to be relative to the line. |
|
| 884 | + */ |
|
| 885 | + protected void drawKnob() { |
|
| 886 | + if (isAttached() && isMinMaxInitialized()) { |
|
| 887 | + // Move the knob to the correct position |
|
| 888 | + Element knobElement = knobImage.getElement(); |
|
| 889 | + int lineWidth = lineElement.getOffsetWidth(); |
|
| 890 | + int knobWidth = knobElement.getOffsetWidth(); |
|
| 891 | + int knobLeftOffset = (int) (lineLeftOffset + (getKnobPercent() * lineWidth) - (knobWidth / 2)); |
|
| 892 | + knobLeftOffset = Math.min(knobLeftOffset, lineLeftOffset + lineWidth - (knobWidth / 2) - 1); |
|
| 893 | + knobElement.getStyle().setLeft(knobLeftOffset, Unit.PX); |
|
| 894 | + } |
|
| 895 | + } |
|
| 896 | + |
|
| 897 | + /** |
|
| 898 | + * Draw the labels along the line. |
|
| 899 | + */ |
|
| 900 | + protected void drawTickLabels() { |
|
| 901 | + if (isAttached() && isMinMaxInitialized()) { |
|
| 902 | + // Draw the tick labels |
|
| 903 | + int lineWidth = lineElement.getOffsetWidth(); |
|
| 904 | + if (numTickLabels > 0) { |
|
| 905 | + // Create the labels or make them visible |
|
| 906 | + Double previousValue = null; |
|
| 907 | + for (int i = 0; i <= numTickLabels; i++) { |
|
| 908 | + Element label = null; |
|
| 909 | + if (i < tickLabelElements.size()) { |
|
| 910 | + label = tickLabelElements.get(i); |
|
| 911 | + } else { // Create the new label |
|
| 912 | + label = DOM.createDiv(); |
|
| 913 | + label.getStyle().setPosition(Position.ABSOLUTE); |
|
| 914 | + label.getStyle().setDisplay(Display.NONE); |
|
| 915 | + if (enabled) { |
|
| 916 | + label.setPropertyString("className", "gwt-SliderBar-ticklabel"); |
|
| 917 | + } else { |
|
| 918 | + label.setPropertyString("className", "gwt-SliderBar-ticklabel-disabled"); |
|
| 919 | + } |
|
| 920 | + DOM.appendChild(getElement(), label); |
|
| 921 | + tickLabelElements.add(label); |
|
| 922 | + } |
|
| 923 | + |
|
| 924 | + // Set the label text |
|
| 925 | + double value = minValue + (getTotalRange() * i / numTickLabels); |
|
| 926 | + label.getStyle().setVisibility(Visibility.HIDDEN); |
|
| 927 | + label.getStyle().setProperty("display", ""); |
|
| 928 | + label.setPropertyString("innerHTML", formatTickLabel(value, previousValue)); |
|
| 929 | + |
|
| 930 | + // Move to the left so the label width is not clipped by the shell |
|
| 931 | + label.getStyle().setLeft(0, Unit.PX); |
|
| 932 | + |
|
| 933 | + // Position the label and make it visible |
|
| 934 | + int labelWidth = label.getOffsetWidth(); |
|
| 935 | + int labelLeftOffset = lineLeftOffset + (lineWidth * i / numTickLabels) - (labelWidth / 2); |
|
| 936 | + labelLeftOffset = Math.min(labelLeftOffset, lineLeftOffset + lineWidth - labelWidth); |
|
| 937 | + labelLeftOffset = Math.max(labelLeftOffset, lineLeftOffset); |
|
| 938 | + label.getStyle().setLeft(labelLeftOffset, Unit.PX); |
|
| 939 | + label.getStyle().setVisibility(Visibility.VISIBLE); |
|
| 940 | + |
|
| 941 | + previousValue = value; |
|
| 942 | + } |
|
| 943 | + |
|
| 944 | + // Hide unused labels |
|
| 945 | + for (int i = (numTickLabels + 1); i < tickLabelElements.size(); i++) { |
|
| 946 | + tickLabelElements.get(i).getStyle().setDisplay(Display.NONE); |
|
| 947 | + } |
|
| 948 | + } else { // Hide all labels |
|
| 949 | + for (Element elem : tickLabelElements) { |
|
| 950 | + elem.getStyle().setDisplay(Display.NONE); |
|
| 951 | + } |
|
| 952 | + } |
|
| 953 | + } |
|
| 954 | + } |
|
| 955 | + |
|
| 956 | + /** |
|
| 957 | + * Draw the tick along the line. |
|
| 958 | + */ |
|
| 959 | + protected void drawTicks() { |
|
| 960 | + if (isAttached() && isMinMaxInitialized()) { |
|
| 961 | + // Draw the ticks |
|
| 962 | + int lineWidth = lineElement.getOffsetWidth(); |
|
| 963 | + if (numTicks > 0) { |
|
| 964 | + // Create the ticks or make them visible |
|
| 965 | + for (int i = 0; i <= numTicks; i++) { |
|
| 966 | + Element tick = null; |
|
| 967 | + if (i < tickElements.size()) { |
|
| 968 | + tick = tickElements.get(i); |
|
| 969 | + } else { // Create the new tick |
|
| 970 | + tick = DOM.createDiv(); |
|
| 971 | + tick.getStyle().setPosition(Position.ABSOLUTE); |
|
| 972 | + tick.getStyle().setDisplay(Display.NONE); |
|
| 973 | + DOM.appendChild(getElement(), tick); |
|
| 974 | + tickElements.add(tick); |
|
| 975 | + } |
|
| 976 | + if (enabled) { |
|
| 977 | + tick.setPropertyString("className", "gwt-SliderBar-tick"); |
|
| 978 | + } else { |
|
| 979 | + tick.setPropertyString("className", "gwt-SliderBar-tick gwt-SliderBar-tick-disabled"); |
|
| 980 | + } |
|
| 981 | + // Position the tick and make it visible |
|
| 982 | + tick.getStyle().setVisibility(Visibility.HIDDEN); |
|
| 983 | + tick.getStyle().setProperty("display", ""); |
|
| 984 | + int tickWidth = tick.getOffsetWidth(); |
|
| 985 | + int tickLeftOffset = lineLeftOffset + (lineWidth * i / numTicks) - (tickWidth / 2); |
|
| 986 | + tickLeftOffset = Math.min(tickLeftOffset, lineLeftOffset + lineWidth - tickWidth); |
|
| 987 | + tick.getStyle().setLeft(tickLeftOffset, Unit.PX); |
|
| 988 | + tick.getStyle().setVisibility(Visibility.VISIBLE); |
|
| 989 | + } |
|
| 990 | + |
|
| 991 | + // Hide unused ticks |
|
| 992 | + for (int i = (numTicks + 1); i < tickElements.size(); i++) { |
|
| 993 | + tickElements.get(i).getStyle().setDisplay(Display.NONE); |
|
| 994 | + } |
|
| 995 | + } else { // Hide all ticks |
|
| 996 | + for (Element elem : tickElements) { |
|
| 997 | + elem.getStyle().setDisplay(Display.NONE); |
|
| 998 | + } |
|
| 999 | + } |
|
| 1000 | + } |
|
| 1001 | + } |
|
| 1002 | + |
|
| 1003 | + /** |
|
| 1004 | + * Draw the markers. |
|
| 1005 | + */ |
|
| 1006 | + private void drawMarkers() { |
|
| 1007 | + if (isAttached() && isMinMaxInitialized()) { |
|
| 1008 | + int numMarkers = markers.size(); |
|
| 1009 | + // Draw the markers |
|
| 1010 | + int lineWidth = lineElement.getOffsetWidth(); |
|
| 1011 | + if (numMarkers > 0) { |
|
| 1012 | + // Create the markers or make them visible |
|
| 1013 | + Element lastMarkerElement = null; |
|
| 1014 | + int lastMarkerOffsetLeft = 0; |
|
| 1015 | + for (int i = 0; i < numMarkers; i++) { |
|
| 1016 | + Marker marker = markers.get(i); |
|
| 1017 | + Element markerElem = null; |
|
| 1018 | + if (i < markerElements.size()) { |
|
| 1019 | + markerElem = markerElements.get(i); |
|
| 1020 | + } else { // Create the new markes |
|
| 1021 | + markerElem = DOM.createDiv(); |
|
| 1022 | + markerElem.getStyle().setPosition(Position.ABSOLUTE); |
|
| 1023 | + markerElem.getStyle().setDisplay(Display.NONE); |
|
| 1024 | + DOM.appendChild(getElement(), markerElem); |
|
| 1025 | + markerElements.add(markerElem); |
|
| 1026 | + } |
|
| 1027 | + if (enabled) { |
|
| 1028 | + markerElem.setPropertyString("className", "gwt-SliderBar-mark"); |
|
| 1029 | + } else { |
|
| 1030 | + markerElem.setPropertyString("className", "gwt-SliderBar-mark gwt-SliderBar-mark-disabled"); |
|
| 1031 | + } |
|
| 1032 | + // Position the marker and make it visible |
|
| 1033 | + markerElem.getStyle().setVisibility(Visibility.HIDDEN); |
|
| 1034 | + markerElem.getStyle().setProperty("display", ""); |
|
| 1035 | + double markerLinePosition = (marker.position - minValue) * lineWidth / getTotalRange(); |
|
| 1036 | + int markerWidth = markerElem.getOffsetWidth(); |
|
| 1037 | + int markerLeftOffset = lineLeftOffset + (int) markerLinePosition - (markerWidth / 2); |
|
| 1038 | + markerLeftOffset = Math.min(markerLeftOffset, lineLeftOffset + lineWidth - markerWidth); |
|
| 1039 | + markerElem.getStyle().setLeft(markerLeftOffset, Unit.PX); |
|
| 1040 | + markerElem.getStyle().setVisibility(Visibility.VISIBLE); |
|
| 1041 | + // change class of last marker if it is too close to the one just created |
|
| 1042 | + if (lastMarkerOffsetLeft > 0 && (markerLeftOffset-lastMarkerOffsetLeft) <= markerWidth) { |
|
| 1043 | + if (lastMarkerElement != null) { |
|
| 1044 | + lastMarkerElement.setPropertyString("className", "gwt-SliderBar-mark-tooClose"); |
|
| 1045 | + lastMarkerElement.getStyle().setLeft(markerLeftOffset-10, Unit.PX); |
|
| 1046 | + } |
|
| 1047 | + } |
|
| 1048 | + lastMarkerElement = markerElem; |
|
| 1049 | + lastMarkerOffsetLeft = markerLeftOffset; |
|
| 1050 | + } |
|
| 1051 | + |
|
| 1052 | + // Hide unused markers |
|
| 1053 | + for (int i = numMarkers; i < markerElements.size(); i++) { |
|
| 1054 | + markerElements.get(i).getStyle().setDisplay(Display.NONE); |
|
| 1055 | + } |
|
| 1056 | + } else { // Hide all markers |
|
| 1057 | + for (Element elem : markerElements) { |
|
| 1058 | + elem.getStyle().setDisplay(Display.NONE); |
|
| 1059 | + } |
|
| 1060 | + } |
|
| 1061 | + } |
|
| 1062 | + } |
|
| 1063 | + |
|
| 1064 | + /** |
|
| 1065 | + * Draw the marker labels. |
|
| 1066 | + */ |
|
| 1067 | + private void drawMarkerLabels() { |
|
| 1068 | + if (isAttached() && isMinMaxInitialized()) { |
|
| 1069 | + int numMarkers = markers.size(); |
|
| 1070 | + // Draw the marker labels |
|
| 1071 | + int lineWidth = lineElement.getOffsetWidth(); |
|
| 1072 | + if (numMarkers > 0) { |
|
| 1073 | + Element lastMarkerLabel = null; |
|
| 1074 | + Marker lastMarker = null; |
|
| 1075 | + int lastMarkerLabelOffsetLeft = 0; |
|
| 1076 | + // Create the labels or make them visible |
|
| 1077 | + for (int i = 0; i < numMarkers; i++) { |
|
| 1078 | + Marker marker = markers.get(i); |
|
| 1079 | + Element label = null; |
|
| 1080 | + if (i < markerLabelElements.size()) { |
|
| 1081 | + label = markerLabelElements.get(i); |
|
| 1082 | + } else { // Create the new label |
|
| 1083 | + label = DOM.createDiv(); |
|
| 1084 | + label.getStyle().setPosition(Position.ABSOLUTE); |
|
| 1085 | + label.getStyle().setDisplay(Display.NONE); |
|
| 1086 | + DOM.appendChild(getElement(), label); |
|
| 1087 | + markerLabelElements.add(label); |
|
| 1088 | + } |
|
| 1089 | + if (enabled) { |
|
| 1090 | + label.setPropertyString("className", "gwt-SliderBar-markerlabel"); |
|
| 1091 | + } else { |
|
| 1092 | + label.setPropertyString("className", "gwt-SliderBar-markerlabel-disabled"); |
|
| 1093 | + } |
|
| 1094 | + |
|
| 1095 | + // Set the marker label text |
|
| 1096 | + label.getStyle().setVisibility(Visibility.HIDDEN); |
|
| 1097 | + label.getStyle().setProperty("display", ""); |
|
| 1098 | + label.setPropertyString("innerHTML", marker.name); |
|
| 1099 | + |
|
| 1100 | + // Move to the left so the label width is not clipped by the shell |
|
| 1101 | + label.getStyle().setLeft(0, Unit.PX); |
|
| 1102 | + |
|
| 1103 | + // Position the label and make it visible |
|
| 1104 | + double markerLinePosition = (marker.position - minValue) * lineWidth / getTotalRange(); |
|
| 1105 | + int labelWidth = label.getOffsetWidth(); |
|
| 1106 | + int labelLeftOffset = lineLeftOffset + (int) markerLinePosition - (labelWidth / 2); |
|
| 1107 | + labelLeftOffset = Math.min(labelLeftOffset, lineLeftOffset + lineWidth - labelWidth); |
|
| 1108 | + |
|
| 1109 | + label.getStyle().setLeft(labelLeftOffset, Unit.PX); |
|
| 1110 | + label.getStyle().setVisibility(Visibility.VISIBLE); |
|
| 1111 | + |
|
| 1112 | + // hide last marker label if it is too close to the one just created |
|
| 1113 | + if (lastMarkerLabelOffsetLeft > 0 && (labelLeftOffset-lastMarkerLabelOffsetLeft) <= 20) { |
|
| 1114 | + if (lastMarkerLabel != null && lastMarker != null) { |
|
| 1115 | + lastMarkerLabel.setPropertyString("className", "gwt-SliderBar-markerlabel-tooClose"); |
|
| 1116 | + lastMarkerLabel.setPropertyString("innerHTML", lastMarker.name); // no space for more |
|
| 1117 | + int currentMarkerOffsetLeft = labelLeftOffset; |
|
| 1118 | + try { |
|
| 1119 | + currentMarkerOffsetLeft = Integer.parseInt(markerElements.get(i).getStyle().getLeft().replace("px", "")); |
|
| 1120 | + } catch (NumberFormatException ex) { |
|
| 1121 | + // ignore, can't do anything |
|
| 1122 | + } |
|
| 1123 | + lastMarkerLabel.getStyle().setLeft(currentMarkerOffsetLeft-8, Unit.PX); |
|
| 1124 | + } |
|
| 1125 | + } |
|
| 1126 | + lastMarker = marker; |
|
| 1127 | + lastMarkerLabel = label; |
|
| 1128 | + lastMarkerLabelOffsetLeft = labelLeftOffset; |
|
| 1129 | + } |
|
| 1130 | + |
|
| 1131 | + // Hide unused labels |
|
| 1132 | + for (int i = (numMarkers + 1); i < markerLabelElements.size(); i++) { |
|
| 1133 | + markerLabelElements.get(i).getStyle().setDisplay(Display.NONE); |
|
| 1134 | + } |
|
| 1135 | + } else { // Hide all labels |
|
| 1136 | + for (Element elem : markerLabelElements) { |
|
| 1137 | + elem.getStyle().setDisplay(Display.NONE); |
|
| 1138 | + } |
|
| 1139 | + } |
|
| 1140 | + } |
|
| 1141 | + } |
|
| 1142 | + |
|
| 1143 | + /** |
|
| 1144 | + * Highlight this widget. |
|
| 1145 | + */ |
|
| 1146 | + private void highlight() { |
|
| 1147 | + String styleName = getStylePrimaryName(); |
|
| 1148 | + getElement().setPropertyString("className", styleName + " " + styleName + "-focused"); |
|
| 1149 | + } |
|
| 1150 | + |
|
| 1151 | + /** |
|
| 1152 | + * Reset the progress to constrain the progress to the current range and redraw the knob as needed. |
|
| 1153 | + */ |
|
| 1154 | + private synchronized void resetCurrentValue(boolean fireEvent) { |
|
| 1155 | + setCurrentValue(getCurrentValue(), fireEvent); |
|
| 1156 | + } |
|
| 1157 | + |
|
| 1158 | + /** |
|
| 1159 | + * Slide the knob to a new location. |
|
| 1160 | + * |
|
| 1161 | + * @param event |
|
| 1162 | + * the mouse event |
|
| 1163 | + */ |
|
| 1164 | + private void slideKnob(Event event) { |
|
| 1165 | + // Adding scrollLeft to adjust the position, if the user had scrolled with the lower scroll bar |
|
| 1166 | + int x = event.getClientX() + Window.getScrollLeft(); |
|
| 1167 | + if (x > 0) { |
|
| 1168 | + int lineWidth = lineElement.getOffsetWidth(); |
|
| 1169 | + int lineLeft = lineElement.getAbsoluteLeft(); |
|
| 1170 | + double percent = (double) (x - lineLeft) / lineWidth * 1.0; |
|
| 1171 | + setCurrentValue(getTotalRange() * percent + minValue, true); |
|
| 1172 | + } |
|
| 1173 | + } |
|
| 1174 | + |
|
| 1175 | + /** |
|
| 1176 | + * Start sliding the knob. |
|
| 1177 | + * |
|
| 1178 | + * @param highlight |
|
| 1179 | + * true to change the style |
|
| 1180 | + * @param fireEvent |
|
| 1181 | + * true to fire the event |
|
| 1182 | + */ |
|
| 1183 | + private void startSliding(boolean highlight, boolean fireEvent) { |
|
| 1184 | + if (highlight) { |
|
| 1185 | + lineElement.setPropertyString("className", "gwt-SliderBar-line gwt-SliderBar-line-sliding"); |
|
| 1186 | + knobImage.getElement().setPropertyString("className", "gwt-SliderBar-knob gwt-SliderBar-knob-sliding"); |
|
| 1187 | + knobImage.setResource(images.sliderSliding()); |
|
| 1188 | + } |
|
| 1189 | + } |
|
| 1190 | + |
|
| 1191 | + /** |
|
| 1192 | + * Stop sliding the knob. |
|
| 1193 | + * |
|
| 1194 | + * @param unhighlight |
|
| 1195 | + * true to change the style |
|
| 1196 | + * @param fireEvent |
|
| 1197 | + * true to fire the event |
|
| 1198 | + */ |
|
| 1199 | + private void stopSliding(boolean unhighlight, boolean fireEvent) { |
|
| 1200 | + if (unhighlight) { |
|
| 1201 | + lineElement.setPropertyString("className", "gwt-SliderBar-line"); |
|
| 1202 | + knobImage.getElement().setPropertyString("className", "gwt-SliderBar-knob"); |
|
| 1203 | + knobImage.setResource(images.slider()); |
|
| 1204 | + } |
|
| 1205 | + } |
|
| 1206 | + |
|
| 1207 | + /** |
|
| 1208 | + * Unhighlight this widget. |
|
| 1209 | + */ |
|
| 1210 | + private void unhighlight() { |
|
| 1211 | + getElement().setPropertyString("className", getStylePrimaryName()); |
|
| 1212 | + } |
|
| 1213 | + |
|
| 1214 | + @Override |
|
| 1215 | + public void onResize() { |
|
| 1216 | + redraw(); |
|
| 1217 | + } |
|
| 1218 | + |
|
| 1219 | + public void clearMarkers() { |
|
| 1220 | + markers.clear(); |
|
| 1221 | + } |
|
| 1222 | + |
|
| 1223 | + public boolean addMarker(String markerName, Double markerPosition) { |
|
| 1224 | + return markers.add(new Marker(markerName, markerPosition)); |
|
| 1225 | + } |
|
| 1226 | + |
|
| 1227 | + public boolean setMarker(String markerName, Double markerPosition) { |
|
| 1228 | + Marker marker = findMarkerByName(markerName); |
|
| 1229 | + if (marker != null) { |
|
| 1230 | + marker.position = markerPosition; |
|
| 1231 | + return true; |
|
| 1232 | + } |
|
| 1233 | + return false; |
|
| 1234 | + } |
|
| 1235 | + |
|
| 1236 | + public boolean removeMarker(String markerName) { |
|
| 1237 | + Marker marker = findMarkerByName(markerName); |
|
| 1238 | + if (marker != null) { |
|
| 1239 | + return markers.remove(marker); |
|
| 1240 | + } |
|
| 1241 | + return false; |
|
| 1242 | + } |
|
| 1243 | + |
|
| 1244 | + public Iterator<Marker> getMarkers() { |
|
| 1245 | + return markers.iterator(); |
|
| 1246 | + } |
|
| 1247 | + |
|
| 1248 | + private Marker findMarkerByName(String markerName) { |
|
| 1249 | + for (Marker marker : markers) { |
|
| 1250 | + if (marker.name.equals(markerName)) { |
|
| 1251 | + return marker; |
|
| 1252 | + } |
|
| 1253 | + } |
|
| 1254 | + return null; |
|
| 1255 | + } |
|
| 1256 | +} |
java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/TimeSlider.java
| ... | ... | @@ -26,7 +26,9 @@ public class TimeSlider extends SliderBar { |
| 26 | 26 | private List<TickPosition> calculatedTimeTicks; |
| 27 | 27 | |
| 28 | 28 | private boolean isZoomed; |
| 29 | - |
|
| 29 | + |
|
| 30 | + private int visibleLabelsInterval = 1; |
|
| 31 | + |
|
| 30 | 32 | private final int TICKCOUNT = 10; |
| 31 | 33 | |
| 32 | 34 | public TimeSlider() { |
| ... | ... | @@ -84,7 +86,7 @@ public class TimeSlider extends SliderBar { |
| 84 | 86 | int tickLeftOffset = lineLeftOffset + (int) pos - (tickWidth / 2); |
| 85 | 87 | tickLeftOffset = Math.min(tickLeftOffset, lineLeftOffset + lineWidth - tickWidth); |
| 86 | 88 | tick.getStyle().setLeft(tickLeftOffset, Unit.PX); |
| 87 | - tick.getStyle().setVisibility(Visibility.VISIBLE); |
|
| 89 | + tick.getStyle().setVisibility(isTickInVisibleRange(tickPosition) ? Visibility.VISIBLE : Visibility.HIDDEN); |
|
| 88 | 90 | } |
| 89 | 91 | } |
| 90 | 92 | |
| ... | ... | @@ -106,7 +108,7 @@ public class TimeSlider extends SliderBar { |
| 106 | 108 | int lineWidth = lineElement.getOffsetWidth(); |
| 107 | 109 | // Create the labels or make them visible |
| 108 | 110 | Double previousValue = null; |
| 109 | - for (int i = 0; i < calculatedTimeTicks.size(); i++) { |
|
| 111 | + for (int i = 0, ticksInVisibleRange = 0; i < calculatedTimeTicks.size(); i++) { |
|
| 110 | 112 | TickPosition tickPosition = calculatedTimeTicks.get(i); |
| 111 | 113 | Element label = null; |
| 112 | 114 | if (i < tickLabelElements.size()) { |
| ... | ... | @@ -142,7 +144,8 @@ public class TimeSlider extends SliderBar { |
| 142 | 144 | labelLeftOffset = Math.min(labelLeftOffset, lineLeftOffset + lineWidth - labelWidth); |
| 143 | 145 | labelLeftOffset = Math.max(labelLeftOffset, lineLeftOffset); |
| 144 | 146 | label.getStyle().setLeft(labelLeftOffset, Unit.PX); |
| 145 | - label.getStyle().setVisibility(Visibility.VISIBLE); |
|
| 147 | + boolean visible = isTickInVisibleRange(tickPosition) && ticksInVisibleRange++ % visibleLabelsInterval == 0; |
|
| 148 | + label.getStyle().setVisibility(visible ? Visibility.VISIBLE : Visibility.HIDDEN); |
|
| 146 | 149 | } |
| 147 | 150 | previousValue = value; |
| 148 | 151 | } |
| ... | ... | @@ -152,6 +155,11 @@ public class TimeSlider extends SliderBar { |
| 152 | 155 | tickLabelElements.get(i).getStyle().setDisplay(Display.NONE); |
| 153 | 156 | } |
| 154 | 157 | } |
| 158 | + |
|
| 159 | + private boolean isTickInVisibleRange(TickPosition tickPosition) { |
|
| 160 | + long tickValue = tickPosition.getPosition().getTime(); |
|
| 161 | + return tickValue >= minValue && tickValue <= maxValue; |
|
| 162 | + } |
|
| 155 | 163 | |
| 156 | 164 | public void clearMarkersAndLabelsAndTicks() { |
| 157 | 165 | clearMarkers(); |
| ... | ... | @@ -245,4 +253,10 @@ public class TimeSlider extends SliderBar { |
| 245 | 253 | public void setZoomed(boolean isZoomed) { |
| 246 | 254 | this.isZoomed = isZoomed; |
| 247 | 255 | } |
| 248 | -} |
|
| 256 | + |
|
| 257 | + @Override |
|
| 258 | + public void onResize() { |
|
| 259 | + visibleLabelsInterval = (TICKCOUNT / Math.max(getOffsetWidth() / 35, 1)) + 1; |
|
| 260 | + super.onResize(); |
|
| 261 | + } |
|
| 262 | +} |
|
| ... | ... | \ No newline at end of file |
java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/TimeTicksCalculator.java
| ... | ... | @@ -1,246 +1,247 @@ |
| 1 | -package com.sap.sse.gwt.client.controls.slider;
|
|
| 2 | -
|
|
| 3 | -import java.util.ArrayList;
|
|
| 4 | -import java.util.Date;
|
|
| 5 | -import java.util.List;
|
|
| 6 | -
|
|
| 7 | -/**
|
|
| 8 | - * This calculator emulates the way the HighCharts charting library is computing the chart ticks
|
|
| 9 | - * in case of a x-axis based on time values.
|
|
| 10 | - */
|
|
| 11 | -public class TimeTicksCalculator {
|
|
| 12 | -
|
|
| 13 | - public TimeTicksCalculator() {
|
|
| 14 | - }
|
|
| 15 | -
|
|
| 16 | - /**
|
|
| 17 | - * Get a normalized tick interval for dates. Returns a configuration object with unit range (interval), count and
|
|
| 18 | - * name. Used to prepare data for getTimeTicks. Previously this logic was part of getTimeTicks, but as getTimeTicks
|
|
| 19 | - * now runs of segments in stock charts, the normalizing logic was extracted in order to prevent it for running over
|
|
| 20 | - * again for each segment having the same interval.
|
|
| 21 | - */
|
|
| 22 | - public NormalizedInterval normalizeTimeTickInterval(long tickInterval) {
|
|
| 23 | - TimeUnits[] units = TimeUnits.values();
|
|
| 24 | -
|
|
| 25 | - TimeUnits unit = units[units.length - 1]; // default unit is years
|
|
| 26 | - long interval = unit.unitInMs;
|
|
| 27 | - int[] multiples = unit.allowedMultiples;
|
|
| 28 | - int count;
|
|
| 29 | -
|
|
| 30 | - // loop through the units to find the one that best fits the tickInterval
|
|
| 31 | - for (int i = 0; i < units.length; i++) {
|
|
| 32 | - unit = units[i];
|
|
| 33 | - interval = unit.unitInMs;
|
|
| 34 | - multiples = unit.allowedMultiples;
|
|
| 35 | -
|
|
| 36 | - if (i + 1 < units.length) {
|
|
| 37 | - // lessThan is in the middle between the highest multiple and the next unit.
|
|
| 38 | - long lessThan = (interval * multiples[multiples.length - 1] + units[i + 1].unitInMs) / 2;
|
|
| 39 | -
|
|
| 40 | - // break and keep the current unit
|
|
| 41 | - if (tickInterval <= lessThan) {
|
|
| 42 | - break;
|
|
| 43 | - }
|
|
| 44 | - }
|
|
| 45 | - }
|
|
| 46 | -
|
|
| 47 | - // prevent 2.5 years intervals, though 25, 250 etc. are allowed
|
|
| 48 | - if (interval == TimeUnits.YEAR.unitInMs && tickInterval < 5 * interval) {
|
|
| 49 | - multiples = new int[] { 1, 2, 5 };
|
|
| 50 | - }
|
|
| 51 | -
|
|
| 52 | - // get the count
|
|
| 53 | - count = (int) normalizeTickInterval(tickInterval / interval, multiples, 1);
|
|
| 54 | -
|
|
| 55 | - return new NormalizedInterval(unit.name(), interval, count);
|
|
| 56 | - }
|
|
| 57 | -
|
|
| 58 | - /**
|
|
| 59 | - * Take an interval and normalize it to multiples of 1, 2, 2.5 and 5
|
|
| 60 | - *
|
|
| 61 | - * @param {Number} interval
|
|
| 62 | - * @param {Array} multiples
|
|
| 63 | - * @param {Number} magnitude
|
|
| 64 | - * @param {Object} options
|
|
| 65 | - */
|
|
| 66 | - public long normalizeTickInterval(long interval, int[] multiples, int magnitude) {
|
|
| 67 | - long normalized;
|
|
| 68 | -
|
|
| 69 | - // round to a tenfold of 1, 2, 2.5 or 5
|
|
| 70 | - normalized = interval / magnitude;
|
|
| 71 | -
|
|
| 72 | - // normalize the interval to the nearest multiple
|
|
| 73 | - for (int i = 0; i < multiples.length; i++) {
|
|
| 74 | - interval = multiples[i];
|
|
| 75 | - int addedValue = i + 1 < multiples.length ? multiples[i + 1] : multiples[i];
|
|
| 76 | - if (normalized <= (multiples[i] + addedValue) / 2) {
|
|
| 77 | - break;
|
|
| 78 | - }
|
|
| 79 | - }
|
|
| 80 | -
|
|
| 81 | - // multiply back to the correct magnitude
|
|
| 82 | - interval *= magnitude;
|
|
| 83 | -
|
|
| 84 | - return interval;
|
|
| 85 | - }
|
|
| 86 | -
|
|
| 87 | - private long makeTime(int year, int month) {
|
|
| 88 | - return makeTime(year, month, 1, 0, 0, 0);
|
|
| 89 | - }
|
|
| 90 | -
|
|
| 91 | - private long makeTime(int year, int month, int date) {
|
|
| 92 | - return makeTime(year, month, date, 0, 0, 0);
|
|
| 93 | - }
|
|
| 94 | -
|
|
| 95 | - @SuppressWarnings("deprecation")
|
|
| 96 | - private long makeTime(int year, int month, int date, int hours, int minutes, int seconds) {
|
|
| 97 | - return new Date(year, month, date, hours, minutes, seconds).getTime();
|
|
| 98 | - }
|
|
| 99 | -
|
|
| 100 | - /**
|
|
| 101 | - * Set the tick positions to a time unit that makes sense, for example on the first of each month or on every
|
|
| 102 | - * Monday. Return an array with the time positions. Used in datetime axes as well as for grouping data on a datetime
|
|
| 103 | - * axis.
|
|
| 104 | - *
|
|
| 105 | - * @param {Object} normalizedInterval The interval in axis values (ms) and the count
|
|
| 106 | - * @param {Number} min The minimum in axis values
|
|
| 107 | - * @param {Number} max The maximum in axis values
|
|
| 108 | - * @param {Number} startOfWeek
|
|
| 109 | - */
|
|
| 110 | - @SuppressWarnings("deprecation")
|
|
| 111 | - public List<TickPosition> calculateTimeTicks(NormalizedInterval normalizedInterval, long min, long max,
|
|
| 112 | - long startOfWeek) {
|
|
| 113 | - List<TickPosition> tickPositions = new ArrayList<TickPosition>();
|
|
| 114 | - int i;
|
|
| 115 | - boolean useUTC = true;
|
|
| 116 | - int minYear = 0; // used in months and years as a basis for Date.UTC()
|
|
| 117 | - Date minDate = new Date(min);
|
|
| 118 | - long interval = normalizedInterval.unitRange;
|
|
| 119 | - int count = (int) normalizedInterval.count;
|
|
| 120 | -
|
|
| 121 | -
|
|
| 122 | - if (interval >= TimeUnits.SECOND.unitInMs) { // second
|
|
| 123 | - minDate.setTime(min - (min % 1000));
|
|
| 124 | - minDate.setSeconds(interval >= TimeUnits.MINUTE.unitInMs ? 0 : count
|
|
| 125 | - * Math.round(minDate.getSeconds() / count));
|
|
| 126 | - }
|
|
| 127 | -
|
|
| 128 | - if (interval >= TimeUnits.MINUTE.unitInMs) { // minute
|
|
| 129 | - minDate.setMinutes(interval >= TimeUnits.HOUR.unitInMs ? 0 : count
|
|
| 130 | - * Math.round(minDate.getMinutes() / count));
|
|
| 131 | - }
|
|
| 132 | -
|
|
| 133 | - if (interval >= TimeUnits.HOUR.unitInMs) { // hour
|
|
| 134 | - minDate.setHours(interval >= TimeUnits.DAY.unitInMs ? 0 : count * Math.round(minDate.getHours() / count));
|
|
| 135 | - }
|
|
| 136 | -
|
|
| 137 | - if (interval >= TimeUnits.DAY.unitInMs) { // day
|
|
| 138 | - minDate.setDate(interval >= TimeUnits.MONTH.unitInMs ? 1 : count * Math.round(minDate.getDate() / count));
|
|
| 139 | - }
|
|
| 140 | -
|
|
| 141 | - if (interval >= TimeUnits.MONTH.unitInMs) { // month
|
|
| 142 | - minDate.setMonth(interval >= TimeUnits.YEAR.unitInMs ? 0 : count * Math.round(minDate.getMonth() + 1 / count) - 1);
|
|
| 143 | - minYear = minDate.getYear() + 1900;
|
|
| 144 | - }
|
|
| 145 | -
|
|
| 146 | - if (interval >= TimeUnits.YEAR.unitInMs) { // year
|
|
| 147 | - minYear -= minYear % count;
|
|
| 148 | - minDate.setYear(minYear - 1900);
|
|
| 149 | - }
|
|
| 150 | -
|
|
| 151 | - // week is a special case that runs outside the hierarchy
|
|
| 152 | - if (interval == TimeUnits.WEEK.unitInMs) {
|
|
| 153 | - // get start of current week, independent of count
|
|
| 154 | - long value = minDate.getDate() - minDate.getDay() + startOfWeek;
|
|
| 155 | - minDate.setDate((int) value);
|
|
| 156 | - }
|
|
| 157 | -
|
|
| 158 | - // get tick positions
|
|
| 159 | - i = 1;
|
|
| 160 | - minYear = minDate.getYear() + 1900;
|
|
| 161 | - int minMonth = minDate.getMonth() + 1;
|
|
| 162 | - int minDateDate = minDate.getDate();
|
|
| 163 | - long time = minDate.getTime();
|
|
| 164 | -
|
|
| 165 | - // iterate and add tick positions at appropriate values
|
|
| 166 | - while (time < max) {
|
|
| 167 | - if(time >= min)
|
|
| 168 | - tickPositions.add(new TickPosition(time));
|
|
| 169 | -
|
|
| 170 | - // if the interval is years, use Date.UTC to increase years
|
|
| 171 | - if (interval == TimeUnits.YEAR.unitInMs) {
|
|
| 172 | - time = makeTime(minYear + i * count, 0);
|
|
| 173 | -
|
|
| 174 | - // if the interval is months, use Date.UTC to increase months
|
|
| 175 | - } else if (interval == TimeUnits.MONTH.unitInMs) {
|
|
| 176 | - time = makeTime(minYear, minMonth + i * count);
|
|
| 177 | -
|
|
| 178 | - // if we're using global time, the interval is not fixed as it jumps
|
|
| 179 | - // one hour at the DST crossover
|
|
| 180 | - } else if (!useUTC && (interval == TimeUnits.DAY.unitInMs || interval == TimeUnits.WEEK.unitInMs)) {
|
|
| 181 | - time = makeTime(minYear, minMonth, minDateDate + i * count * (interval == TimeUnits.DAY.unitInMs ? 1 : 7));
|
|
| 182 | -
|
|
| 183 | - // else, the interval is fixed and we use simple addition
|
|
| 184 | - } else {
|
|
| 185 | - time += interval * count;
|
|
| 186 | - }
|
|
| 187 | -
|
|
| 188 | - i++;
|
|
| 189 | - }
|
|
| 190 | -
|
|
| 191 | - // push the last time
|
|
| 192 | - if(time <= max)
|
|
| 193 | - tickPositions.add(new TickPosition(time));
|
|
| 194 | -
|
|
| 195 | - return tickPositions;
|
|
| 196 | - }
|
|
| 197 | -
|
|
| 198 | - public class NormalizedInterval {
|
|
| 199 | - public String unitName;
|
|
| 200 | - public long unitRange;
|
|
| 201 | - public int count;
|
|
| 202 | -
|
|
| 203 | - public NormalizedInterval(String unitName, long unitRange, int count) {
|
|
| 204 | - this.unitName = unitName;
|
|
| 205 | - this.unitRange = unitRange;
|
|
| 206 | - this.count = count;
|
|
| 207 | - }
|
|
| 208 | -
|
|
| 209 | - @Override
|
|
| 210 | - public String toString() {
|
|
| 211 | - return "NormalizedInterval [unitName=" + unitName + ", unitRange=" + unitRange + ", count=" + count + "]";
|
|
| 212 | - }
|
|
| 213 | - }
|
|
| 214 | -
|
|
| 215 | - public class TickPosition {
|
|
| 216 | - private Date position;
|
|
| 217 | -
|
|
| 218 | - public TickPosition(long time) {
|
|
| 219 | - this.position = new Date(time);
|
|
| 220 | - }
|
|
| 221 | -
|
|
| 222 | - public Date getPosition() {
|
|
| 223 | - return position;
|
|
| 224 | - }
|
|
| 225 | - }
|
|
| 226 | -
|
|
| 227 | - enum TimeUnits {
|
|
| 228 | - MILLISECOND(1, new int[] { 1, 2, 5, 10, 20, 25, 50, 100, 200, 500 }),
|
|
| 229 | - SECOND(1000, new int[] { 1, 2, 5, 10, 15, 30 }),
|
|
| 230 | - MINUTE(60 * 1000, new int[] { 1, 2, 5, 10, 15, 30 }),
|
|
| 231 | - HOUR(60 * 60 * 1000, new int[] { 1, 2, 3, 4, 6, 8, 12 }),
|
|
| 232 | - DAY(24 * 3600000l, new int[] { 1, 2 }),
|
|
| 233 | - WEEK(7 * 24 * 3600000l, new int[] { 1, 2 }),
|
|
| 234 | - MONTH(30 * 24 * 3600000l, new int[] { 1, 2, 3, 4, 6 }),
|
|
| 235 | - YEAR(31556952000l, new int[] {});
|
|
| 236 | -
|
|
| 237 | - int[] allowedMultiples;
|
|
| 238 | -
|
|
| 239 | - long unitInMs;
|
|
| 240 | -
|
|
| 241 | - TimeUnits(long unitInMs, int[] allowedMultiples) {
|
|
| 242 | - this.unitInMs = unitInMs;
|
|
| 243 | - this.allowedMultiples = allowedMultiples;
|
|
| 244 | - }
|
|
| 245 | - }
|
|
| 246 | -}
|
|
| 1 | +package com.sap.sse.gwt.client.controls.slider; |
|
| 2 | + |
|
| 3 | +import java.util.ArrayList; |
|
| 4 | +import java.util.Date; |
|
| 5 | +import java.util.List; |
|
| 6 | + |
|
| 7 | +/** |
|
| 8 | + * This calculator emulates the way the HighCharts charting library is computing the chart ticks |
|
| 9 | + * in case of a x-axis based on time values. |
|
| 10 | + */ |
|
| 11 | +public class TimeTicksCalculator { |
|
| 12 | + |
|
| 13 | + public TimeTicksCalculator() { |
|
| 14 | + } |
|
| 15 | + |
|
| 16 | + /** |
|
| 17 | + * Get a normalized tick interval for dates. Returns a configuration object with unit range (interval), count and |
|
| 18 | + * name. Used to prepare data for getTimeTicks. Previously this logic was part of getTimeTicks, but as getTimeTicks |
|
| 19 | + * now runs of segments in stock charts, the normalizing logic was extracted in order to prevent it for running over |
|
| 20 | + * again for each segment having the same interval. |
|
| 21 | + */ |
|
| 22 | + public NormalizedInterval normalizeTimeTickInterval(long tickInterval) { |
|
| 23 | + TimeUnits[] units = TimeUnits.values(); |
|
| 24 | + |
|
| 25 | + TimeUnits unit = units[units.length - 1]; // default unit is years |
|
| 26 | + long interval = unit.unitInMs; |
|
| 27 | + int[] multiples = unit.allowedMultiples; |
|
| 28 | + int count; |
|
| 29 | + |
|
| 30 | + // loop through the units to find the one that best fits the tickInterval |
|
| 31 | + for (int i = 0; i < units.length; i++) { |
|
| 32 | + unit = units[i]; |
|
| 33 | + interval = unit.unitInMs; |
|
| 34 | + multiples = unit.allowedMultiples; |
|
| 35 | + |
|
| 36 | + if (i + 1 < units.length) { |
|
| 37 | + // lessThan is in the middle between the highest multiple and the next unit. |
|
| 38 | + double lessThan = (interval * multiples[multiples.length - 1] + units[i + 1].unitInMs) / 2; |
|
| 39 | + |
|
| 40 | + // break and keep the current unit |
|
| 41 | + if (tickInterval <= lessThan) { |
|
| 42 | + break; |
|
| 43 | + } |
|
| 44 | + } |
|
| 45 | + } |
|
| 46 | + |
|
| 47 | + // prevent 2.5 years intervals, though 25, 250 etc. are allowed |
|
| 48 | + if (interval == TimeUnits.YEAR.unitInMs && tickInterval < 5 * interval) { |
|
| 49 | + multiples = new int[] { 1, 2, 5 }; |
|
| 50 | + } |
|
| 51 | + |
|
| 52 | + // get the count |
|
| 53 | + double temp = ((double) tickInterval) / ((double) interval); |
|
| 54 | + count = (int) normalizeTickInterval(temp, multiples, 1); |
|
| 55 | + |
|
| 56 | + return new NormalizedInterval(unit.name(), interval, count); |
|
| 57 | + } |
|
| 58 | + |
|
| 59 | + /** |
|
| 60 | + * Take an interval and normalize it to multiples of 1, 2, 2.5 and 5 |
|
| 61 | + * |
|
| 62 | + * @param {Number} interval |
|
| 63 | + * @param {Array} multiples |
|
| 64 | + * @param {Number} magnitude |
|
| 65 | + * @param {Object} options |
|
| 66 | + */ |
|
| 67 | + public long normalizeTickInterval(double interval, int[] multiples, int magnitude) { |
|
| 68 | + double normalized; |
|
| 69 | + |
|
| 70 | + // round to a tenfold of 1, 2, 2.5 or 5 |
|
| 71 | + normalized = interval / magnitude; |
|
| 72 | + |
|
| 73 | + // normalize the interval to the nearest multiple |
|
| 74 | + for (int i = 0; i < multiples.length; i++) { |
|
| 75 | + interval = multiples[i]; |
|
| 76 | + int addedValue = i + 1 < multiples.length ? multiples[i + 1] : multiples[i]; |
|
| 77 | + if (normalized <= (multiples[i] + addedValue) / 2) { |
|
| 78 | + break; |
|
| 79 | + } |
|
| 80 | + } |
|
| 81 | + |
|
| 82 | + // multiply back to the correct magnitude |
|
| 83 | + interval *= magnitude; |
|
| 84 | + |
|
| 85 | + return Math.round(interval); |
|
| 86 | + } |
|
| 87 | + |
|
| 88 | + private long makeTime(int year, int month) { |
|
| 89 | + return makeTime(year, month, 1, 0, 0, 0); |
|
| 90 | + } |
|
| 91 | + |
|
| 92 | + private long makeTime(int year, int month, int date) { |
|
| 93 | + return makeTime(year, month, date, 0, 0, 0); |
|
| 94 | + } |
|
| 95 | + |
|
| 96 | + @SuppressWarnings("deprecation") |
|
| 97 | + private long makeTime(int year, int month, int date, int hours, int minutes, int seconds) { |
|
| 98 | + return new Date(year, month, date, hours, minutes, seconds).getTime(); |
|
| 99 | + } |
|
| 100 | + |
|
| 101 | + /** |
|
| 102 | + * Set the tick positions to a time unit that makes sense, for example on the first of each month or on every |
|
| 103 | + * Monday. Return an array with the time positions. Used in datetime axes as well as for grouping data on a datetime |
|
| 104 | + * axis. |
|
| 105 | + * |
|
| 106 | + * @param {Object} normalizedInterval The interval in axis values (ms) and the count |
|
| 107 | + * @param {Number} min The minimum in axis values |
|
| 108 | + * @param {Number} max The maximum in axis values |
|
| 109 | + * @param {Number} startOfWeek |
|
| 110 | + */ |
|
| 111 | + @SuppressWarnings("deprecation") |
|
| 112 | + public List<TickPosition> calculateTimeTicks(NormalizedInterval normalizedInterval, long min, long max, |
|
| 113 | + long startOfWeek) { |
|
| 114 | + List<TickPosition> tickPositions = new ArrayList<TickPosition>(); |
|
| 115 | + int i; |
|
| 116 | + boolean useUTC = true; |
|
| 117 | + int minYear = 0; // used in months and years as a basis for Date.UTC() |
|
| 118 | + Date minDate = new Date(min); |
|
| 119 | + long interval = normalizedInterval.unitRange; |
|
| 120 | + int count = (int) normalizedInterval.count; |
|
| 121 | + |
|
| 122 | + |
|
| 123 | + if (interval >= TimeUnits.SECOND.unitInMs) { // second |
|
| 124 | + minDate.setTime(min - (min % 1000)); |
|
| 125 | + minDate.setSeconds(interval >= TimeUnits.MINUTE.unitInMs ? 0 : count |
|
| 126 | + * Math.round(minDate.getSeconds() / count)); |
|
| 127 | + } |
|
| 128 | + |
|
| 129 | + if (interval >= TimeUnits.MINUTE.unitInMs) { // minute |
|
| 130 | + minDate.setMinutes(interval >= TimeUnits.HOUR.unitInMs ? 0 : count |
|
| 131 | + * Math.round(minDate.getMinutes() / count)); |
|
| 132 | + } |
|
| 133 | + |
|
| 134 | + if (interval >= TimeUnits.HOUR.unitInMs) { // hour |
|
| 135 | + minDate.setHours(interval >= TimeUnits.DAY.unitInMs ? 0 : count * Math.round(minDate.getHours() / count)); |
|
| 136 | + } |
|
| 137 | + |
|
| 138 | + if (interval >= TimeUnits.DAY.unitInMs) { // day |
|
| 139 | + minDate.setDate(interval >= TimeUnits.MONTH.unitInMs ? 1 : count * Math.round(minDate.getDate() / count)); |
|
| 140 | + } |
|
| 141 | + |
|
| 142 | + if (interval >= TimeUnits.MONTH.unitInMs) { // month |
|
| 143 | + minDate.setMonth(interval >= TimeUnits.YEAR.unitInMs ? 0 : count * Math.round(minDate.getMonth() + 1 / count) - 1); |
|
| 144 | + minYear = minDate.getYear() + 1900; |
|
| 145 | + } |
|
| 146 | + |
|
| 147 | + if (interval >= TimeUnits.YEAR.unitInMs) { // year |
|
| 148 | + minYear -= minYear % count; |
|
| 149 | + minDate.setYear(minYear - 1900); |
|
| 150 | + } |
|
| 151 | + |
|
| 152 | + // week is a special case that runs outside the hierarchy |
|
| 153 | + if (interval == TimeUnits.WEEK.unitInMs) { |
|
| 154 | + // get start of current week, independent of count |
|
| 155 | + long value = minDate.getDate() - minDate.getDay() + startOfWeek; |
|
| 156 | + minDate.setDate((int) value); |
|
| 157 | + } |
|
| 158 | + |
|
| 159 | + // get tick positions |
|
| 160 | + i = 1; |
|
| 161 | + minYear = minDate.getYear() + 1900; |
|
| 162 | + int minMonth = minDate.getMonth() + 1; |
|
| 163 | + int minDateDate = minDate.getDate(); |
|
| 164 | + long time = minDate.getTime(); |
|
| 165 | + |
|
| 166 | + // iterate and add tick positions at appropriate values |
|
| 167 | + while (time < max) { |
|
| 168 | + if(time >= min) |
|
| 169 | + tickPositions.add(new TickPosition(time)); |
|
| 170 | + |
|
| 171 | + // if the interval is years, use Date.UTC to increase years |
|
| 172 | + if (interval == TimeUnits.YEAR.unitInMs) { |
|
| 173 | + time = makeTime(minYear + i * count, 0); |
|
| 174 | + |
|
| 175 | + // if the interval is months, use Date.UTC to increase months |
|
| 176 | + } else if (interval == TimeUnits.MONTH.unitInMs) { |
|
| 177 | + time = makeTime(minYear, minMonth + i * count); |
|
| 178 | + |
|
| 179 | + // if we're using global time, the interval is not fixed as it jumps |
|
| 180 | + // one hour at the DST crossover |
|
| 181 | + } else if (!useUTC && (interval == TimeUnits.DAY.unitInMs || interval == TimeUnits.WEEK.unitInMs)) { |
|
| 182 | + time = makeTime(minYear, minMonth, minDateDate + i * count * (interval == TimeUnits.DAY.unitInMs ? 1 : 7)); |
|
| 183 | + |
|
| 184 | + // else, the interval is fixed and we use simple addition |
|
| 185 | + } else { |
|
| 186 | + time += interval * count; |
|
| 187 | + } |
|
| 188 | + |
|
| 189 | + i++; |
|
| 190 | + } |
|
| 191 | + |
|
| 192 | + // push the last time |
|
| 193 | + if(time <= max) |
|
| 194 | + tickPositions.add(new TickPosition(time)); |
|
| 195 | + |
|
| 196 | + return tickPositions; |
|
| 197 | + } |
|
| 198 | + |
|
| 199 | + public class NormalizedInterval { |
|
| 200 | + public String unitName; |
|
| 201 | + public long unitRange; |
|
| 202 | + public int count; |
|
| 203 | + |
|
| 204 | + public NormalizedInterval(String unitName, long unitRange, int count) { |
|
| 205 | + this.unitName = unitName; |
|
| 206 | + this.unitRange = unitRange; |
|
| 207 | + this.count = count; |
|
| 208 | + } |
|
| 209 | + |
|
| 210 | + @Override |
|
| 211 | + public String toString() { |
|
| 212 | + return "NormalizedInterval [unitName=" + unitName + ", unitRange=" + unitRange + ", count=" + count + "]"; |
|
| 213 | + } |
|
| 214 | + } |
|
| 215 | + |
|
| 216 | + public class TickPosition { |
|
| 217 | + private Date position; |
|
| 218 | + |
|
| 219 | + public TickPosition(long time) { |
|
| 220 | + this.position = new Date(time); |
|
| 221 | + } |
|
| 222 | + |
|
| 223 | + public Date getPosition() { |
|
| 224 | + return position; |
|
| 225 | + } |
|
| 226 | + } |
|
| 227 | + |
|
| 228 | + enum TimeUnits { |
|
| 229 | + MILLISECOND(1l, 1, 2, 5, 10, 20, 25, 50, 100, 200, 500), |
|
| 230 | + SECOND(1000l, 1, 2, 5, 10, 15, 30), |
|
| 231 | + MINUTE(60 * 1000l, 1, 2, 5, 10, 15, 30), |
|
| 232 | + HOUR(60 * 60 * 1000l, 1, 2, 3, 4, 6, 8, 12), |
|
| 233 | + DAY(24 * 3600000l, 1, 2), |
|
| 234 | + WEEK(7 * 24 * 3600000l, 1, 2), |
|
| 235 | + MONTH(30 * 24 * 3600000l, 1, 2, 3, 4, 6), |
|
| 236 | + YEAR(31556952000l); |
|
| 237 | + |
|
| 238 | + int[] allowedMultiples; |
|
| 239 | + |
|
| 240 | + long unitInMs; |
|
| 241 | + |
|
| 242 | + TimeUnits(long unitInMs, int... allowedMultiples) { |
|
| 243 | + this.unitInMs = unitInMs; |
|
| 244 | + this.allowedMultiples = allowedMultiples; |
|
| 245 | + } |
|
| 246 | + } |
|
| 247 | +} |
java/com.sap.sse.gwt/src/com/sap/sse/gwt/resources/css/CommonControls.css
| ... | ... | @@ -169,8 +169,7 @@ body ::-webkit-scrollbar-track { |
| 169 | 169 | |
| 170 | 170 | .gwt-SplitLayoutPanel-EastToggleButton-Panel { |
| 171 | 171 | position: absolute; |
| 172 | - bottom: 77px; |
|
| 173 | - margin-left: -35px; |
|
| 172 | + bottom: -10px; |
|
| 174 | 173 | } |
| 175 | 174 | |
| 176 | 175 | .gwt-SplitLayoutPanel-EastToggleButton { |
| ... | ... | @@ -181,9 +180,14 @@ body ::-webkit-scrollbar-track { |
| 181 | 180 | -moz-transform: rotate(-90deg); |
| 182 | 181 | -o-transform: rotate(-90deg); |
| 183 | 182 | -ms-transform: rotate(-90deg); |
| 183 | + transform: rotate(-90deg); |
|
| 184 | + -webkit-transform-origin: 0 0; |
|
| 185 | + -moz-transform-origin: 0 0; |
|
| 186 | + -o-transform-origin: 0 0; |
|
| 187 | + -ms-transform-origin: 0 0; |
|
| 188 | + transform-origin: 0 0; |
|
| 184 | 189 | padding-right: 25px; |
| 185 | 190 | padding-left: 30px; |
| 186 | - margin-left: -15px; |
|
| 187 | 191 | border-bottom: 1px solid #cecece; |
| 188 | 192 | border-right: 1px solid #cecece; |
| 189 | 193 | border-left: 1px solid #cecece; |
java/com.sap.sse.security.common/pom.xml
| ... | ... | @@ -1,32 +1,32 @@ |
| 1 | -<?xml version="1.0" encoding="UTF-8"?>
|
|
| 2 | -<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
|
| 3 | - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
| 4 | - <modelVersion>4.0.0</modelVersion>
|
|
| 5 | - <parent>
|
|
| 6 | - <artifactId>root</artifactId>
|
|
| 7 | - <groupId>com.sap.sailing</groupId>
|
|
| 8 | - <version>1.0.0-SNAPSHOT</version>
|
|
| 9 | - </parent>
|
|
| 10 | - <artifactId>com.sap.sse.security.common</artifactId>
|
|
| 11 | - <packaging>eclipse-plugin</packaging>
|
|
| 12 | -
|
|
| 13 | - <build>
|
|
| 14 | - <plugins>
|
|
| 15 | - <plugin>
|
|
| 16 | - <groupId>org.eclipse.tycho</groupId>
|
|
| 17 | - <artifactId>tycho-source-plugin</artifactId>
|
|
| 18 | - <version>${tycho-version}</version>
|
|
| 19 | - <executions>
|
|
| 20 | - <execution>
|
|
| 21 | - <id>plugin-source</id>
|
|
| 22 | - <phase>generate-sources</phase>
|
|
| 23 | - <goals>
|
|
| 24 | - <goal>plugin-source</goal>
|
|
| 25 | - </goals>
|
|
| 26 | - </execution>
|
|
| 27 | - </executions>
|
|
| 28 | - </plugin>
|
|
| 29 | - </plugins>
|
|
| 30 | - </build>
|
|
| 31 | -
|
|
| 32 | -</project>
|
|
| 1 | +<?xml version="1.0" encoding="UTF-8"?> |
|
| 2 | +<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" |
|
| 3 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
|
| 4 | + <modelVersion>4.0.0</modelVersion> |
|
| 5 | + <parent> |
|
| 6 | + <artifactId>root</artifactId> |
|
| 7 | + <groupId>com.sap.sailing</groupId> |
|
| 8 | + <version>1.0.0-SNAPSHOT</version> |
|
| 9 | + </parent> |
|
| 10 | + <artifactId>com.sap.sse.security.common</artifactId> |
|
| 11 | + <packaging>eclipse-plugin</packaging> |
|
| 12 | + |
|
| 13 | + <build> |
|
| 14 | + <plugins> |
|
| 15 | + <plugin> |
|
| 16 | + <groupId>org.eclipse.tycho</groupId> |
|
| 17 | + <artifactId>tycho-source-plugin</artifactId> |
|
| 18 | + <version>${tycho-version}</version> |
|
| 19 | + <executions> |
|
| 20 | + <execution> |
|
| 21 | + <id>plugin-source</id> |
|
| 22 | + <phase>generate-sources</phase> |
|
| 23 | + <goals> |
|
| 24 | + <goal>plugin-source</goal> |
|
| 25 | + </goals> |
|
| 26 | + </execution> |
|
| 27 | + </executions> |
|
| 28 | + </plugin> |
|
| 29 | + </plugins> |
|
| 30 | + </build> |
|
| 31 | + |
|
| 32 | +</project> |
java/com.sap.sse.shared.android/META-INF/MANIFEST.MF
| ... | ... | @@ -7,4 +7,6 @@ Bundle-Vendor: SAP |
| 7 | 7 | Bundle-RequiredExecutionEnvironment: JavaSE-1.7 |
| 8 | 8 | Require-Bundle: com.sap.sse.common |
| 9 | 9 | Export-Package: com.sap.sse.concurrent, |
| 10 | + com.sap.sse.shared.media, |
|
| 11 | + com.sap.sse.shared.media.impl, |
|
| 10 | 12 | com.sap.sse.util.impl |
java/com.sap.sse.shared.android/src/com/sap/sse/shared/media/ImageDescriptor.java
| ... | ... | @@ -0,0 +1,14 @@ |
| 1 | +package com.sap.sse.shared.media; |
|
| 2 | + |
|
| 3 | +import com.sap.sse.common.Util.Pair; |
|
| 4 | + |
|
| 5 | +public interface ImageDescriptor extends MediaDescriptor { |
|
| 6 | + void setSize(Pair<Integer, Integer> size); |
|
| 7 | + void setSize(Integer widthInPx, Integer heightInPx); |
|
| 8 | + |
|
| 9 | + Integer getWidthInPx(); |
|
| 10 | + Integer getHeightInPx(); |
|
| 11 | + |
|
| 12 | + boolean hasSize(); |
|
| 13 | + int getArea(); |
|
| 14 | +} |
java/com.sap.sse.shared.android/src/com/sap/sse/shared/media/MediaDescriptor.java
| ... | ... | @@ -0,0 +1,37 @@ |
| 1 | +package com.sap.sse.shared.media; |
|
| 2 | + |
|
| 3 | +import java.io.Serializable; |
|
| 4 | +import java.net.URL; |
|
| 5 | +import java.util.Locale; |
|
| 6 | + |
|
| 7 | +import com.sap.sse.common.TimePoint; |
|
| 8 | +import com.sap.sse.common.media.MimeType; |
|
| 9 | + |
|
| 10 | +/** |
|
| 11 | + * A common media interface for all kinds of media like images or videos. |
|
| 12 | + */ |
|
| 13 | +public interface MediaDescriptor extends Serializable { |
|
| 14 | + MimeType getMimeType(); |
|
| 15 | + URL getURL(); |
|
| 16 | + |
|
| 17 | + String getTitle(); |
|
| 18 | + void setTitle(String title); |
|
| 19 | + |
|
| 20 | + Iterable<String> getTags(); |
|
| 21 | + void setTags(Iterable<String> tags); |
|
| 22 | + boolean addTag(String tagName); |
|
| 23 | + boolean removeTag(String tagName); |
|
| 24 | + boolean hasTag(String tagName); |
|
| 25 | + |
|
| 26 | + String getSubtitle(); |
|
| 27 | + void setSubtitle(String subtitle); |
|
| 28 | + |
|
| 29 | + TimePoint getCreatedAtDate(); |
|
| 30 | + void setCreatedAtDate(TimePoint createdAtDate); |
|
| 31 | + |
|
| 32 | + String getCopyright(); |
|
| 33 | + void setCopyright(String copyright); |
|
| 34 | + |
|
| 35 | + Locale getLocale(); |
|
| 36 | + void setLocale(Locale locale); |
|
| 37 | +} |
java/com.sap.sse.shared.android/src/com/sap/sse/shared/media/MediaUtils.java
| ... | ... | @@ -0,0 +1,110 @@ |
| 1 | +package com.sap.sse.shared.media; |
|
| 2 | + |
|
| 3 | +import java.io.IOException; |
|
| 4 | +import java.net.URL; |
|
| 5 | +import java.net.URLConnection; |
|
| 6 | +import java.util.Iterator; |
|
| 7 | +import java.util.concurrent.Callable; |
|
| 8 | +import java.util.concurrent.ExecutorService; |
|
| 9 | +import java.util.concurrent.Executors; |
|
| 10 | +import java.util.concurrent.Future; |
|
| 11 | +import java.util.logging.Level; |
|
| 12 | +import java.util.logging.Logger; |
|
| 13 | +import java.util.regex.Pattern; |
|
| 14 | + |
|
| 15 | +import javax.imageio.ImageIO; |
|
| 16 | +import javax.imageio.ImageReader; |
|
| 17 | +import javax.imageio.stream.ImageInputStream; |
|
| 18 | + |
|
| 19 | +import com.sap.sse.common.Util; |
|
| 20 | +import com.sap.sse.common.Util.Pair; |
|
| 21 | +import com.sap.sse.common.media.MimeType; |
|
| 22 | + |
|
| 23 | +public class MediaUtils { |
|
| 24 | + private static final Logger logger = Logger.getLogger(MediaUtils.class.getName()); |
|
| 25 | + |
|
| 26 | + /** |
|
| 27 | + * Youtube regex detection from: |
|
| 28 | + * http://stackoverflow.com/questions/3452546/javascript-regex-how-to-get-youtube-video-id-from-url, mantish Mar 4 |
|
| 29 | + * at 15:33 |
|
| 30 | + */ |
|
| 31 | + private static final Pattern YOUTUBE_ID_REGEX = Pattern |
|
| 32 | + .compile("^.*(youtu.be/|v/|u/\\w/|embed/|watch\\?v=|\\&v=)([^#\\&\\?]+).*$"); |
|
| 33 | + |
|
| 34 | + private static final Pattern VIMEO_REGEX = Pattern.compile("^.*(vimeo\\.com\\/).*"); |
|
| 35 | + |
|
| 36 | + private static final Pattern MP4_REGEX = Pattern.compile(".*\\.mp4$"); |
|
| 37 | + |
|
| 38 | + /** |
|
| 39 | + * Detect mimetype for given url. |
|
| 40 | + * |
|
| 41 | + * @param url |
|
| 42 | + * the source pointing to the video mediafile |
|
| 43 | + * @return mimetype detected or MimeType.unknown |
|
| 44 | + */ |
|
| 45 | + public static MimeType detectMimeTypeFromUrl(String url) { |
|
| 46 | + |
|
| 47 | + if (YOUTUBE_ID_REGEX.matcher(url).matches()) { |
|
| 48 | + return MimeType.youtube; |
|
| 49 | + } else if (VIMEO_REGEX.matcher(url).matches()) { |
|
| 50 | + return MimeType.vimeo; |
|
| 51 | + } else if (MP4_REGEX.matcher(url).matches()) { |
|
| 52 | + return MimeType.mp4; |
|
| 53 | + } else { |
|
| 54 | + return MimeType.unknown; |
|
| 55 | + } |
|
| 56 | + } |
|
| 57 | + |
|
| 58 | + public static Pair<Integer, Integer> getImageDimensions(URL imageURL) { |
|
| 59 | + Future<Pair<Integer, Integer>> imageSizeCalculator = getOrCreateImageSizeCalculator(imageURL); |
|
| 60 | + try { |
|
| 61 | + return imageSizeCalculator.get(); |
|
| 62 | + } catch (Exception e) { |
|
| 63 | + return null; |
|
| 64 | + } |
|
| 65 | + } |
|
| 66 | + |
|
| 67 | + private static final ExecutorService executor = Executors.newCachedThreadPool(); |
|
| 68 | + |
|
| 69 | + private static Future<Pair<Integer, Integer>> getOrCreateImageSizeCalculator(final URL imageURL) { |
|
| 70 | + Future<Pair<Integer, Integer>> imageSizeFetcher = executor.submit(new Callable<Pair<Integer, Integer>>() { |
|
| 71 | + @Override |
|
| 72 | + public Pair<Integer, Integer> call() throws IOException { |
|
| 73 | + Pair<Integer, Integer> result = null; |
|
| 74 | + ImageInputStream in = null; |
|
| 75 | + try { |
|
| 76 | + URLConnection conn = imageURL.openConnection(); |
|
| 77 | + in = ImageIO.createImageInputStream(conn.getInputStream()); |
|
| 78 | + final Iterator<ImageReader> readers = ImageIO.getImageReaders(in); |
|
| 79 | + if (readers.hasNext()) { |
|
| 80 | + ImageReader reader = readers.next(); |
|
| 81 | + try { |
|
| 82 | + reader.setInput(in); |
|
| 83 | + result = new Pair<>(reader.getWidth(0), reader.getHeight(0)); |
|
| 84 | + } finally { |
|
| 85 | + reader.dispose(); |
|
| 86 | + } |
|
| 87 | + } |
|
| 88 | + } catch (IOException ioe) { |
|
| 89 | + logger.log(Level.SEVERE, "Stale image URL: "+imageURL, ioe); |
|
| 90 | + throw ioe; |
|
| 91 | + } finally { |
|
| 92 | + if (in != null) { |
|
| 93 | + in.close(); |
|
| 94 | + } |
|
| 95 | + } |
|
| 96 | + return result; |
|
| 97 | + } |
|
| 98 | + }); |
|
| 99 | + return imageSizeFetcher; |
|
| 100 | + } |
|
| 101 | + |
|
| 102 | + public static Util.Pair<Integer, Integer> fitImageSizeToBox(int boxWidth, int boxHeight, int imageWidth, int imageHeight, boolean neverScaleUp) { |
|
| 103 | + double scale = Math.min((double) boxWidth / (double) imageWidth, (double) boxHeight / (double) imageHeight); |
|
| 104 | + |
|
| 105 | + int h = (int) (!neverScaleUp || scale < 1.0 ? scale * imageHeight : imageHeight); |
|
| 106 | + int w = (int) (!neverScaleUp || scale < 1.0 ? scale * imageWidth : imageWidth); |
|
| 107 | + |
|
| 108 | + return new Util.Pair<Integer, Integer>(w,h); |
|
| 109 | + } |
|
| 110 | +} |
java/com.sap.sse.shared.android/src/com/sap/sse/shared/media/VideoDescriptor.java
| ... | ... | @@ -0,0 +1,11 @@ |
| 1 | +package com.sap.sse.shared.media; |
|
| 2 | + |
|
| 3 | +import java.net.URL; |
|
| 4 | + |
|
| 5 | +public interface VideoDescriptor extends MediaDescriptor { |
|
| 6 | + Integer getLengthInSeconds(); |
|
| 7 | + void setLengthInSeconds(Integer lengthInSeconds); |
|
| 8 | + |
|
| 9 | + URL getThumbnailURL(); |
|
| 10 | + void setThumbnailURL(URL thumbnailURL); |
|
| 11 | +} |
java/com.sap.sse.shared.android/src/com/sap/sse/shared/media/WithImages.java
| ... | ... | @@ -0,0 +1,26 @@ |
| 1 | +package com.sap.sse.shared.media; |
|
| 2 | + |
|
| 3 | + |
|
| 4 | +public interface WithImages { |
|
| 5 | + /** |
|
| 6 | + * Returns a non-<code>null</code> live but unmodifiable collection of image resources that can be |
|
| 7 | + * used to represent the event, e.g., on a web page. |
|
| 8 | + * |
|
| 9 | + * @return a non-<code>null</code> value which may be empty |
|
| 10 | + */ |
|
| 11 | + Iterable<ImageDescriptor> getImages(); |
|
| 12 | + |
|
| 13 | + /** |
|
| 14 | + * Replaces the {@link #getImages() current contents of the image sequence by the images in |
|
| 15 | + * <code>images</code>. |
|
| 16 | + * |
|
| 17 | + * @param images |
|
| 18 | + * if <code>null</code>, the internal sequence of images is cleared but remains valid (non- |
|
| 19 | + * <code>null</code>) |
|
| 20 | + */ |
|
| 21 | + void setImages(Iterable<ImageDescriptor> images); |
|
| 22 | + |
|
| 23 | + void addImage(ImageDescriptor image); |
|
| 24 | + |
|
| 25 | + void removeImage(ImageDescriptor image); |
|
| 26 | +} |
java/com.sap.sse.shared.android/src/com/sap/sse/shared/media/WithMedia.java
| ... | ... | @@ -0,0 +1,12 @@ |
| 1 | +package com.sap.sse.shared.media; |
|
| 2 | + |
|
| 3 | +import java.util.List; |
|
| 4 | + |
|
| 5 | +public interface WithMedia extends WithImages, WithVideos { |
|
| 6 | + ImageDescriptor findImageWithTag(String tagName); |
|
| 7 | + List<ImageDescriptor> findImagesWithTag(String tagName); |
|
| 8 | + boolean hasImageWithTag(String tagName); |
|
| 9 | + |
|
| 10 | + VideoDescriptor findVideoWithTag(String tagName); |
|
| 11 | + List<VideoDescriptor> findVideosWithTag(String tagName); |
|
| 12 | +} |
java/com.sap.sse.shared.android/src/com/sap/sse/shared/media/WithVideos.java
| ... | ... | @@ -0,0 +1,27 @@ |
| 1 | +package com.sap.sse.shared.media; |
|
| 2 | + |
|
| 3 | + |
|
| 4 | +public interface WithVideos { |
|
| 5 | + |
|
| 6 | + /** |
|
| 7 | + * Returns a non-<code>null</code> live but unmodifiable collection of video resources that can be |
|
| 8 | + * used to represent the event, e.g., on a web page. |
|
| 9 | + * |
|
| 10 | + * @return a non-<code>null</code> value which may be empty |
|
| 11 | + */ |
|
| 12 | + Iterable<VideoDescriptor> getVideos(); |
|
| 13 | + |
|
| 14 | + /** |
|
| 15 | + * Replaces the {@link #getVideos() current contents of the video sequence by the videos in |
|
| 16 | + * <code>videos</code>. |
|
| 17 | + * |
|
| 18 | + * @param videos |
|
| 19 | + * if <code>null</code>, the internal sequence of videos is cleared but remains valid (non- |
|
| 20 | + * <code>null</code>) |
|
| 21 | + */ |
|
| 22 | + void setVideos(Iterable<VideoDescriptor> videos); |
|
| 23 | + |
|
| 24 | + void addVideo(VideoDescriptor video); |
|
| 25 | + |
|
| 26 | + void removeVideo(VideoDescriptor video); |
|
| 27 | +} |
java/com.sap.sse.shared.android/src/com/sap/sse/shared/media/impl/AbstractMediaDescriptor.java
| ... | ... | @@ -0,0 +1,138 @@ |
| 1 | +package com.sap.sse.shared.media.impl; |
|
| 2 | + |
|
| 3 | +import java.io.Serializable; |
|
| 4 | +import java.net.URL; |
|
| 5 | +import java.util.LinkedHashSet; |
|
| 6 | +import java.util.Locale; |
|
| 7 | +import java.util.Set; |
|
| 8 | + |
|
| 9 | +import com.sap.sse.common.TimePoint; |
|
| 10 | +import com.sap.sse.common.Util; |
|
| 11 | +import com.sap.sse.common.media.MimeType; |
|
| 12 | +import com.sap.sse.shared.media.MediaDescriptor; |
|
| 13 | + |
|
| 14 | +/** |
|
| 15 | + * Common media data for media items |
|
| 16 | + * |
|
| 17 | + * @author pgtaboada |
|
| 18 | + * |
|
| 19 | + */ |
|
| 20 | +public abstract class AbstractMediaDescriptor implements MediaDescriptor, Serializable { |
|
| 21 | + private static final long serialVersionUID = -6671425870632517274L; |
|
| 22 | + |
|
| 23 | + protected String title; |
|
| 24 | + |
|
| 25 | + protected String subtitle; |
|
| 26 | + |
|
| 27 | + protected TimePoint createdAtDate; |
|
| 28 | + |
|
| 29 | + protected String copyright; |
|
| 30 | + |
|
| 31 | + protected MimeType mimeType; |
|
| 32 | + |
|
| 33 | + protected Set<String> tags = new LinkedHashSet<String>(); |
|
| 34 | + |
|
| 35 | + protected URL url; |
|
| 36 | + |
|
| 37 | + protected Locale locale; |
|
| 38 | + |
|
| 39 | + /** |
|
| 40 | + * Media item with minimal set of information |
|
| 41 | + * @param url |
|
| 42 | + * @param mimeType |
|
| 43 | + */ |
|
| 44 | + public AbstractMediaDescriptor(URL url, MimeType mimeType, TimePoint createdAtDate) { |
|
| 45 | + this.mimeType = mimeType; |
|
| 46 | + this.url = url; |
|
| 47 | + this.createdAtDate = createdAtDate; |
|
| 48 | + } |
|
| 49 | + |
|
| 50 | + @Override |
|
| 51 | + public MimeType getMimeType() { |
|
| 52 | + return mimeType; |
|
| 53 | + } |
|
| 54 | + |
|
| 55 | + @Override |
|
| 56 | + public URL getURL() { |
|
| 57 | + return url; |
|
| 58 | + } |
|
| 59 | + |
|
| 60 | + @Override |
|
| 61 | + public String getTitle() { |
|
| 62 | + return title; |
|
| 63 | + } |
|
| 64 | + |
|
| 65 | + @Override |
|
| 66 | + public void setTitle(String title) { |
|
| 67 | + this.title = title; |
|
| 68 | + } |
|
| 69 | + |
|
| 70 | + @Override |
|
| 71 | + public Set<String> getTags() { |
|
| 72 | + return tags; |
|
| 73 | + } |
|
| 74 | + |
|
| 75 | + @Override |
|
| 76 | + public void setTags(Iterable<String> tags) { |
|
| 77 | + this.tags.clear(); |
|
| 78 | + if (tags != null) { |
|
| 79 | + Util.addAll(tags, this.tags); |
|
| 80 | + } |
|
| 81 | + } |
|
| 82 | + |
|
| 83 | + |
|
| 84 | + @Override |
|
| 85 | + public boolean addTag(String tagName) { |
|
| 86 | + return tags.add(tagName); |
|
| 87 | + } |
|
| 88 | + |
|
| 89 | + @Override |
|
| 90 | + public boolean removeTag(String tagName) { |
|
| 91 | + return tags.remove(tagName); |
|
| 92 | + } |
|
| 93 | + |
|
| 94 | + @Override |
|
| 95 | + public String getSubtitle() { |
|
| 96 | + return subtitle; |
|
| 97 | + } |
|
| 98 | + |
|
| 99 | + @Override |
|
| 100 | + public void setSubtitle(String subtitle) { |
|
| 101 | + this.subtitle = subtitle; |
|
| 102 | + } |
|
| 103 | + |
|
| 104 | + @Override |
|
| 105 | + public TimePoint getCreatedAtDate() { |
|
| 106 | + return createdAtDate; |
|
| 107 | + } |
|
| 108 | + |
|
| 109 | + @Override |
|
| 110 | + public void setCreatedAtDate(TimePoint createdAtDate) { |
|
| 111 | + this.createdAtDate = createdAtDate; |
|
| 112 | + } |
|
| 113 | + |
|
| 114 | + @Override |
|
| 115 | + public String getCopyright() { |
|
| 116 | + return copyright; |
|
| 117 | + } |
|
| 118 | + |
|
| 119 | + @Override |
|
| 120 | + public void setCopyright(String copyright) { |
|
| 121 | + this.copyright = copyright; |
|
| 122 | + } |
|
| 123 | + |
|
| 124 | + @Override |
|
| 125 | + public Locale getLocale() { |
|
| 126 | + return locale; |
|
| 127 | + } |
|
| 128 | + |
|
| 129 | + @Override |
|
| 130 | + public void setLocale(Locale locale) { |
|
| 131 | + this.locale = locale; |
|
| 132 | + } |
|
| 133 | + |
|
| 134 | + @Override |
|
| 135 | + public boolean hasTag(String tagName) { |
|
| 136 | + return tags.contains(tagName); |
|
| 137 | + } |
|
| 138 | +} |
java/com.sap.sse.shared.android/src/com/sap/sse/shared/media/impl/ImageDescriptorImpl.java
| ... | ... | @@ -0,0 +1,64 @@ |
| 1 | +package com.sap.sse.shared.media.impl; |
|
| 2 | + |
|
| 3 | +import java.net.URL; |
|
| 4 | + |
|
| 5 | +import com.sap.sse.common.TimePoint; |
|
| 6 | +import com.sap.sse.common.Util.Pair; |
|
| 7 | +import com.sap.sse.common.media.MimeType; |
|
| 8 | +import com.sap.sse.shared.media.ImageDescriptor; |
|
| 9 | + |
|
| 10 | + |
|
| 11 | +public class ImageDescriptorImpl extends AbstractMediaDescriptor implements ImageDescriptor { |
|
| 12 | + private static final long serialVersionUID = -702731462768602331L; |
|
| 13 | + |
|
| 14 | + private Integer widthInPx; |
|
| 15 | + private Integer heightInPx; |
|
| 16 | + |
|
| 17 | + /** |
|
| 18 | + * @param imageURL |
|
| 19 | + * @param size |
|
| 20 | + */ |
|
| 21 | + public ImageDescriptorImpl(URL imageURL, TimePoint createdAtDate) { |
|
| 22 | + super(imageURL, MimeType.image, createdAtDate); |
|
| 23 | + } |
|
| 24 | + |
|
| 25 | + @Override |
|
| 26 | + public Integer getWidthInPx() { |
|
| 27 | + return widthInPx; |
|
| 28 | + } |
|
| 29 | + |
|
| 30 | + @Override |
|
| 31 | + public Integer getHeightInPx() { |
|
| 32 | + return heightInPx; |
|
| 33 | + } |
|
| 34 | + |
|
| 35 | + @Override |
|
| 36 | + public void setSize(Pair<Integer, Integer> size) { |
|
| 37 | + if (size != null) { |
|
| 38 | + this.widthInPx = size.getA(); |
|
| 39 | + this.heightInPx = size.getB(); |
|
| 40 | + } else { |
|
| 41 | + this.widthInPx = null; |
|
| 42 | + this.heightInPx = null; |
|
| 43 | + } |
|
| 44 | + } |
|
| 45 | + |
|
| 46 | + @Override |
|
| 47 | + public void setSize(Integer widthInPx, Integer heightInPx) { |
|
| 48 | + this.widthInPx = widthInPx; |
|
| 49 | + this.heightInPx = heightInPx; |
|
| 50 | + } |
|
| 51 | + |
|
| 52 | + @Override |
|
| 53 | + public boolean hasSize() { |
|
| 54 | + return widthInPx != null && heightInPx != null; |
|
| 55 | + } |
|
| 56 | + |
|
| 57 | + @Override |
|
| 58 | + public int getArea() { |
|
| 59 | + if(hasSize()) { |
|
| 60 | + return widthInPx * heightInPx; |
|
| 61 | + } |
|
| 62 | + return 0; |
|
| 63 | + } |
|
| 64 | +} |
java/com.sap.sse.shared.android/src/com/sap/sse/shared/media/impl/VideoDescriptorImpl.java
| ... | ... | @@ -0,0 +1,42 @@ |
| 1 | +package com.sap.sse.shared.media.impl; |
|
| 2 | + |
|
| 3 | +import java.net.URL; |
|
| 4 | + |
|
| 5 | +import com.sap.sse.common.TimePoint; |
|
| 6 | +import com.sap.sse.common.media.MimeType; |
|
| 7 | +import com.sap.sse.shared.media.VideoDescriptor; |
|
| 8 | + |
|
| 9 | +public class VideoDescriptorImpl extends AbstractMediaDescriptor implements VideoDescriptor { |
|
| 10 | + private static final long serialVersionUID = 2651747912466590862L; |
|
| 11 | + |
|
| 12 | + private Integer lengthInSeconds; |
|
| 13 | + |
|
| 14 | + /** |
|
| 15 | + * URL to thumbnail image. This information works as override for youtube videos or as missing thumbnail |
|
| 16 | + * information for other formats. It can be either a link to thumbnail or even be a data/url contaning the base64 |
|
| 17 | + * encoded image. |
|
| 18 | + */ |
|
| 19 | + private URL thumbnailURL; |
|
| 20 | + |
|
| 21 | + public VideoDescriptorImpl(URL url, MimeType mimeType, TimePoint createdAtDate) { |
|
| 22 | + super(url, mimeType, createdAtDate); |
|
| 23 | + } |
|
| 24 | + |
|
| 25 | + @Override |
|
| 26 | + public Integer getLengthInSeconds() { |
|
| 27 | + return lengthInSeconds; |
|
| 28 | + } |
|
| 29 | + |
|
| 30 | + public void setLengthInSeconds(Integer lengthInSeconds) { |
|
| 31 | + this.lengthInSeconds = lengthInSeconds; |
|
| 32 | + } |
|
| 33 | + |
|
| 34 | + @Override |
|
| 35 | + public URL getThumbnailURL() { |
|
| 36 | + return thumbnailURL; |
|
| 37 | + } |
|
| 38 | + |
|
| 39 | + public void setThumbnailURL(URL thumbnailURL) { |
|
| 40 | + this.thumbnailURL = thumbnailURL; |
|
| 41 | + } |
|
| 42 | +} |
java/com.sap.sse/src/com/sap/sse/ServerInfo.java
| ... | ... | @@ -3,6 +3,8 @@ package com.sap.sse; |
| 3 | 3 | import java.io.BufferedReader; |
| 4 | 4 | import java.io.File; |
| 5 | 5 | import java.io.FileReader; |
| 6 | +import java.util.logging.Level; |
|
| 7 | +import java.util.logging.Logger; |
|
| 6 | 8 | |
| 7 | 9 | /** |
| 8 | 10 | * A class providing static information about the running server. |
| ... | ... | @@ -10,14 +12,16 @@ import java.io.FileReader; |
| 10 | 12 | * |
| 11 | 13 | */ |
| 12 | 14 | public class ServerInfo { |
| 15 | + private static final Logger logger = Logger.getLogger(ServerInfo.class.getName()); |
|
| 16 | + |
|
| 13 | 17 | public static String getBuildVersion() { |
| 14 | 18 | String version = "Unknown or Development (" + ServerStartupConstants.SERVER_NAME + ")"; |
| 15 | 19 | File versionfile = new File(ServerStartupConstants.JETTY_HOME + File.separator + "version.txt"); |
| 16 | 20 | if (versionfile.exists()) { |
| 17 | - try { |
|
| 18 | - version = new BufferedReader(new FileReader(versionfile)).readLine(); |
|
| 21 | + try (BufferedReader bufferedReader = new BufferedReader(new FileReader(versionfile))) { |
|
| 22 | + version = bufferedReader.readLine(); |
|
| 19 | 23 | } catch (Exception ex) { |
| 20 | - /* ignore */ |
|
| 24 | + logger.log(Level.WARNING, "Error trying to obtain version info", ex); |
|
| 21 | 25 | } |
| 22 | 26 | } |
| 23 | 27 | return version; |
java/com.sap.sse/src/com/sap/sse/util/IdentityWrapper.java
| ... | ... | @@ -0,0 +1,34 @@ |
| 1 | +package com.sap.sse.util;
|
|
| 2 | +
|
|
| 3 | +import java.io.Serializable;
|
|
| 4 | +
|
|
| 5 | +/**
|
|
| 6 | + * Implements {@link Object#equals(Object)} and {@link Object#hashCode()} based on the wrapped object's
|
|
| 7 | + * identity, no matter what the object itself defines as {@code equals} and {@code hashCode}.
|
|
| 8 | + *
|
|
| 9 | + * @author Axel Uhl (D043530)
|
|
| 10 | + *
|
|
| 11 | + */
|
|
| 12 | +public class IdentityWrapper<T> implements Serializable {
|
|
| 13 | + private static final long serialVersionUID = 3514488568026067341L;
|
|
| 14 | + private final T t;
|
|
| 15 | +
|
|
| 16 | + public IdentityWrapper(T t) {
|
|
| 17 | + this.t = t;
|
|
| 18 | + }
|
|
| 19 | +
|
|
| 20 | + @Override
|
|
| 21 | + public int hashCode() {
|
|
| 22 | + return System.identityHashCode(t);
|
|
| 23 | + }
|
|
| 24 | +
|
|
| 25 | + @Override
|
|
| 26 | + public boolean equals(Object obj) {
|
|
| 27 | + return (obj instanceof IdentityWrapper<?>) ? ((IdentityWrapper<?>) obj).t == this.t : obj == t;
|
|
| 28 | + }
|
|
| 29 | +
|
|
| 30 | + @Override
|
|
| 31 | + public String toString() {
|
|
| 32 | + return "IdentityWrapper for "+t;
|
|
| 33 | + }
|
|
| 34 | +}
|
java/org.json.simple/pom.xml
| ... | ... | @@ -1,40 +1,40 @@ |
| 1 | -<?xml version="1.0" encoding="UTF-8"?>
|
|
| 2 | -<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
|
| 3 | - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
| 4 | - <modelVersion>4.0.0</modelVersion>
|
|
| 5 | - <parent>
|
|
| 6 | - <artifactId>root</artifactId>
|
|
| 7 | - <groupId>com.sap.sailing</groupId>
|
|
| 8 | - <version>1.0.0-SNAPSHOT</version>
|
|
| 9 | - </parent>
|
|
| 10 | - <artifactId>org.json.simple</artifactId>
|
|
| 11 | - <version>1.1.0-SNAPSHOT</version>
|
|
| 12 | - <packaging>eclipse-plugin</packaging>
|
|
| 13 | - <build>
|
|
| 14 | - <plugins>
|
|
| 15 | - <plugin>
|
|
| 16 | - <groupId>org.eclipse.tycho</groupId>
|
|
| 17 | - <artifactId>tycho-compiler-plugin</artifactId>
|
|
| 18 | - <version>${tycho-version}</version>
|
|
| 19 | - <configuration>
|
|
| 20 | - <source>1.7</source>
|
|
| 21 | - <target>1.7</target>
|
|
| 22 | - </configuration>
|
|
| 23 | - </plugin>
|
|
| 24 | - <plugin>
|
|
| 25 | - <groupId>org.eclipse.tycho</groupId>
|
|
| 26 | - <artifactId>tycho-source-plugin</artifactId>
|
|
| 27 | - <version>${tycho-version}</version>
|
|
| 28 | - <executions>
|
|
| 29 | - <execution>
|
|
| 30 | - <id>plugin-source</id>
|
|
| 31 | - <phase>generate-sources</phase>
|
|
| 32 | - <goals>
|
|
| 33 | - <goal>plugin-source</goal>
|
|
| 34 | - </goals>
|
|
| 35 | - </execution>
|
|
| 36 | - </executions>
|
|
| 37 | - </plugin>
|
|
| 38 | - </plugins>
|
|
| 39 | - </build>
|
|
| 40 | -</project>
|
|
| 1 | +<?xml version="1.0" encoding="UTF-8"?> |
|
| 2 | +<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" |
|
| 3 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
|
| 4 | + <modelVersion>4.0.0</modelVersion> |
|
| 5 | + <parent> |
|
| 6 | + <artifactId>root</artifactId> |
|
| 7 | + <groupId>com.sap.sailing</groupId> |
|
| 8 | + <version>1.0.0-SNAPSHOT</version> |
|
| 9 | + </parent> |
|
| 10 | + <artifactId>org.json.simple</artifactId> |
|
| 11 | + <version>1.1.0-SNAPSHOT</version> |
|
| 12 | + <packaging>eclipse-plugin</packaging> |
|
| 13 | + <build> |
|
| 14 | + <plugins> |
|
| 15 | + <plugin> |
|
| 16 | + <groupId>org.eclipse.tycho</groupId> |
|
| 17 | + <artifactId>tycho-compiler-plugin</artifactId> |
|
| 18 | + <version>${tycho-version}</version> |
|
| 19 | + <configuration> |
|
| 20 | + <source>1.7</source> |
|
| 21 | + <target>1.7</target> |
|
| 22 | + </configuration> |
|
| 23 | + </plugin> |
|
| 24 | + <plugin> |
|
| 25 | + <groupId>org.eclipse.tycho</groupId> |
|
| 26 | + <artifactId>tycho-source-plugin</artifactId> |
|
| 27 | + <version>${tycho-version}</version> |
|
| 28 | + <executions> |
|
| 29 | + <execution> |
|
| 30 | + <id>plugin-source</id> |
|
| 31 | + <phase>generate-sources</phase> |
|
| 32 | + <goals> |
|
| 33 | + <goal>plugin-source</goal> |
|
| 34 | + </goals> |
|
| 35 | + </execution> |
|
| 36 | + </executions> |
|
| 37 | + </plugin> |
|
| 38 | + </plugins> |
|
| 39 | + </build> |
|
| 40 | +</project> |
java/pom.xml
| ... | ... | @@ -134,8 +134,8 @@ |
| 134 | 134 | <module>com.sap.sailing.autoload</module> |
| 135 | 135 | <module>com.sap.sailing.feature.p2build</module> |
| 136 | 136 | <module>com.sap.sailing.polars</module> |
| 137 | - <module>com.sap.sailing.polars.datamining</module> |
|
| 138 | - <module>com.sap.sailing.polars.datamining.shared</module> |
|
| 137 | + <module>com.sap.sailing.polars.datamining</module> |
|
| 138 | + <module>com.sap.sailing.polars.datamining.shared</module> |
|
| 139 | 139 | <module>com.sap.sailing.polars.test</module> |
| 140 | 140 | <!-- |
| 141 | 141 | com.sap.sailing.targetplatform.base |
| ... | ... | @@ -264,15 +264,15 @@ |
| 264 | 264 | <version>${tycho-version}</version> |
| 265 | 265 | <configuration> |
| 266 | 266 | <resolver>p2</resolver> |
| 267 | - <dependency-resolution> |
|
| 268 | - <extraRequirements> |
|
| 269 | - <requirement> |
|
| 270 | - <type>eclipse-plugin</type> |
|
| 271 | - <id>com.google.gwt.dev</id> |
|
| 272 | - <versionRange>0.0.0</versionRange> |
|
| 273 | - </requirement> |
|
| 274 | - </extraRequirements> |
|
| 275 | - </dependency-resolution> |
|
| 267 | + <dependency-resolution> |
|
| 268 | + <extraRequirements> |
|
| 269 | + <requirement> |
|
| 270 | + <type>eclipse-plugin</type> |
|
| 271 | + <id>com.google.gwt.dev</id> |
|
| 272 | + <versionRange>0.0.0</versionRange> |
|
| 273 | + </requirement> |
|
| 274 | + </extraRequirements> |
|
| 275 | + </dependency-resolution> |
|
| 276 | 276 | <target> |
| 277 | 277 | <artifact> |
| 278 | 278 | <groupId>${project.groupId}</groupId> |
java/target/configuration/jetty/etc/jetty.xml
| ... | ... | @@ -31,7 +31,7 @@ |
| 31 | 31 | <Arg name="threadpool"> |
| 32 | 32 | <New id="threadpool" class="org.eclipse.jetty.util.thread.QueuedThreadPool"> |
| 33 | 33 | <Arg name="minThreads" type="int">10</Arg> |
| 34 | - <Arg name="maxThreads" type="int">3000</Arg> |
|
| 34 | + <Arg name="maxThreads" type="int">64</Arg> |
|
| 35 | 35 | <Arg name="idleTimeout" type="int">25000</Arg> |
| 36 | 36 | <Set name="detailedDump">false</Set> |
| 37 | 37 | </New> |
mobile/com.sap.sailing.android.shared/pom.xml
| ... | ... | @@ -1,17 +1,17 @@ |
| 1 | 1 | <?xml version="1.0" encoding="UTF-8"?> |
| 2 | 2 | <project |
| 3 | - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" |
|
| 4 | - xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
|
| 5 | - <modelVersion>4.0.0</modelVersion> |
|
| 6 | - <parent> |
|
| 7 | - <groupId>com.sap.sailing</groupId> |
|
| 8 | - <artifactId>mobile</artifactId> |
|
| 9 | - <version>1.0.0-SNAPSHOT</version> |
|
| 10 | - </parent> |
|
| 11 | - <artifactId>com.sap.sailing.android.shared</artifactId> |
|
| 12 | - <packaging>apklib</packaging> |
|
| 3 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" |
|
| 4 | + xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
|
| 5 | + <modelVersion>4.0.0</modelVersion> |
|
| 6 | + <parent> |
|
| 7 | + <groupId>com.sap.sailing</groupId> |
|
| 8 | + <artifactId>mobile</artifactId> |
|
| 9 | + <version>1.0.0-SNAPSHOT</version> |
|
| 10 | + </parent> |
|
| 11 | + <artifactId>com.sap.sailing.android.shared</artifactId> |
|
| 12 | + <packaging>apklib</packaging> |
|
| 13 | 13 | |
| 14 | - <dependencies> |
|
| 14 | + <dependencies> |
|
| 15 | 15 | <dependency> |
| 16 | 16 | <groupId>com.sap.sailing</groupId> |
| 17 | 17 | <artifactId>org.json.simple</artifactId> |
| ... | ... | @@ -86,12 +86,12 @@ |
| 86 | 86 | <groupId>android</groupId> |
| 87 | 87 | <artifactId>android</artifactId> |
| 88 | 88 | </dependency> |
| 89 | - </dependencies> |
|
| 89 | + </dependencies> |
|
| 90 | 90 | |
| 91 | - <build> |
|
| 92 | - <sourceDirectory>src</sourceDirectory> |
|
| 93 | - <finalName>${project.artifactId}</finalName> |
|
| 94 | - <plugins> |
|
| 91 | + <build> |
|
| 92 | + <sourceDirectory>src</sourceDirectory> |
|
| 93 | + <finalName>${project.artifactId}</finalName> |
|
| 94 | + <plugins> |
|
| 95 | 95 | <plugin> |
| 96 | 96 | <groupId>com.jayway.maven.plugins.android.generation2</groupId> |
| 97 | 97 | <artifactId>android-maven-plugin</artifactId> |
| ... | ... | @@ -101,6 +101,6 @@ |
| 101 | 101 | </sdk> |
| 102 | 102 | </configuration> |
| 103 | 103 | </plugin> |
| 104 | - </plugins> |
|
| 105 | - </build> |
|
| 104 | + </plugins> |
|
| 105 | + </build> |
|
| 106 | 106 | </project> |
mobile/com.sap.sailing.racecommittee.app/pom.xml
| ... | ... | @@ -120,7 +120,7 @@ |
| 120 | 120 | </executions> |
| 121 | 121 | </plugin> |
| 122 | 122 | |
| 123 | - <!-- plugins for generating version file and copying .apk --> |
|
| 123 | + <!-- plugins for generating version file and copying .apk --> |
|
| 124 | 124 | <plugin> |
| 125 | 125 | <groupId>org.apache.maven.plugins</groupId> |
| 126 | 126 | <artifactId>maven-dependency-plugin</artifactId> |
mobile/pom.xml
| ... | ... | @@ -16,13 +16,13 @@ |
| 16 | 16 | <!-- |
| 17 | 17 | <module>com.sap.sailing.racecommittee.app</module> |
| 18 | 18 | <module>com.sap.sailing.android.shared</module> |
| 19 | - <module>com.sap.sailing.adnroid.buoy.positioning.app</module> |
|
| 19 | + <module>com.sap.sailing.adnroid.buoy.positioning.app</module> |
|
| 20 | 20 | <module>com.sap.sailing.android.tracking.app</module> |
| 21 | 21 | --> |
| 22 | 22 | </modules> |
| 23 | 23 | |
| 24 | 24 | <properties> |
| 25 | - <!-- used to copy android apps to the static web dir (auto-update functionality) --> |
|
| 25 | + <!-- used to copy android apps to the static web dir (auto-update functionality) --> |
|
| 26 | 26 | <!-- relative dir as seen from the child modules --> |
| 27 | 27 | <additional-deployment-dir>../../java/com.sap.sailing.www/apps</additional-deployment-dir> |
| 28 | 28 | <!-- redefine in child poms --> |
| ... | ... | @@ -32,21 +32,21 @@ |
| 32 | 32 | |
| 33 | 33 | <dependencyManagement> |
| 34 | 34 | <dependencies> |
| 35 | - <!-- needed on remote repository --> |
|
| 36 | - <dependency> |
|
| 37 | - <groupId>android</groupId> |
|
| 38 | - <artifactId>android</artifactId> |
|
| 39 | - <version>4.4.2_r4</version> |
|
| 40 | - <scope>provided</scope> |
|
| 41 | - </dependency> |
|
| 35 | + <!-- needed on remote repository --> |
|
| 36 | + <dependency> |
|
| 37 | + <groupId>android</groupId> |
|
| 38 | + <artifactId>android</artifactId> |
|
| 39 | + <version>4.4.2_r4</version> |
|
| 40 | + <scope>provided</scope> |
|
| 41 | + </dependency> |
|
| 42 | 42 | |
| 43 | - <!-- needed on remote repository --> |
|
| 44 | - <dependency> |
|
| 45 | - <groupId>android.support</groupId> |
|
| 46 | - <artifactId>compatibility-v4</artifactId> |
|
| 47 | - <version>19</version> |
|
| 48 | - </dependency> |
|
| 49 | - </dependencies> |
|
| 43 | + <!-- needed on remote repository --> |
|
| 44 | + <dependency> |
|
| 45 | + <groupId>android.support</groupId> |
|
| 46 | + <artifactId>compatibility-v4</artifactId> |
|
| 47 | + <version>19</version> |
|
| 48 | + </dependency> |
|
| 49 | + </dependencies> |
|
| 50 | 50 | </dependencyManagement> |
| 51 | 51 | |
| 52 | 52 | <build> |
| ... | ... | @@ -55,90 +55,90 @@ |
| 55 | 55 | <plugin> |
| 56 | 56 | <groupId>com.jayway.maven.plugins.android.generation2</groupId> |
| 57 | 57 | <artifactId>android-maven-plugin</artifactId> |
| 58 | - <version>3.9.0-rc.1</version> |
|
| 59 | - <configuration> |
|
| 60 | - <sdk> |
|
| 61 | - <platform>19</platform> |
|
| 62 | - </sdk> |
|
| 63 | - </configuration> |
|
| 58 | + <version>3.9.0-rc.1</version> |
|
| 59 | + <configuration> |
|
| 60 | + <sdk> |
|
| 61 | + <platform>19</platform> |
|
| 62 | + </sdk> |
|
| 63 | + </configuration> |
|
| 64 | 64 | <extensions>true</extensions> |
| 65 | 65 | </plugin> |
| 66 | - <plugin> |
|
| 67 | - <groupId>org.apache.maven.plugins</groupId> |
|
| 68 | - <artifactId>maven-dependency-plugin</artifactId> |
|
| 69 | - <version>2.8</version> |
|
| 70 | - <executions> |
|
| 71 | - <execution> |
|
| 72 | - <id>copy-installed</id> |
|
| 73 | - <phase>install</phase> |
|
| 74 | - <goals> |
|
| 75 | - <goal>copy</goal> |
|
| 76 | - </goals> |
|
| 77 | - <configuration> |
|
| 78 | - <artifactItems> |
|
| 79 | - <artifactItem> |
|
| 80 | - <groupId>${project.groupId}</groupId> |
|
| 81 | - <artifactId>${project.artifactId}</artifactId> |
|
| 82 | - <version>${project.version}</version> |
|
| 83 | - <type>${project.packaging}</type> |
|
| 84 | - </artifactItem> |
|
| 85 | - </artifactItems> |
|
| 86 | - <outputDirectory>${additional-deployment-dir}</outputDirectory> |
|
| 87 | - <stripVersion>true</stripVersion> |
|
| 88 | - </configuration> |
|
| 89 | - </execution> |
|
| 90 | - </executions> |
|
| 91 | - </plugin> |
|
| 92 | - <plugin> |
|
| 93 | - <groupId>org.apache.maven.plugins</groupId> |
|
| 94 | - <artifactId>maven-antrun-plugin</artifactId> |
|
| 95 | - <version>1.3</version> |
|
| 96 | - <executions> |
|
| 97 | - <execution> |
|
| 98 | - <id>generate-version</id> |
|
| 99 | - <phase>install</phase> |
|
| 100 | - <goals> |
|
| 101 | - <goal>run</goal> |
|
| 102 | - </goals> |
|
| 103 | - <configuration> |
|
| 104 | - <tasks> |
|
| 105 | - <property name="target.dir" value="${additional-deployment-dir}" /> |
|
| 106 | - <property name="target.name" value="${additional-deployment-version-file}" /> |
|
| 107 | - |
|
| 108 | - <echo file="${target.dir}/${target.name}" message="${project.build.finalName}.apk=${app-version}" /> |
|
| 109 | - <echo message="Version file generated in ${target.dir}/${target.name}." /> |
|
| 110 | - </tasks> |
|
| 111 | - </configuration> |
|
| 112 | - </execution> |
|
| 113 | - </executions> |
|
| 114 | - </plugin> |
|
| 115 | - <plugin> |
|
| 116 | - <artifactId>maven-clean-plugin</artifactId> |
|
| 117 | - <version>2.5</version> |
|
| 118 | - <configuration> |
|
| 119 | - <filesets> |
|
| 120 | - <fileset> |
|
| 121 | - <directory>${additional-deployment-dir}</directory> |
|
| 122 | - <includes> |
|
| 123 | - <include>${project.build.finalName}.apk</include> |
|
| 124 | - <include>${additional-deployment-version-file}</include> |
|
| 125 | - </includes> |
|
| 126 | - <excludes> |
|
| 127 | - <exclude>*.gitignore</exclude> |
|
| 128 | - </excludes> |
|
| 129 | - <followSymlinks>false</followSymlinks> |
|
| 130 | - </fileset> |
|
| 131 | - </filesets> |
|
| 132 | - </configuration> |
|
| 133 | - </plugin> |
|
| 134 | - <plugin> |
|
| 135 | - <groupId>org.apache.maven.plugins</groupId> |
|
| 136 | - <artifactId>maven-compiler-plugin</artifactId> |
|
| 137 | - <configuration> |
|
| 138 | - <source>1.7</source> |
|
| 139 | - <target>1.7</target> |
|
| 140 | - </configuration> |
|
| 141 | - </plugin> |
|
| 66 | + <plugin> |
|
| 67 | + <groupId>org.apache.maven.plugins</groupId> |
|
| 68 | + <artifactId>maven-dependency-plugin</artifactId> |
|
| 69 | + <version>2.8</version> |
|
| 70 | + <executions> |
|
| 71 | + <execution> |
|
| 72 | + <id>copy-installed</id> |
|
| 73 | + <phase>install</phase> |
|
| 74 | + <goals> |
|
| 75 | + <goal>copy</goal> |
|
| 76 | + </goals> |
|
| 77 | + <configuration> |
|
| 78 | + <artifactItems> |
|
| 79 | + <artifactItem> |
|
| 80 | + <groupId>${project.groupId}</groupId> |
|
| 81 | + <artifactId>${project.artifactId}</artifactId> |
|
| 82 | + <version>${project.version}</version> |
|
| 83 | + <type>${project.packaging}</type> |
|
| 84 | + </artifactItem> |
|
| 85 | + </artifactItems> |
|
| 86 | + <outputDirectory>${additional-deployment-dir}</outputDirectory> |
|
| 87 | + <stripVersion>true</stripVersion> |
|
| 88 | + </configuration> |
|
| 89 | + </execution> |
|
| 90 | + </executions> |
|
| 91 | + </plugin> |
|
| 92 | + <plugin> |
|
| 93 | + <groupId>org.apache.maven.plugins</groupId> |
|
| 94 | + <artifactId>maven-antrun-plugin</artifactId> |
|
| 95 | + <version>1.3</version> |
|
| 96 | + <executions> |
|
| 97 | + <execution> |
|
| 98 | + <id>generate-version</id> |
|
| 99 | + <phase>install</phase> |
|
| 100 | + <goals> |
|
| 101 | + <goal>run</goal> |
|
| 102 | + </goals> |
|
| 103 | + <configuration> |
|
| 104 | + <tasks> |
|
| 105 | + <property name="target.dir" value="${additional-deployment-dir}" /> |
|
| 106 | + <property name="target.name" value="${additional-deployment-version-file}" /> |
|
| 107 | + |
|
| 108 | + <echo file="${target.dir}/${target.name}" message="${project.build.finalName}.apk=${app-version}" /> |
|
| 109 | + <echo message="Version file generated in ${target.dir}/${target.name}." /> |
|
| 110 | + </tasks> |
|
| 111 | + </configuration> |
|
| 112 | + </execution> |
|
| 113 | + </executions> |
|
| 114 | + </plugin> |
|
| 115 | + <plugin> |
|
| 116 | + <artifactId>maven-clean-plugin</artifactId> |
|
| 117 | + <version>2.5</version> |
|
| 118 | + <configuration> |
|
| 119 | + <filesets> |
|
| 120 | + <fileset> |
|
| 121 | + <directory>${additional-deployment-dir}</directory> |
|
| 122 | + <includes> |
|
| 123 | + <include>${project.build.finalName}.apk</include> |
|
| 124 | + <include>${additional-deployment-version-file}</include> |
|
| 125 | + </includes> |
|
| 126 | + <excludes> |
|
| 127 | + <exclude>*.gitignore</exclude> |
|
| 128 | + </excludes> |
|
| 129 | + <followSymlinks>false</followSymlinks> |
|
| 130 | + </fileset> |
|
| 131 | + </filesets> |
|
| 132 | + </configuration> |
|
| 133 | + </plugin> |
|
| 134 | + <plugin> |
|
| 135 | + <groupId>org.apache.maven.plugins</groupId> |
|
| 136 | + <artifactId>maven-compiler-plugin</artifactId> |
|
| 137 | + <configuration> |
|
| 138 | + <source>1.7</source> |
|
| 139 | + <target>1.7</target> |
|
| 140 | + </configuration> |
|
| 141 | + </plugin> |
|
| 142 | 142 | </plugins> |
| 143 | 143 | </pluginManagement> |
| 144 | 144 | </build> |
pom.xml
| ... | ... | @@ -1,68 +1,68 @@ |
| 1 | 1 | <?xml version="1.0" encoding="UTF-8"?> |
| 2 | 2 | <project |
| 3 | - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" |
|
| 4 | - xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
|
| 5 | - <modelVersion>4.0.0</modelVersion> |
|
| 3 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" |
|
| 4 | + xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
|
| 5 | + <modelVersion>4.0.0</modelVersion> |
|
| 6 | 6 | |
| 7 | - <groupId>com.sap.sailing</groupId> |
|
| 8 | - <artifactId>workspace</artifactId> |
|
| 9 | - <version>1.0.0-SNAPSHOT</version> |
|
| 10 | - <packaging>pom</packaging> |
|
| 7 | + <groupId>com.sap.sailing</groupId> |
|
| 8 | + <artifactId>workspace</artifactId> |
|
| 9 | + <version>1.0.0-SNAPSHOT</version> |
|
| 10 | + <packaging>pom</packaging> |
|
| 11 | 11 | |
| 12 | - <modules> |
|
| 13 | - <module>java</module> |
|
| 14 | - </modules> |
|
| 12 | + <modules> |
|
| 13 | + <module>java</module> |
|
| 14 | + </modules> |
|
| 15 | 15 | |
| 16 | - <profiles> |
|
| 17 | - <profile> |
|
| 18 | - <!-- deactivate with parameter '-P !with-mobile'--> |
|
| 19 | - <id>with-mobile</id> |
|
| 20 | - <activation> |
|
| 21 | - <activeByDefault>true</activeByDefault> |
|
| 22 | - </activation> |
|
| 23 | - <modules> |
|
| 24 | - <module>mobile</module> |
|
| 25 | - </modules> |
|
| 26 | - </profile> |
|
| 27 | - <profile> |
|
| 28 | - <!-- activate with parameter '-P with-ios'--> |
|
| 29 | - <id>with-ios</id> |
|
| 30 | - <modules> |
|
| 31 | - <module>ios</module> |
|
| 32 | - </modules> |
|
| 33 | - </profile> |
|
| 34 | - </profiles> |
|
| 16 | + <profiles> |
|
| 17 | + <profile> |
|
| 18 | + <!-- deactivate with parameter '-P !with-mobile'--> |
|
| 19 | + <id>with-mobile</id> |
|
| 20 | + <activation> |
|
| 21 | + <activeByDefault>true</activeByDefault> |
|
| 22 | + </activation> |
|
| 23 | + <modules> |
|
| 24 | + <module>mobile</module> |
|
| 25 | + </modules> |
|
| 26 | + </profile> |
|
| 27 | + <profile> |
|
| 28 | + <!-- activate with parameter '-P with-ios'--> |
|
| 29 | + <id>with-ios</id> |
|
| 30 | + <modules> |
|
| 31 | + <module>ios</module> |
|
| 32 | + </modules> |
|
| 33 | + </profile> |
|
| 34 | + </profiles> |
|
| 35 | 35 | |
| 36 | - <properties> |
|
| 37 | - <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
|
| 38 | - <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> |
|
| 39 | - </properties> |
|
| 40 | - |
|
| 41 | - <repositories> |
|
| 42 | - <repository> |
|
| 43 | - <id>equinox-sdk-3.9</id> |
|
| 44 | - <layout>p2</layout> |
|
| 45 | - <url>http://download.eclipse.org/releases/luna/</url> |
|
| 46 | - </repository> |
|
| 47 | -<!-- |
|
| 48 | - <repository> |
|
| 49 | - <id>sap-nexus</id> |
|
| 50 | - <url>http://nexus.wdf.sap.corp:8081/nexus/content/groups/build.snapshots</url> |
|
| 51 | - </repository> |
|
| 36 | + <properties> |
|
| 37 | + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
|
| 38 | + <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> |
|
| 39 | + </properties> |
|
| 40 | + |
|
| 41 | + <repositories> |
|
| 42 | + <repository> |
|
| 43 | + <id>equinox-sdk-3.9</id> |
|
| 44 | + <layout>p2</layout> |
|
| 45 | + <url>http://download.eclipse.org/releases/luna/</url> |
|
| 46 | + </repository> |
|
| 47 | +<!-- |
|
| 48 | + <repository> |
|
| 49 | + <id>sap-nexus</id> |
|
| 50 | + <url>http://nexus.wdf.sap.corp:8081/nexus/content/groups/build.snapshots</url> |
|
| 51 | + </repository> |
|
| 52 | 52 | --> |
| 53 | - <repository> |
|
| 54 | - <id>sailing-server-maven</id> |
|
| 55 | - <url>http://maven.sapsailing.com/maven</url> |
|
| 56 | - </repository> |
|
| 53 | + <repository> |
|
| 54 | + <id>sailing-server-maven</id> |
|
| 55 | + <url>http://maven.sapsailing.com/maven</url> |
|
| 56 | + </repository> |
|
| 57 | 57 | |
| 58 | - <repository> |
|
| 59 | - <id>central</id> |
|
| 60 | - <url>http://repo1.maven.org/maven2</url> |
|
| 61 | - </repository> |
|
| 58 | + <repository> |
|
| 59 | + <id>central</id> |
|
| 60 | + <url>http://repo1.maven.org/maven2</url> |
|
| 61 | + </repository> |
|
| 62 | 62 | |
| 63 | - <repository> |
|
| 64 | - <id>uni-luebeck</id> |
|
| 65 | - <url>http://www.itm.uni-luebeck.de/projects/maven/releases</url> |
|
| 66 | - </repository> |
|
| 67 | - </repositories> |
|
| 63 | + <repository> |
|
| 64 | + <id>uni-luebeck</id> |
|
| 65 | + <url>http://www.itm.uni-luebeck.de/projects/maven/releases</url> |
|
| 66 | + </repository> |
|
| 67 | + </repositories> |
|
| 68 | 68 | </project> |
wiki/Log-File-Analysis.md
| ... | ... | @@ -64,33 +64,103 @@ This uses the SetEnvIf module, tries to match an IP address at the start of the |
| 64 | 64 | |
| 65 | 65 | ### Amazon EC2 Elastic Load Balancer (ELB) Logs |
| 66 | 66 | |
| 67 | -An Amazon ELB can be configured to write log files to the S3 storage. The general format is [explained here](http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/access-log-collection.html#access-log-entry-format). It contains in particular the client IP where the request originated and can tell timing parameters for request forwarding and processing that otherwise would not be available. It seems a good idea to always capture these logs for archiving purposes. They end up on an S3 bucket, and by default we use `sapsailing-access-logs`. |
|
| 67 | +An Amazon ELB can be configured to write log files to the S3 storage. The general format is [explained here](http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/access-log-collection.html#access-log-entry-format). It contains in particular the client IP where the request originated and can tell timing parameters for request forwarding and processing that otherwise would not be available. |
|
| 68 | + |
|
| 69 | +The format for these logs is |
|
| 70 | + |
|
| 71 | +<pre> |
|
| 72 | +timestamp elb client:port backend:port request_processing_time backend_processing_time response_processing_time elb_status_code backend_status_code received_bytes sent_bytes "request" "user_agent" ssl_cipher ssl_protocol |
|
| 73 | +</pre> |
|
| 74 | + |
|
| 75 | +It seems a good idea to always capture these logs for archiving purposes. They end up on an S3 bucket, and by default we use `sapsailing-access-logs`. |
|
| 68 | 76 | |
| 69 | 77 | Content can be synced from there to a local directory using the following command: |
| 70 | 78 | |
| 71 | 79 | ``s3cmd sync s3://sapsailing-access-logs/elb-access-logs ./elb-access-logs/`` |
| 72 | 80 | |
| 81 | +Our central web server at `www.sapsailing.com` does this periodically every day, sync'ing the logs to `/var/log/old/elb-access-logs/`. The corresponding cron job is defined in `/etc/cron.daily/syncEC2ElbLogs` which is a script also found in git in the `configuration/` folder. |
|
| 82 | + |
|
| 83 | +### Broken or Partly Missing Logs and How We Recover |
|
| 84 | + |
|
| 85 | +During a few events we unfortunately failed to create proper Apache log files according to the above rules for scenarios using a load-balanced setup ("ELB scenario"). In some cases, the Apache log format was missing the referrer URL. This is easy to patch into the files because we collected the logs on a per-server basis and we knew which server ran which event. For example, for Kieler Woche 2015 and Travemünder Woche 2015 we needed to insert `kielerwoche2015.sapsailing.com` and `tw2015.sapsailing.com`, respectively, to match the general log format used for analysis. |
|
| 86 | + |
|
| 87 | +The other problem, as described already above, is that of the original client IP address. Before we encountered how to log the original client IP from the `X-Forwarded-For` header field, only the ELB's IP addresses were logged in the Apache logs. While counting the correct number of hits is possible with such logs, they don't reveal the true number of unique visitors. They do, however, contain a valid referrer URL, telling us which leaderboards and races were the most popular. |
|
| 88 | + |
|
| 89 | +In case we had an ELB log active, storing the Amazon ELB logs to an S3 bucket, we were able to convert those with a script stored in our git at `configuration/convertELBLogToApacheFormat` into our general Apache log format. This allows us to count the unique visitors for those events, but the ELB logs don't contain the referrer URL, so we patch that to always be the plain event URL, such as `http://kielerwoche2015.sapsailing.com`. Therefore, while these converted logs tell the correct number of unique visitors and the correct number of hits, they don't allow for an analysis of the most popular leaderboards and races. |
|
| 90 | + |
|
| 91 | +We hope that these partially-logged events remain the exception and that future events are logged homogeneously according to the rules above. The following naming conventions have been established under `/var/log/old` that shall allow the automatic log analyzers to identify the log file contents by file name patterns: |
|
| 92 | + |
|
| 93 | + - `access_log*`: A proper Apache log file with virtual host name as the first field, followed by the original client's IP address and a valid referrer URL as the second-to-last field |
|
| 94 | + - `elb-origin-access_log*`: An Apache log file whose origin IP addresses are usually only the ELB IP addresses, therefore not allowing for unique visitor analysis |
|
| 95 | + - `bundesliga2015_elb_access_log.gz` and `tw2015_elb_access_log.gz`: ELB log files converted to Apache format, appended in order of ascending time stamps, prefixed with the virtual host name as the first field, using the original client IP address which allows for unique visitor analysis, and the virtual host name used again as generic referrer URL |
|
| 96 | + - `original-access_log*`: Apache log file without leading virtual host name field, also usually only with ELB IP addresses instead of an original client IP; not good for any log file analysis without further conversion, but kept for reference purposes |
|
| 97 | + |
|
| 73 | 98 | ## Automatic Log File Rotation to /var/log/old |
| 74 | 99 | |
| 75 | - - describe how logrotate is used and automatically configured during instance start-up and where the logs go |
|
| 100 | +When an EC2 server is launched from its Amazon Machine Image (AMI), the /etc/init.d/sailing script is executed with the "start" parameter. This script is really just a link to `/home/sailing/code/configuration/sailing` which comes from the git version checked out to the workspace at `/home/sailing/code`. This script patches the file `/etc/logrotate.d/httpd` such that when log rotation happens, the existence of the directory `/var/log/old/$SERVER_NAME/$SERVER_IP` is ensured and the log files are copied there. |
|
| 101 | + |
|
| 102 | +This way, logs end up in a per server-name and per server-ip directory. |
|
| 103 | + |
|
| 104 | +The general log rotation rules (size, time, compression, etc.) is governed by `/etc/logrotate.conf`. |
|
| 76 | 105 | |
| 77 | 106 | ## Analysis Tools |
| 78 | 107 | |
| 79 | 108 | ### goaccess |
| 80 | 109 | |
| 81 | - - mention specific parameters and .goaccess config file required to parse our format |
|
| 110 | +The file `/root/.goaccessrc` on `www.sapsailing.com` looks as follows: |
|
| 111 | + |
|
| 112 | +``` |
|
| 113 | +color_scheme 1 |
|
| 114 | +date_format %d/%b/%Y |
|
| 115 | +time_format %H:%M:%S |
|
| 116 | +log_format %^ %h %^[%d:%^] "%r" %s %b "%R" "%u" |
|
| 117 | +log_file STDIN |
|
| 118 | +``` |
|
| 119 | + |
|
| 120 | +Still, specific parameters are required for `goaccess` in order to parse our regular Apache logs that have the virtual host name as their first field: |
|
| 121 | + |
|
| 122 | +`goaccess --no-global-config -a -f /var/log/httpd/access_log` |
|
| 123 | + |
|
| 124 | +The unique visitors that goaccess lists are based on the common definition that counts requests as originating from the same visitor if they happened on the same day with equal user agent and IP address. |
|
| 82 | 125 | |
| 83 | 126 | ### apachetop |
| 84 | 127 | |
| 128 | +The simple command `apachetop` shows the traffic on the Apache httpd server running on the host where the command is invoked, based on the access log file in the usual location `/var/log/httpd/access_log`. |
|
| 129 | + |
|
| 85 | 130 | ### AWStats |
| 86 | 131 | |
| 87 | - - specific log collection approach configured in /etc/awstats/*.conf script |
|
| 132 | +We use AWStats to analyze our Apache access log files and produce per-month reports at [http://awstats.sapsailing.com](http://awstats.sapsailing.com/awstats/awstats.pl?output=main&config=www.sapsailing.com&framename=index). The username is `awstats`. Ask axel.uhl@sap.com or stefan.lacher@sap.com for the password. |
|
| 133 | + |
|
| 134 | +AWStats report production is controlled by three things: a cron job hooked up by `/etc/cron.weekly/awstats` which is basically a one-liner launching the `awstats` command like this: |
|
| 88 | 135 | |
| 89 | - - extension for per-virtual-host hit count |
|
| 136 | +`exec /usr/share/awstats/tools/awstats_updateall.pl now -configdir="/etc/awstats" -awstatsprog="/usr/share/awstats/wwwroot/cgi-bin/awstats.pl"` |
|
| 90 | 137 | |
| 91 | - - configuration of central apache to present results (awstats.sapsailing.com) |
|
| 138 | +and a configuration file located at `/etc/awstats/awstats.www.sapsailing.com.conf` which describes in its `LogFile` directive the filename pattern to use for collecting the log files that shall be analyzed. Currently, the file name pattern is this: |
|
| 139 | + |
|
| 140 | +`/var/log/httpd/access_log /var/log/old/access_log-???????? /var/log/old/access_log-????????.gz /var/log/old/*/elb-origin-access_log* /var/log/old/*/*/elb-origin-access_log* /var/log/old/*/access_log* /var/log/old/*/*/access_log*` |
|
| 141 | + |
|
| 142 | +and all log entries that contain `HealthChecker` are eliminated as they are only a "ping" request sent by the load balancer to check the instance's health status which is not to be counted as a "hit." Note that according to the above explanations of our log file formats and variants this focuses on hit analysis for those events with broken or partial / incomplete log files such as Kieler Woche 2015 and Travemünder Woche 2015. It uses the `elb-origin-*` flavors, tolerating the fact that these don't have the original client's IP address. |
|
| 143 | + |
|
| 144 | +The third element of AWStats configuration is how it is published through Apache. This is described in `/etc/httpd/conf.d/awstats.conf` and uses the password definition file `/etc/httpd/conf/passwd.awstats` which can be updated using the `htpasswd` command. |
|
| 92 | 145 | |
| 93 | 146 | ### Custom Scripts |
| 94 | 147 | |
| 95 | - - unique_ips_per_referrer |
|
| 96 | - - convertELBLogToApacheFormat |
|
| ... | ... | \ No newline at end of file |
| 0 | +#### `unique_ips_per_referrer` |
|
| 1 | + |
|
| 2 | +The script is located in git at `configuration/unique_ips_per_referrer`. It has two variants next to it: `unique_ips_per_referrer_generate_results_only` and `unique_ips_per_referrer_generate_month_results_only`. The basic idea of this family of scripts is to count the unique visitors for each virtual host name (usually corresponding to an event), in total and by month. Neither the `goaccess` nor the AWStats tool provide us with these numbers. AWStats can only compute the _hits_ per virtual host name, and `goaccess` only lists unique visitors per day, not per virtual host. |
|
| 3 | + |
|
| 4 | +The script can be fed with a list of log files in our common Apache format, with virtual host name in first, original client IP in second, time stamp in third and user agent in last column. For all new files (files already analyzed are recorded in `/var/log/old/cache/unique-ips-per-referrer/visited) the script then reduces each line to its timestamp, original client IP and user agent fields, constituting the key for identifying a unique visitor. Each log entry that has been analyzed and reduced this way is then appended to a file under `/var/log/old/cache/unique-ips-per-referrer/stats/<virtual-host-name>.ips`. When done with all inputs, the `*.ips` files produced are filtered for unique entries (using `sort -u`) and number of lines are counted, resulting in the total number of unique visitors per virtual host name since the beginning of our log file records. |
|
| 5 | + |
|
| 6 | +Additionally, a per-month analysis is carried out by splitting the `*.ips` files by the months found in the time stamps and again applying a unique count. The resulting files can be found at `/var/log/old/cache/unique-ips-per-referrer/stats/unique-ips-days-useragents-per-event` and `/var/log/old/cache/unique-ips-per-referrer/stats/unique-ips-days-useragents-per-event-MMM-YYYY` where `MMM` represents the three-letter month name such as `Apr` and `YYYY` stands for the year. |
|
| 7 | + |
|
| 8 | +The `unique_ips_per_referrer` script is registered as a weekly cron job in `/etc/cron.weekly` and is fed the special converted log files of Travemünder Woche 2015 and the Bundesliga logs from 2015, as well as all files matching the file name pattern `access_log-*` anywhere under `/var/log/old`. This only matches proper Apache log files with original client IP addresses, not those with ELB IP addresses only (which are named `elb-origin-access_log*` by convention, see above). |
|
| 9 | + |
|
| 10 | +The variant `unique_ips_per_referrer_generate_results_only` assumes that the `*.ips` files have already been updated and produces the total and per-month output files. |
|
| 11 | + |
|
| 12 | +The variant `unique_ips_per_referrer_generate_month_results_only` produces only the per-month output files, also assuming that the `*.ips` files have already been updated. |
|
| 13 | + |
|
| 14 | +The results are published under the link [http://awstats.sapsailing.com/unique-visitors/](http://awstats.sapsailing.com/unique-visitors/) which uses the same credentials as the main AWStats publishing page. |
|
| 15 | + |
|
| 16 | +#### `convertELBLogToApacheFormat` |
|
| 17 | + |
|
| 18 | +This script was already briefly mentioned above. It is found at `configuration/convertELBLogToApacheFormat` and converts and Amazon Elastic Load Balancer (ELB) log file to our common Apache log file format with leading virtual host name. The virtual host name is expected as the first parameter; all subsequent parameters are treated as file names of ELB log files, either in GZIP format with `.gz` extension or uncompressed. The conversion result is streamed to the standard output and may therefore be redirected into a file. |
|
| ... | ... | \ No newline at end of file |
wiki/creating-ec2-image-from-scratch.md
| ... | ... | @@ -19,7 +19,8 @@ I then did a `yum update` and added the following packages: |
| 19 | 19 | - goaccess |
| 20 | 20 | - postfix (for sending e-mail, e.g., to invite competitors and buoy pingers) |
| 21 | 21 | - tigervnc-server |
| 22 | - - xdm (to have a rudimentary window manager available for the VNC server) |
|
| 22 | + - WindowMaker |
|
| 23 | + - xterm |
|
| 23 | 24 | |
| 24 | 25 | Then I created a mount point /home/sailing and copied the following lines from the /etc/fstab file from an existing SL instance: |
| 25 | 26 |
wiki/onboarding.md
| ... | ... | @@ -67,11 +67,13 @@ First of all, make sure you've looked at http://www.amazon.de/Patterns-Elements- |
| 67 | 67 | * Rebuild all projects |
| 68 | 68 | 4. Run the Race Analysis Suite |
| 69 | 69 | * Start the MongoDB |
| 70 | - * Start the appropriate Eclipse launch configuration (e.g. 'Sailing Server (Proxy)') You´ll find this in the run dropdown |
|
| 71 | - * Run "Security UI sdm" in the run dropdown |
|
| 72 | - * Run "SailingGWT" in the run dropdown |
|
| 73 | -5. Within the Race Analysis Suite |
|
| 70 | + * Start the appropriate Eclipse launch configuration (e.g. 'Sailing Server (Proxy)') You´ll find this in the debug dropdown |
|
| 71 | + * Run "Security UI sdm" in the debug dropdown |
|
| 72 | + * Run "SailingGWT" in the debug dropdown |
|
| 73 | +5. Import races within the Race Analysis Suite |
|
| 74 | + * Choose "Security UI sdm" in the upper left corner of the "Development Mode" Tab in Eclipse and open "...Login.html" in your browser |
|
| 74 | 75 | * Default Login: user "admin", password "admin" |
| 76 | + * Choose "Sailing GWT" in the "Development Mode" Tab and open "...AdminConsole.html..." (It is normal that the first try fails. Reload the page after the first try) |
|
| 75 | 77 | * For TracTrac Events: (Date 27.11.2012) Use Live URI tcp://10.18.22.156:4412, Stored URI tcp://10.18.22.156:4413, JSON URL http://germanmaster.traclive.dk/events/event_20120905_erEuropean/jsonservice.php |
| 76 | 78 | * Press List Races |
| 77 | 79 |