+ * E.g. {@code [1,2]} denotes that the passing instruction is applicable for {@code ControlPoints} with either one
+ * or two marks.
+ */
+ public final int[] applicability;
+
+ private PassingInstruction(int... applicability) {
+ this.applicability = applicability;
+ }
}
\ No newline at end of file
diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ScoringSchemeType.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ScoringSchemeType.java
index b49b524d5cb..687511c2430 100755
--- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ScoringSchemeType.java
+++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ScoringSchemeType.java
@@ -1,6 +1,6 @@
package com.sap.sailing.domain.common;
public enum ScoringSchemeType {
- LOW_POINT, HIGH_POINT, HIGH_POINT_ESS_OVERALL, HIGH_POINT_LAST_BREAKS_TIE,
- HIGH_POINT_FIRST_GETS_TEN, HIGH_POINT_FIRST_GETS_ONE, LOW_POINT_WINNER_GETS_ZERO
+ LOW_POINT, HIGH_POINT, HIGH_POINT_ESS_OVERALL, HIGH_POINT_LAST_BREAKS_TIE, HIGH_POINT_FIRST_GETS_TEN,
+ HIGH_POINT_FIRST_GETS_ONE, LOW_POINT_WINNER_GETS_ZERO, HIGH_POINT_WINNER_GETS_SIX
}
diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/TimeRange.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/TimeRange.java
new file mode 100644
index 00000000000..600474436a5
--- /dev/null
+++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/TimeRange.java
@@ -0,0 +1,67 @@
+package com.sap.sailing.domain.common;
+
+import java.io.Serializable;
+
+public interface TimeRange extends Comparable
+ * This method does not need to be implemented, but can remain a stub, if no fallback behaviour
+ * is desired.
+ */
+ void setFallbackService(ServiceType fallback);
+}
diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/TypeBasedServiceFinderFactory.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/TypeBasedServiceFinderFactory.java
new file mode 100644
index 00000000000..2b03570cf5a
--- /dev/null
+++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/tracking/TypeBasedServiceFinderFactory.java
@@ -0,0 +1,18 @@
+package com.sap.sailing.domain.common.racelog.tracking;
+
+
+/**
+ * A factory for creating {@link TypeBasedServiceFinder}s, for a specific type of service.
+ * The desired service type is specified by the {@link Class} object passed to the
+ * {@link #createServiceFinder(Class)} method.
+ *
+ * The created {@link TypeBasedServiceFinder} can then be used to find the desired
+ * service for a specific type. E.g., there may be a persistence service interface for
+ * {@link GPSFix}es, and there are several different service registered, depending on the type
+ * of {@link GPSFix}. The service finder then can find the appropriate service for each fix.
+ * @author Fredrik Teschke
+ *
+ */
+public interface TypeBasedServiceFinderFactory {
+ ValueType and the AveragesTo type are both the same because
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/IsScalable.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/IsScalable.java
similarity index 73%
rename from java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/IsScalable.java
rename to java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/IsScalable.java
index cf5d21aa3bd..4789e8a2a3d 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/IsScalable.java
+++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/IsScalable.java
@@ -1,4 +1,4 @@
-package com.sap.sailing.domain.confidence;
+package com.sap.sailing.domain.common.scalablevalue;
import java.io.Serializable;
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/ScalableValue.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/ScalableValue.java
similarity index 81%
rename from java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/ScalableValue.java
rename to java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/ScalableValue.java
index a65e5696786..61ae9bbf7ca 100755
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/confidence/ScalableValue.java
+++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/ScalableValue.java
@@ -1,4 +1,4 @@
-package com.sap.sailing.domain.confidence;
+package com.sap.sailing.domain.common.scalablevalue;
public interface ScalableValuewindSource on the trackedRace. Each
- * update received will be appended to the MongoDB and can later be retrieved. The key used to identify the race is
- * the {@link RaceDefinition#getName() race name} and the {@link Regatta#getName() regatta name}.
- */
- void addWindTrackDumper(TrackedRegatta trackedRegatta, TrackedRace trackedRace, WindSource windSource);
-
- /**
- * Stores the configuration data of leaderboard in the Mongo DB associated with this
- * factory.
- */
- void storeLeaderboard(Leaderboard leaderboard);
-
- /**
- * Removes the leaderboard named name from the database.
- */
- void removeLeaderboard(String leaderboardName);
-
- void renameLeaderboard(String oldName, String newName);
-
- /**
- * Stores the group, if it doesn't exist or updates it. Leaderboards in the group, which aren't stored in the
- * database, will be stored. If the leaderboard group has an {@link LeaderboardGroup#getOverallLeaderboard() overall
- * leaderboard}, it will be stored / updated as well.
- */
- void storeLeaderboardGroup(LeaderboardGroup leaderboardGroup);
-
- /**
- * Removes the group with the name groupName from the database.
- */
- void removeLeaderboardGroup(String groupName);
-
- /**
- * Renames the group with the name oldName.
- */
- void renameLeaderboardGroup(String oldName, String newName);
-
- /**
- * Stores the event with its name, venue and the venue's course areas. The regattas obtained by
- * {@link Event#getRegattas()} are not stored by this call. They need to be stored separately
- * by calls to {@link #storeRegatta} where a reference to their owning event is stored.
- */
- void storeEvent(Event event);
-
- /**
- * Renames the event with the name oldName.
- */
- void renameEvent(Serializable id, String newName);
-
- /**
- * Removes the event named eventName from the database.
- */
- void removeEvent(Serializable id);
-
- /**
- * Stores the regatta together with its name, {@link Series} definitions and an optional link to the
- * {@link Event} to which the regatta belongs.
- * @param oldSeriesNameNewName
- */
- void storeRegatta(Regatta regatta);
-
- void removeRegatta(Regatta regatta);
-
- void storeRegattaForRaceID(String id, Regatta regatta);
-
- void removeRegattaForRaceID(String raceIDAsString, Regatta regatta);
-
- /**
- * Stores a competitor, including the team and boat. This should not be done for competitors for which
- * the master data is supplied by other systems, such as TracTrac, but rather for smartphone tracking,
- * where this data is otherwise not recoverable.
- * @param competitor the competitor to store/update in the database
- */
- void storeCompetitor(Competitor competitor);
-
- void removeAllCompetitors();
-
- void removeCompetitor(Competitor competitor);
-
- DB getDatabase();
-
- void storeDeviceConfiguration(DeviceConfigurationMatcher matcher, DeviceConfiguration configuration);
-
- void removeDeviceConfiguration(DeviceConfigurationMatcher matcher);
-
- void removeRaceLog(RaceLogIdentifier identifier);
-
-}
+package com.sap.sailing.domain.persistence;
+
+import java.io.Serializable;
+
+import com.mongodb.DB;
+import com.mongodb.DBObject;
+import com.sap.sailing.domain.base.Competitor;
+import com.sap.sailing.domain.base.Event;
+import com.sap.sailing.domain.base.RaceDefinition;
+import com.sap.sailing.domain.base.Regatta;
+import com.sap.sailing.domain.base.Series;
+import com.sap.sailing.domain.base.configuration.DeviceConfiguration;
+import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher;
+import com.sap.sailing.domain.common.WindSource;
+import com.sap.sailing.domain.leaderboard.Leaderboard;
+import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
+import com.sap.sailing.domain.racelog.RaceLogIdentifier;
+import com.sap.sailing.domain.tracking.TrackedRace;
+import com.sap.sailing.domain.tracking.TrackedRegatta;
+
+/**
+ * Offers methods to construct {@link DBObject MongoDB objects} from domain objects.
+ *
+ * @author Axel Uhl (d043530)
+ *
+ */
+public interface MongoObjectFactory {
+ /**
+ * Registers for changes of the wind coming from windSource on the trackedRace. Each
+ * update received will be appended to the MongoDB and can later be retrieved. The key used to identify the race is
+ * the {@link RaceDefinition#getName() race name} and the {@link Regatta#getName() regatta name}.
+ */
+ void addWindTrackDumper(TrackedRegatta trackedRegatta, TrackedRace trackedRace, WindSource windSource);
+
+ /**
+ * Stores the configuration data of leaderboard in the Mongo DB associated with this
+ * factory.
+ */
+ void storeLeaderboard(Leaderboard leaderboard);
+
+ /**
+ * Removes the leaderboard named name from the database.
+ */
+ void removeLeaderboard(String leaderboardName);
+
+ void renameLeaderboard(String oldName, String newName);
+
+ /**
+ * Stores the group, if it doesn't exist or updates it. Leaderboards in the group, which aren't stored in the
+ * database, will be stored. If the leaderboard group has an {@link LeaderboardGroup#getOverallLeaderboard() overall
+ * leaderboard}, it will be stored / updated as well.
+ */
+ void storeLeaderboardGroup(LeaderboardGroup leaderboardGroup);
+
+ /**
+ * Removes the group with the name groupName from the database.
+ */
+ void removeLeaderboardGroup(String groupName);
+
+ /**
+ * Renames the group with the name oldName.
+ */
+ void renameLeaderboardGroup(String oldName, String newName);
+
+ /**
+ * Stores the event with its name, venue and the venue's course areas. The regattas obtained by
+ * {@link Event#getRegattas()} are not stored by this call. They need to be stored separately
+ * by calls to {@link #storeRegatta} where a reference to their owning event is stored.
+ */
+ void storeEvent(Event event);
+
+ /**
+ * Renames the event with the name oldName.
+ */
+ void renameEvent(Serializable id, String newName);
+
+ /**
+ * Removes the event named eventName from the database.
+ */
+ void removeEvent(Serializable id);
+
+ /**
+ * Stores the regatta together with its name, {@link Series} definitions and an optional link to the
+ * {@link Event} to which the regatta belongs.
+ * @param oldSeriesNameNewName
+ */
+ void storeRegatta(Regatta regatta);
+
+ void removeRegatta(Regatta regatta);
+
+ void storeRegattaForRaceID(String id, Regatta regatta);
+
+ void removeRegattaForRaceID(String raceIDAsString, Regatta regatta);
+
+ /**
+ * Stores a competitor, including the team and boat. This should not be done for competitors for which
+ * the master data is supplied by other systems, such as TracTrac, but rather for smartphone tracking,
+ * where this data is otherwise not recoverable.
+ * @param competitor the competitor to store/update in the database
+ */
+ void storeCompetitor(Competitor competitor);
+
+ void removeAllCompetitors();
+
+ void removeCompetitor(Competitor competitor);
+
+ DB getDatabase();
+
+ void storeDeviceConfiguration(DeviceConfigurationMatcher matcher, DeviceConfiguration configuration);
+
+ void removeDeviceConfiguration(DeviceConfigurationMatcher matcher);
+ void removeRaceLog(RaceLogIdentifier identifier);
+
+}
diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/PersistenceFactory.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/PersistenceFactory.java
index 9c07d6e31cd..c70f7cf4348 100755
--- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/PersistenceFactory.java
+++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/PersistenceFactory.java
@@ -1,6 +1,7 @@
package com.sap.sailing.domain.persistence;
import com.sap.sailing.domain.base.DomainFactory;
+import com.sap.sailing.domain.common.racelog.tracking.TypeBasedServiceFinderFactory;
import com.sap.sailing.domain.persistence.impl.PersistenceFactoryImpl;
import com.sap.sailing.mongodb.MongoDBService;
@@ -9,6 +10,9 @@ public interface PersistenceFactory {
DomainObjectFactory getDefaultDomainObjectFactory();
DomainObjectFactory getDomainObjectFactory(MongoDBService mongoDBService, DomainFactory baseDomainFactory);
+ DomainObjectFactory getDomainObjectFactory(MongoDBService mongoDBService, DomainFactory baseDomainFactory, TypeBasedServiceFinderFactory serviceFinderFactory);
MongoObjectFactory getDefaultMongoObjectFactory();
MongoObjectFactory getMongoObjectFactory(MongoDBService mongoDBService);
+ MongoObjectFactory getDefaultMongoObjectFactory(TypeBasedServiceFinderFactory serviceFinderFactory);
+ MongoObjectFactory getMongoObjectFactory(MongoDBService mongoDBService, TypeBasedServiceFinderFactory serviceFinderFactory);
}
diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/Activator.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/Activator.java
index f0597d3226f..14452399bfa 100644
--- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/Activator.java
+++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/Activator.java
@@ -6,14 +6,13 @@ import org.osgi.framework.BundleContext;
import com.sap.sailing.mongodb.MongoDBService;
public class Activator implements BundleActivator {
- @Override
- public void start(BundleContext context) throws Exception {
- for (CollectionNames name : CollectionNames.values())
- MongoDBService.INSTANCE.registerExclusively(CollectionNames.class, name.name());
- }
-
- @Override
- public void stop(BundleContext context) throws Exception {
- }
+ @Override
+ public void start(BundleContext context) throws Exception {
+ for (CollectionNames name : CollectionNames.values())
+ MongoDBService.INSTANCE.registerExclusively(CollectionNames.class, name.name());
+ }
+ @Override
+ public void stop(BundleContext context) throws Exception {
+ }
}
diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/CollectionNames.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/CollectionNames.java
index 32e658d47db..2cfa54a0625 100755
--- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/CollectionNames.java
+++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/CollectionNames.java
@@ -25,7 +25,7 @@ public enum CollectionNames {
LEADERBOARD_GROUPS,
/**
- * Top-level event information about events such as Kiel Week 2011, or IDM Travemünde 2011, including name and
+ * Top-level event information about events such as Kiel Week 2011, or IDM Travem�nde 2011, including name and
* course areas.
*/
EVENTS,
@@ -62,5 +62,10 @@ public enum CollectionNames {
/**
* Stores configurations for mobile devices.
*/
- CONFIGURATIONS;
+ CONFIGURATIONS,
+
+ /**
+ * Stores {@link GPSFix}es
+ */
+ GPS_FIXES,
}
diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java
index 510a433dc19..4c92f6a1cdf 100755
--- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java
+++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java
@@ -1,1487 +1,1629 @@
-package com.sap.sailing.domain.persistence.impl;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.UUID;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import org.bson.types.ObjectId;
-import org.json.simple.JSONObject;
-import org.json.simple.parser.JSONParser;
-import org.json.simple.parser.ParseException;
-
-import com.mongodb.BasicDBList;
-import com.mongodb.BasicDBObject;
-import com.mongodb.BasicDBObjectBuilder;
-import com.mongodb.DB;
-import com.mongodb.DBCollection;
-import com.mongodb.DBCursor;
-import com.mongodb.DBObject;
-import com.mongodb.MongoException;
-import com.mongodb.util.JSON;
-import com.sap.sailing.domain.base.BoatClass;
-import com.sap.sailing.domain.base.Competitor;
-import com.sap.sailing.domain.base.ControlPoint;
-import com.sap.sailing.domain.base.ControlPointWithTwoMarks;
-import com.sap.sailing.domain.base.CourseArea;
-import com.sap.sailing.domain.base.CourseBase;
-import com.sap.sailing.domain.base.DomainFactory;
-import com.sap.sailing.domain.base.Event;
-import com.sap.sailing.domain.base.Fleet;
-import com.sap.sailing.domain.base.Mark;
-import com.sap.sailing.domain.base.RaceColumn;
-import com.sap.sailing.domain.base.RaceDefinition;
-import com.sap.sailing.domain.base.Regatta;
-import com.sap.sailing.domain.base.RegattaRegistry;
-import com.sap.sailing.domain.base.Series;
-import com.sap.sailing.domain.base.Venue;
-import com.sap.sailing.domain.base.Waypoint;
-import com.sap.sailing.domain.base.configuration.DeviceConfiguration;
-import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher;
-import com.sap.sailing.domain.base.configuration.RegattaConfiguration;
-import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationImpl;
-import com.sap.sailing.domain.base.configuration.impl.RegattaConfigurationImpl;
-import com.sap.sailing.domain.base.impl.CourseDataImpl;
-import com.sap.sailing.domain.base.impl.EventImpl;
-import com.sap.sailing.domain.base.impl.FleetImpl;
-import com.sap.sailing.domain.base.impl.RegattaImpl;
-import com.sap.sailing.domain.base.impl.SeriesImpl;
-import com.sap.sailing.domain.base.impl.VenueImpl;
-import com.sap.sailing.domain.base.impl.WaypointImpl;
-import com.sap.sailing.domain.common.Color;
-import com.sap.sailing.domain.common.MarkType;
-import com.sap.sailing.domain.common.MaxPointsReason;
-import com.sap.sailing.domain.common.PassingInstruction;
-import com.sap.sailing.domain.common.Position;
-import com.sap.sailing.domain.common.RaceIdentifier;
-import com.sap.sailing.domain.common.RegattaName;
-import com.sap.sailing.domain.common.RegattaNameAndRaceName;
-import com.sap.sailing.domain.common.ScoringSchemeType;
-import com.sap.sailing.domain.common.SpeedWithBearing;
-import com.sap.sailing.domain.common.TimePoint;
-import com.sap.sailing.domain.common.WindSource;
-import com.sap.sailing.domain.common.WindSourceType;
-import com.sap.sailing.domain.common.configuration.DeviceConfigurationMatcherType;
-import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
-import com.sap.sailing.domain.common.impl.DegreePosition;
-import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
-import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
-import com.sap.sailing.domain.common.impl.RGBColor;
-import com.sap.sailing.domain.common.impl.Util.Pair;
-import com.sap.sailing.domain.common.impl.Util.Triple;
-import com.sap.sailing.domain.common.impl.WindSourceImpl;
-import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID;
-import com.sap.sailing.domain.common.racelog.Flags;
-import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus;
-import com.sap.sailing.domain.common.racelog.RacingProcedureType;
-import com.sap.sailing.domain.leaderboard.DelayedLeaderboardCorrections;
-import com.sap.sailing.domain.leaderboard.DelayedLeaderboardCorrections.LeaderboardCorrectionsResolvedListener;
-import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
-import com.sap.sailing.domain.leaderboard.Leaderboard;
-import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
-import com.sap.sailing.domain.leaderboard.LeaderboardRegistry;
-import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
-import com.sap.sailing.domain.leaderboard.ScoringScheme;
-import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
-import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
-import com.sap.sailing.domain.leaderboard.impl.DelayedLeaderboardCorrectionsImpl;
-import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl;
-import com.sap.sailing.domain.leaderboard.impl.LeaderboardGroupImpl;
-import com.sap.sailing.domain.leaderboard.impl.RegattaLeaderboardImpl;
-import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl;
-import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard;
-import com.sap.sailing.domain.persistence.DomainObjectFactory;
-import com.sap.sailing.domain.persistence.MongoRaceLogStoreFactory;
-import com.sap.sailing.domain.racelog.CompetitorResults;
-import com.sap.sailing.domain.racelog.RaceLog;
-import com.sap.sailing.domain.racelog.RaceLogCourseAreaChangedEvent;
-import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent;
-import com.sap.sailing.domain.racelog.RaceLogEvent;
-import com.sap.sailing.domain.racelog.RaceLogEventAuthor;
-import com.sap.sailing.domain.racelog.RaceLogEventRestoreFactory;
-import com.sap.sailing.domain.racelog.RaceLogFinishPositioningConfirmedEvent;
-import com.sap.sailing.domain.racelog.RaceLogFinishPositioningListChangedEvent;
-import com.sap.sailing.domain.racelog.RaceLogFlagEvent;
-import com.sap.sailing.domain.racelog.RaceLogGateLineOpeningTimeEvent;
-import com.sap.sailing.domain.racelog.RaceLogIdentifier;
-import com.sap.sailing.domain.racelog.RaceLogPassChangeEvent;
-import com.sap.sailing.domain.racelog.RaceLogPathfinderEvent;
-import com.sap.sailing.domain.racelog.RaceLogProtestStartTimeEvent;
-import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent;
-import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent;
-import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent;
-import com.sap.sailing.domain.racelog.RaceLogStore;
-import com.sap.sailing.domain.racelog.RaceLogWindFixEvent;
-import com.sap.sailing.domain.racelog.impl.CompetitorResultsImpl;
-import com.sap.sailing.domain.racelog.impl.RaceLogEventAuthorImpl;
-import com.sap.sailing.domain.racelog.impl.RaceLogImpl;
-import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
-import com.sap.sailing.domain.tracking.Wind;
-import com.sap.sailing.domain.tracking.WindTrack;
-import com.sap.sailing.domain.tracking.impl.WindImpl;
-import com.sap.sailing.domain.tracking.impl.WindTrackImpl;
-import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException;
-import com.sap.sailing.server.gateway.deserialization.JsonDeserializer;
-import com.sap.sailing.server.gateway.deserialization.impl.CompetitorJsonDeserializer;
-import com.sap.sailing.server.gateway.deserialization.impl.DeviceConfigurationJsonDeserializer;
-import com.sap.sailing.server.gateway.deserialization.impl.Helpers;
-import com.sap.sailing.server.gateway.deserialization.impl.RegattaConfigurationJsonDeserializer;
-
-public class DomainObjectFactoryImpl implements DomainObjectFactory {
- private static final Logger logger = Logger.getLogger(DomainObjectFactoryImpl.class.getName());
- private final CompetitorJsonDeserializer competitorDeserializer;
-
- private final DB database;
-
- private RaceLogEventRestoreFactory raceLogEventFactory;
- private final DomainFactory baseDomainFactory;
-
- public DomainObjectFactoryImpl(DB db, DomainFactory baseDomainFactory) {
- super();
- this.baseDomainFactory = baseDomainFactory;
- this.competitorDeserializer = CompetitorJsonDeserializer.create(baseDomainFactory);
- this.database = db;
- this.raceLogEventFactory = RaceLogEventRestoreFactory.INSTANCE;
- }
-
- @Override
- public DomainFactory getBaseDomainFactory() {
- return baseDomainFactory;
- }
-
- public Wind loadWind(DBObject object) {
- return new WindImpl(loadPosition(object), loadTimePoint(object), loadSpeedWithBearing(object));
- }
-
- private Position loadPosition(DBObject object) {
- Number latNumber = (Number) object.get(FieldNames.LAT_DEG.name());
- Double lat = latNumber == null ? null : latNumber.doubleValue();
- Number lngNumber = (Number) object.get(FieldNames.LNG_DEG.name());
- Double lng = lngNumber == null ? null : lngNumber.doubleValue();
- if (lat != null && lng != null) {
- return new DegreePosition(lat, lng);
- } else {
- return null;
- }
- }
-
- private TimePoint loadTimePoint(DBObject object, FieldNames field) {
- TimePoint result = null;
- Number timePointAsNumber = (Number) object.get(field.name());
- if (timePointAsNumber != null) {
- result = new MillisecondsTimePoint(timePointAsNumber.longValue());
- }
- return result;
- }
-
- /**
- * Loads a {@link TimePoint} on the given object at {@link FieldNames#TIME_AS_MILLIS}.
- */
- private TimePoint loadTimePoint(DBObject object) {
- return loadTimePoint(object, FieldNames.TIME_AS_MILLIS);
- }
-
- private SpeedWithBearing loadSpeedWithBearing(DBObject object) {
- return new KnotSpeedWithBearingImpl(((Number) object.get(FieldNames.KNOT_SPEED.name())).doubleValue(),
- new DegreeBearingImpl(((Number) object.get(FieldNames.DEGREE_BEARING.name())).doubleValue()));
- }
-
- @Override
- public RaceIdentifier loadRaceIdentifier(DBObject dbObject) {
- RaceIdentifier result = null;
- String regattaName = (String) dbObject.get(FieldNames.EVENT_NAME.name());
- String raceName = (String) dbObject.get(FieldNames.RACE_NAME.name());
- if (regattaName != null && raceName != null) {
- result = new RegattaNameAndRaceName(regattaName, raceName);
- }
- return result;
- }
-
- private void ensureIndicesOnWindTracks(DBCollection windTracks) {
- windTracks.ensureIndex(FieldNames.RACE_ID.name()); // for new programmatic access
- windTracks.ensureIndex(FieldNames.REGATTA_NAME.name()); // for export or human look-up
- // for legacy access to not yet migrated fixes
- windTracks.ensureIndex(new BasicDBObjectBuilder().add(FieldNames.EVENT_NAME.name(), 1)
- .add(FieldNames.RACE_NAME.name(), 1).get());
- // Unique index
- try {
- windTracks.ensureIndex(
- new BasicDBObjectBuilder().add(FieldNames.RACE_ID.name(), 1)
- .add(FieldNames.WIND_SOURCE_NAME.name(), 1).add(FieldNames.WIND_SOURCE_ID.name(), 1)
- .add(FieldNames.WIND.name() + "." + FieldNames.TIME_AS_MILLIS.name(), 1).get(),
- new BasicDBObjectBuilder().add("unique", true).add("dropDups", true).get());
- } catch (MongoException exception) {
-
- if (exception.getCode() == 10092) {
- logger.warning(String.format(
- "Setting the unique index on the %s collection failed because you have too many duplicates. "
- + "This leads to the mongo error code %s and the following message: %s \nTo fix this follow "
- + "the steps provided on the wiki page: http://wiki.sapsailing.com/wiki/cook-book#Remove-"
- + "duplicates-from-WIND_TRACK-collection", CollectionNames.WIND_TRACKS.name(),
- exception.getCode(), exception.getMessage()));
- } else {
- logger.severe(String.format(
- "Setting the unique index on the %s collection failed with error code %s and message: %s",
- CollectionNames.WIND_TRACKS.name(), exception.getCode(), exception.getMessage()));
- }
- }
- }
-
- @Override
- public Leaderboard loadLeaderboard(String name, RegattaRegistry regattaRegistry) {
- DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
- Leaderboard result = null;
- try {
- BasicDBObject query = new BasicDBObject();
- query.put(FieldNames.LEADERBOARD_NAME.name(), name);
- for (DBObject o : leaderboardCollection.find(query)) {
- result = loadLeaderboard(o, regattaRegistry, /* leaderboardRegistry */ null, /* groupForMetaLeaderboard */ null);
- }
- } catch (Exception e) {
- // something went wrong during DB access; report, then use empty new wind track
- logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load leaderboard "+name+".");
- logger.log(Level.SEVERE, "loadLeaderboard", e);
- }
- return result;
- }
-
- @Override
- public Iterablenull, then before creating and loading the leaderboard it is looked up in this
- * registry and only loaded if not found there. If leaderboardRegistry is null,
- * the leaderboard is loaded in any case. If the leaderboard is loaded and
- * leaderboardRegistry is not null, the leaderboard loaded is
- * {@link LeaderboardRegistry#addLeaderboard(Leaderboard) added to the registry}.
- * @param groupForMetaLeaderboard
- * if not null, a {@link LeaderboardGroupMetaLeaderboard} instance is created and set as the
- * group's {@link LeaderboardGroup#setOverallLeaderboard(Leaderboard) overall leaderboard}
- *
- * @return null in case the leaderboard couldn't be loaded, e.g., because the regatta referenced by a
- * {@link RegattaLeaderboard} cannot be found; the leaderboard loaded or found in
- * leaderboardRegistry, otherwise
- */
- private Leaderboard loadLeaderboard(DBObject dbLeaderboard, RegattaRegistry regattaRegistry, LeaderboardRegistry leaderboardRegistry,
- LeaderboardGroup groupForMetaLeaderboard) {
- Leaderboard result = null;
- String leaderboardName = (String) dbLeaderboard.get(FieldNames.LEADERBOARD_NAME.name());
- if (leaderboardRegistry != null) {
- result = leaderboardRegistry.getLeaderboardByName(leaderboardName);
- }
- if (result == null) {
- ThresholdBasedResultDiscardingRule resultDiscardingRule = loadResultDiscardingRule(dbLeaderboard, FieldNames.LEADERBOARD_DISCARDING_THRESHOLDS);
- String regattaName = (String) dbLeaderboard.get(FieldNames.REGATTA_NAME.name());
- if (groupForMetaLeaderboard != null) {
- result = new LeaderboardGroupMetaLeaderboard(groupForMetaLeaderboard, loadScoringScheme(dbLeaderboard), resultDiscardingRule);
- groupForMetaLeaderboard.setOverallLeaderboard(result);
- } else if (regattaName == null) {
- result = loadFlexibleLeaderboard(dbLeaderboard, resultDiscardingRule);
- } else {
- result = loadRegattaLeaderboard(leaderboardName, regattaName, dbLeaderboard, resultDiscardingRule, regattaRegistry);
- }
- if (result != null) {
- final Leaderboard finalResult = result;
- finalResult.setDisplayName((String) dbLeaderboard.get(FieldNames.LEADERBOARD_DISPLAY_NAME.name()));
-
- DelayedLeaderboardCorrections loadedLeaderboardCorrections = new DelayedLeaderboardCorrectionsImpl(result, baseDomainFactory);
- final boolean[] needsMigration = new boolean[1];
- loadedLeaderboardCorrections.addLeaderboardCorrectionsResolvedListener(new LeaderboardCorrectionsResolvedListener() {
- @Override
- public void correctionsResolved(DelayedLeaderboardCorrections delayedLeaderboardCorrections) {
- if (needsMigration[0]) {
- new MongoObjectFactoryImpl(database).storeLeaderboard(finalResult);
- }
- }
- });
- needsMigration[0] = loadLeaderboardCorrections(dbLeaderboard, loadedLeaderboardCorrections, result.getScoreCorrection()) || needsMigration[0];
- needsMigration[0] = loadSuppressedCompetitors(dbLeaderboard, loadedLeaderboardCorrections) || needsMigration[0];
- loadColumnFactors(dbLeaderboard, result);
- // add the leaderboard to the registry
- if (leaderboardRegistry != null) {
- leaderboardRegistry.addLeaderboard(result);
- logger.info("loaded leaderboard "+result.getName()+" into "+leaderboardRegistry);
- }
- }
- }
- return result;
- }
-
- private void loadColumnFactors(DBObject dbLeaderboard, Leaderboard result) {
- DBObject dbColumnFactors = (DBObject) dbLeaderboard.get(FieldNames.LEADERBOARD_COLUMN_FACTORS.name());
- if (dbColumnFactors != null) {
- for (String encodedRaceColumnName : dbColumnFactors.keySet()) {
- double factor = ((Number) dbColumnFactors.get(encodedRaceColumnName)).doubleValue();
- String raceColumnName = MongoUtils.unescapeDollarAndDot(encodedRaceColumnName);
- final RaceColumn raceColumn = result.getRaceColumnByName(raceColumnName);
- if (raceColumn != null) {
- raceColumn.setFactor(factor);
- } else {
- logger.warning("Expected to find race column named "+raceColumnName+" in leaderboard "+result.getName()+
- " to apply column factor "+factor+", but the race column wasn't found. Ignoring factor.");
- }
- }
- }
- }
-
- private boolean loadSuppressedCompetitors(DBObject dbLeaderboard,
- DelayedLeaderboardCorrections loadedLeaderboardCorrections) {
- final boolean needsMigration;
- BasicDBList dbSuppressedCompetitorNames = (BasicDBList) dbLeaderboard.get(FieldNames.LEADERBOARD_SUPPRESSED_COMPETITORS.name());
- if (dbSuppressedCompetitorNames != null) {
- needsMigration = true;
- for (Object escapedCompetitorName : dbSuppressedCompetitorNames) {
- loadedLeaderboardCorrections.suppressCompetitorByName(MongoUtils.unescapeDollarAndDot((String) escapedCompetitorName));
- }
- } else {
- needsMigration = false;
- }
- BasicDBList dbSuppressedCompetitorIDs = (BasicDBList) dbLeaderboard.get(FieldNames.LEADERBOARD_SUPPRESSED_COMPETITOR_IDS.name());
- if (dbSuppressedCompetitorIDs != null) {
- for (Object competitorId : dbSuppressedCompetitorIDs) {
- loadedLeaderboardCorrections.suppressCompetitorById((Serializable) competitorId);
- }
- }
- return needsMigration;
- }
-
- /**
- * @param dbObject expects to find a field identified by field which holds a {@link BasicDBList}
- */
- private ThresholdBasedResultDiscardingRule loadResultDiscardingRule(DBObject dbObject, FieldNames field) {
- BasicDBList dbDiscardIndexResultsStartingWithHowManyRaces = (BasicDBList) dbObject.get(field.name());
- final ThresholdBasedResultDiscardingRule result;
- if (dbDiscardIndexResultsStartingWithHowManyRaces == null) {
- result = null;
- } else {
- int[] discardIndexResultsStartingWithHowManyRaces = new int[dbDiscardIndexResultsStartingWithHowManyRaces
- .size()];
- int i = 0;
- for (Object discardingThresholdAsObject : dbDiscardIndexResultsStartingWithHowManyRaces) {
- discardIndexResultsStartingWithHowManyRaces[i++] = (Integer) discardingThresholdAsObject;
- }
- result = new ThresholdBasedResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces);
- }
- return result;
- }
-
- /**
- * @return null if the regatta cannot be resolved; otherwise the leaderboard for the regatta specified
- */
- private RegattaLeaderboard loadRegattaLeaderboard(String leaderboardName, String regattaName, DBObject dbLeaderboard,
- ThresholdBasedResultDiscardingRule resultDiscardingRule, RegattaRegistry regattaRegistry) {
- RegattaLeaderboard result = null;
- Regatta regatta = regattaRegistry.getRegatta(new RegattaName(regattaName));
- if (regatta == null) {
- logger.info("Couldn't find regatta "+regattaName+" for corresponding regatta leaderboard. Not loading regatta leaderboard.");
- } else {
- result = new RegattaLeaderboardImpl(regatta, resultDiscardingRule);
- result.setName(leaderboardName);
- }
- return result;
- }
-
- private FlexibleLeaderboard loadFlexibleLeaderboard(DBObject dbLeaderboard,
- ThresholdBasedResultDiscardingRule resultDiscardingRule) {
- final FlexibleLeaderboardImpl result;
- BasicDBList dbRaceColumns = (BasicDBList) dbLeaderboard.get(FieldNames.LEADERBOARD_COLUMNS.name());
- if (dbRaceColumns == null) {
- // this was probably an orphaned overall leaderboard
- logger.warning("Probably found orphan overall leaderboard named "
- + dbLeaderboard.get(FieldNames.LEADERBOARD_NAME.name())+". Ignoring.");
- result = null;
- } else {
- final ScoringScheme scoringScheme = loadScoringScheme(dbLeaderboard);
- RaceLogStore raceLogStore = MongoRaceLogStoreFactory.INSTANCE.getMongoRaceLogStore(
- new MongoObjectFactoryImpl(database), this);
-
- Serializable courseAreaId = (Serializable) dbLeaderboard.get(FieldNames.COURSE_AREA_ID.name());
- CourseArea courseArea = null;
- if (courseAreaId != null) {
- UUID courseAreaUuid = UUID.fromString(courseAreaId.toString());
- courseArea = baseDomainFactory.getExistingCourseAreaById(courseAreaUuid);
- }
-
- result = new FlexibleLeaderboardImpl(raceLogStore, (String) dbLeaderboard.get(FieldNames.LEADERBOARD_NAME
- .name()), resultDiscardingRule, scoringScheme, courseArea);
- // For a FlexibleLeaderboard, fleets are owned by the leaderboard's RaceColumn objects. We need to manage
- // them here:
- MapdbRaceColumn. In this case, it is returned with null as the fleet name key.
- *
- * @return a map with fleet names as key and the corresponding fleet's race identifier as value; the special
- * null key is used to identify a "default fleet" for backward compatibility with stored
- * leaderboards which don't know about fleets yet; this key should be mapped to the leaderboard's default
- * fleet.
- */
- private Mapnull, wind for all sources will be loaded; otherwise, only wind data for the wind
- * source specified by this argument will be loaded
- */
- private Mapnull as the {@link TypeBasedServiceFinder}, meaning that no {@link DeviceIdentifier}s can be loaded
+ * using this instance of a {@link DomainObjectFactory}.
+ */
+ public DomainObjectFactoryImpl(DB db, DomainFactory baseDomainFactory) {
+ this(db, baseDomainFactory, /* deviceTypeServiceFinder */ null);
+ }
+
+ public DomainObjectFactoryImpl(DB db, DomainFactory baseDomainFactory, TypeBasedServiceFinderFactory serviceFinderFactory) {
+ super();
+ this.serviceFinderFactory = serviceFinderFactory;
+ if (serviceFinderFactory != null) {
+ this.deviceIdentifierServiceFinder = serviceFinderFactory.createServiceFinder(DeviceIdentifierMongoHandler.class);
+ this.deviceIdentifierServiceFinder.setFallbackService(new PlaceHolderDeviceIdentifierMongoHandler());
+ } else {
+ this.deviceIdentifierServiceFinder = null;
+ }
+ this.baseDomainFactory = baseDomainFactory;
+ this.competitorDeserializer = CompetitorJsonDeserializer.create(baseDomainFactory);
+ this.database = db;
+ this.raceLogEventFactory = RaceLogEventRestoreFactory.INSTANCE;
+ }
+
+ @Override
+ public DomainFactory getBaseDomainFactory() {
+ return baseDomainFactory;
+ }
+
+ public Wind loadWind(DBObject object) {
+ return new WindImpl(loadPosition(object), loadTimePoint(object), loadSpeedWithBearing(object));
+ }
+
+ public Position loadPosition(DBObject object) {
+ Number latNumber = (Number) object.get(FieldNames.LAT_DEG.name());
+ Double lat = latNumber == null ? null : latNumber.doubleValue();
+ Number lngNumber = (Number) object.get(FieldNames.LNG_DEG.name());
+ Double lng = lngNumber == null ? null : lngNumber.doubleValue();
+ if (lat != null && lng != null) {
+ return new DegreePosition(lat, lng);
+ } else {
+ return null;
+ }
+ }
+
+ public static TimePoint loadTimePoint(DBObject object, String fieldName) {
+ TimePoint result = null;
+ Number timePointAsNumber = (Number) object.get(fieldName);
+ if (timePointAsNumber != null) {
+ result = new MillisecondsTimePoint(timePointAsNumber.longValue());
+ }
+ return result;
+ }
+
+ public static TimePoint loadTimePoint(DBObject object, FieldNames field) {
+ return loadTimePoint(object, field.name());
+ }
+
+ /**
+ * Loads a {@link TimePoint} on the given object at {@link FieldNames#TIME_AS_MILLIS}.
+ */
+ public TimePoint loadTimePoint(DBObject object) {
+ return loadTimePoint(object, FieldNames.TIME_AS_MILLIS);
+ }
+
+ public SpeedWithBearing loadSpeedWithBearing(DBObject object) {
+ return new KnotSpeedWithBearingImpl(((Number) object.get(FieldNames.KNOT_SPEED.name())).doubleValue(),
+ new DegreeBearingImpl(((Number) object.get(FieldNames.DEGREE_BEARING.name())).doubleValue()));
+ }
+
+ @Override
+ public RaceIdentifier loadRaceIdentifier(DBObject dbObject) {
+ RaceIdentifier result = null;
+ String regattaName = (String) dbObject.get(FieldNames.EVENT_NAME.name());
+ String raceName = (String) dbObject.get(FieldNames.RACE_NAME.name());
+ if (regattaName != null && raceName != null) {
+ result = new RegattaNameAndRaceName(regattaName, raceName);
+ }
+ return result;
+ }
+
+ private void ensureIndicesOnWindTracks(DBCollection windTracks) {
+ windTracks.ensureIndex(FieldNames.RACE_ID.name()); // for new programmatic access
+ windTracks.ensureIndex(FieldNames.REGATTA_NAME.name()); // for export or human look-up
+ // for legacy access to not yet migrated fixes
+ windTracks.ensureIndex(new BasicDBObjectBuilder().add(FieldNames.EVENT_NAME.name(), 1)
+ .add(FieldNames.RACE_NAME.name(), 1).get());
+ // Unique index
+ try {
+ windTracks.ensureIndex(
+ new BasicDBObjectBuilder().add(FieldNames.RACE_ID.name(), 1)
+ .add(FieldNames.WIND_SOURCE_NAME.name(), 1).add(FieldNames.WIND_SOURCE_ID.name(), 1)
+ .add(FieldNames.WIND.name() + "." + FieldNames.TIME_AS_MILLIS.name(), 1).get(),
+ new BasicDBObjectBuilder().add("unique", true).add("dropDups", true).get());
+ } catch (MongoException exception) {
+
+ if (exception.getCode() == 10092) {
+ logger.warning(String.format(
+ "Setting the unique index on the %s collection failed because you have too many duplicates. "
+ + "This leads to the mongo error code %s and the following message: %s \nTo fix this follow "
+ + "the steps provided on the wiki page: http://wiki.sapsailing.com/wiki/cook-book#Remove-"
+ + "duplicates-from-WIND_TRACK-collection", CollectionNames.WIND_TRACKS.name(),
+ exception.getCode(), exception.getMessage()));
+ } else {
+ logger.severe(String.format(
+ "Setting the unique index on the %s collection failed with error code %s and message: %s",
+ CollectionNames.WIND_TRACKS.name(), exception.getCode(), exception.getMessage()));
+ }
+ }
+ }
+
+ @Override
+ public Leaderboard loadLeaderboard(String name, RegattaRegistry regattaRegistry) {
+ DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
+ Leaderboard result = null;
+ try {
+ BasicDBObject query = new BasicDBObject();
+ query.put(FieldNames.LEADERBOARD_NAME.name(), name);
+ for (DBObject o : leaderboardCollection.find(query)) {
+ result = loadLeaderboard(o, regattaRegistry, /* leaderboardRegistry */ null, /* groupForMetaLeaderboard */ null);
+ }
+ } catch (Exception e) {
+ // something went wrong during DB access; report, then use empty new wind track
+ logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load leaderboard "+name+".");
+ logger.log(Level.SEVERE, "loadLeaderboard", e);
+ }
+ return result;
+ }
+
+ @Override
+ public Iterablenull, then before creating and loading the leaderboard it is looked up in this
+ * registry and only loaded if not found there. If leaderboardRegistry is null,
+ * the leaderboard is loaded in any case. If the leaderboard is loaded and
+ * leaderboardRegistry is not null, the leaderboard loaded is
+ * {@link LeaderboardRegistry#addLeaderboard(Leaderboard) added to the registry}.
+ * @param groupForMetaLeaderboard
+ * if not null, a {@link LeaderboardGroupMetaLeaderboard} instance is created and set as the
+ * group's {@link LeaderboardGroup#setOverallLeaderboard(Leaderboard) overall leaderboard}
+ *
+ * @return null in case the leaderboard couldn't be loaded, e.g., because the regatta referenced by a
+ * {@link RegattaLeaderboard} cannot be found; the leaderboard loaded or found in
+ * leaderboardRegistry, otherwise
+ */
+ private Leaderboard loadLeaderboard(DBObject dbLeaderboard, RegattaRegistry regattaRegistry, LeaderboardRegistry leaderboardRegistry,
+ LeaderboardGroup groupForMetaLeaderboard) {
+ Leaderboard result = null;
+ String leaderboardName = (String) dbLeaderboard.get(FieldNames.LEADERBOARD_NAME.name());
+ if (leaderboardRegistry != null) {
+ result = leaderboardRegistry.getLeaderboardByName(leaderboardName);
+ }
+ if (result == null) {
+ ThresholdBasedResultDiscardingRule resultDiscardingRule = loadResultDiscardingRule(dbLeaderboard, FieldNames.LEADERBOARD_DISCARDING_THRESHOLDS);
+ String regattaName = (String) dbLeaderboard.get(FieldNames.REGATTA_NAME.name());
+ if (groupForMetaLeaderboard != null) {
+ result = new LeaderboardGroupMetaLeaderboard(groupForMetaLeaderboard, loadScoringScheme(dbLeaderboard), resultDiscardingRule);
+ groupForMetaLeaderboard.setOverallLeaderboard(result);
+ } else if (regattaName == null) {
+ result = loadFlexibleLeaderboard(dbLeaderboard, resultDiscardingRule);
+ } else {
+ result = loadRegattaLeaderboard(leaderboardName, regattaName, dbLeaderboard, resultDiscardingRule, regattaRegistry);
+ }
+ if (result != null) {
+ final Leaderboard finalResult = result;
+ finalResult.setDisplayName((String) dbLeaderboard.get(FieldNames.LEADERBOARD_DISPLAY_NAME.name()));
+
+ DelayedLeaderboardCorrections loadedLeaderboardCorrections = new DelayedLeaderboardCorrectionsImpl(result, baseDomainFactory);
+ final boolean[] needsMigration = new boolean[1];
+ loadedLeaderboardCorrections.addLeaderboardCorrectionsResolvedListener(new LeaderboardCorrectionsResolvedListener() {
+ @Override
+ public void correctionsResolved(DelayedLeaderboardCorrections delayedLeaderboardCorrections) {
+ if (needsMigration[0]) {
+ new MongoObjectFactoryImpl(database).storeLeaderboard(finalResult);
+ }
+ }
+ });
+ needsMigration[0] = loadLeaderboardCorrections(dbLeaderboard, loadedLeaderboardCorrections, result.getScoreCorrection()) || needsMigration[0];
+ needsMigration[0] = loadSuppressedCompetitors(dbLeaderboard, loadedLeaderboardCorrections) || needsMigration[0];
+ loadColumnFactors(dbLeaderboard, result);
+ // add the leaderboard to the registry
+ if (leaderboardRegistry != null) {
+ leaderboardRegistry.addLeaderboard(result);
+ logger.info("loaded leaderboard "+result.getName()+" into "+leaderboardRegistry);
+ }
+ }
+ }
+ return result;
+ }
+
+ private void loadColumnFactors(DBObject dbLeaderboard, Leaderboard result) {
+ DBObject dbColumnFactors = (DBObject) dbLeaderboard.get(FieldNames.LEADERBOARD_COLUMN_FACTORS.name());
+ if (dbColumnFactors != null) {
+ for (String encodedRaceColumnName : dbColumnFactors.keySet()) {
+ double factor = ((Number) dbColumnFactors.get(encodedRaceColumnName)).doubleValue();
+ String raceColumnName = MongoUtils.unescapeDollarAndDot(encodedRaceColumnName);
+ final RaceColumn raceColumn = result.getRaceColumnByName(raceColumnName);
+ if (raceColumn != null) {
+ raceColumn.setFactor(factor);
+ } else {
+ logger.warning("Expected to find race column named "+raceColumnName+" in leaderboard "+result.getName()+
+ " to apply column factor "+factor+", but the race column wasn't found. Ignoring factor.");
+ }
+ }
+ }
+ }
+
+ private boolean loadSuppressedCompetitors(DBObject dbLeaderboard,
+ DelayedLeaderboardCorrections loadedLeaderboardCorrections) {
+ final boolean needsMigration;
+ BasicDBList dbSuppressedCompetitorNames = (BasicDBList) dbLeaderboard.get(FieldNames.LEADERBOARD_SUPPRESSED_COMPETITORS.name());
+ if (dbSuppressedCompetitorNames != null) {
+ needsMigration = true;
+ for (Object escapedCompetitorName : dbSuppressedCompetitorNames) {
+ loadedLeaderboardCorrections.suppressCompetitorByName(MongoUtils.unescapeDollarAndDot((String) escapedCompetitorName));
+ }
+ } else {
+ needsMigration = false;
+ }
+ BasicDBList dbSuppressedCompetitorIDs = (BasicDBList) dbLeaderboard.get(FieldNames.LEADERBOARD_SUPPRESSED_COMPETITOR_IDS.name());
+ if (dbSuppressedCompetitorIDs != null) {
+ for (Object competitorId : dbSuppressedCompetitorIDs) {
+ loadedLeaderboardCorrections.suppressCompetitorById((Serializable) competitorId);
+ }
+ }
+ return needsMigration;
+ }
+
+ /**
+ * @param dbObject expects to find a field identified by field which holds a {@link BasicDBList}
+ */
+ private ThresholdBasedResultDiscardingRule loadResultDiscardingRule(DBObject dbObject, FieldNames field) {
+ BasicDBList dbDiscardIndexResultsStartingWithHowManyRaces = (BasicDBList) dbObject.get(field.name());
+ final ThresholdBasedResultDiscardingRule result;
+ if (dbDiscardIndexResultsStartingWithHowManyRaces == null) {
+ result = null;
+ } else {
+ int[] discardIndexResultsStartingWithHowManyRaces = new int[dbDiscardIndexResultsStartingWithHowManyRaces
+ .size()];
+ int i = 0;
+ for (Object discardingThresholdAsObject : dbDiscardIndexResultsStartingWithHowManyRaces) {
+ discardIndexResultsStartingWithHowManyRaces[i++] = (Integer) discardingThresholdAsObject;
+ }
+ result = new ThresholdBasedResultDiscardingRuleImpl(discardIndexResultsStartingWithHowManyRaces);
+ }
+ return result;
+ }
+
+ /**
+ * @return null if the regatta cannot be resolved; otherwise the leaderboard for the regatta specified
+ */
+ private RegattaLeaderboard loadRegattaLeaderboard(String leaderboardName, String regattaName, DBObject dbLeaderboard,
+ ThresholdBasedResultDiscardingRule resultDiscardingRule, RegattaRegistry regattaRegistry) {
+ RegattaLeaderboard result = null;
+ Regatta regatta = regattaRegistry.getRegatta(new RegattaName(regattaName));
+ if (regatta == null) {
+ logger.info("Couldn't find regatta "+regattaName+" for corresponding regatta leaderboard. Not loading regatta leaderboard.");
+ } else {
+ result = new RegattaLeaderboardImpl(regatta, resultDiscardingRule);
+ result.setName(leaderboardName);
+ }
+ return result;
+ }
+
+ private FlexibleLeaderboard loadFlexibleLeaderboard(DBObject dbLeaderboard,
+ ThresholdBasedResultDiscardingRule resultDiscardingRule) {
+ final FlexibleLeaderboardImpl result;
+ BasicDBList dbRaceColumns = (BasicDBList) dbLeaderboard.get(FieldNames.LEADERBOARD_COLUMNS.name());
+ if (dbRaceColumns == null) {
+ // this was probably an orphaned overall leaderboard
+ logger.warning("Probably found orphan overall leaderboard named "
+ + dbLeaderboard.get(FieldNames.LEADERBOARD_NAME.name())+". Ignoring.");
+ result = null;
+ } else {
+ final ScoringScheme scoringScheme = loadScoringScheme(dbLeaderboard);
+ RaceLogStore raceLogStore = MongoRaceLogStoreFactory.INSTANCE.getMongoRaceLogStore(
+ new MongoObjectFactoryImpl(database), this);
+
+ Serializable courseAreaId = (Serializable) dbLeaderboard.get(FieldNames.COURSE_AREA_ID.name());
+ CourseArea courseArea = null;
+ if (courseAreaId != null) {
+ UUID courseAreaUuid = UUID.fromString(courseAreaId.toString());
+ courseArea = baseDomainFactory.getExistingCourseAreaById(courseAreaUuid);
+ }
+
+ result = new FlexibleLeaderboardImpl(raceLogStore, (String) dbLeaderboard.get(FieldNames.LEADERBOARD_NAME
+ .name()), resultDiscardingRule, scoringScheme, courseArea);
+ // For a FlexibleLeaderboard, fleets are owned by the leaderboard's RaceColumn objects. We need to manage
+ // them here:
+ MapdbRaceColumn. In this case, it is returned with null as the fleet name key.
+ *
+ * @return a map with fleet names as key and the corresponding fleet's race identifier as value; the special
+ * null key is used to identify a "default fleet" for backward compatibility with stored
+ * leaderboards which don't know about fleets yet; this key should be mapped to the leaderboard's default
+ * fleet.
+ */
+ private Mapnull, wind for all sources will be loaded; otherwise, only wind data for the wind
+ * source specified by this argument will be loaded
+ */
+ private MapdbObject's field identified by field if the result discarding
- * rule is not null and is of type {@link ThresholdBasedResultDiscardingRule}. Otherwise, it is assumed that the
- * result discarding rule is otherwise implicitly obtained, e.g., from a definition of a regatta with its series, stored elsewhere.
- */
- private void storeResultDiscardingRule(DBObject dbObject,
- final ResultDiscardingRule resultDiscardingRule, FieldNames field) {
- if (resultDiscardingRule != null && resultDiscardingRule instanceof ThresholdBasedResultDiscardingRule) {
- BasicDBList dbResultDiscardingThresholds = new BasicDBList();
- for (int threshold : ((ThresholdBasedResultDiscardingRule) resultDiscardingRule).getDiscardIndexResultsStartingWithHowManyRaces()) {
- dbResultDiscardingThresholds.add(threshold);
- }
- dbObject.put(field.name(), dbResultDiscardingThresholds);
- }
- }
-
- private BasicDBObject storeRaceColumn(RaceColumn raceColumn) {
- BasicDBObject dbRaceColumn = new BasicDBObject();
- dbRaceColumn.put(FieldNames.LEADERBOARD_COLUMN_NAME.name(), raceColumn.getName());
- dbRaceColumn.put(FieldNames.LEADERBOARD_IS_MEDAL_RACE_COLUMN.name(), raceColumn.isMedalRace());
- storeRaceIdentifiers(raceColumn, dbRaceColumn);
- return dbRaceColumn;
- }
-
- private void storeScoreCorrections(Leaderboard leaderboard, BasicDBObject dbScoreCorrections) {
- TimePoint now = MillisecondsTimePoint.now();
- SettableScoreCorrection scoreCorrection = leaderboard.getScoreCorrection();
- for (RaceColumn raceColumn : scoreCorrection.getRaceColumnsThatHaveCorrections()) {
- BasicDBList dbCorrectionForRace = new BasicDBList();
- for (Competitor competitor : scoreCorrection.getCompetitorsThatHaveCorrectionsIn(raceColumn)) {
- // TODO bug 655: make score corrections time dependent
- if (scoreCorrection.isScoreCorrected(competitor, raceColumn, now)) {
- BasicDBObject dbCorrectionForCompetitor = new BasicDBObject();
- dbCorrectionForCompetitor.put(FieldNames.COMPETITOR_ID.name(), competitor.getId());
- MaxPointsReason maxPointsReason = scoreCorrection.getMaxPointsReason(competitor, raceColumn, now);
- if (maxPointsReason != MaxPointsReason.NONE) {
- dbCorrectionForCompetitor.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON.name(),
- maxPointsReason.name());
- }
- Double explicitScoreCorrection = scoreCorrection
- .getExplicitScoreCorrection(competitor, raceColumn);
- if (explicitScoreCorrection != null) {
- dbCorrectionForCompetitor.put(FieldNames.LEADERBOARD_CORRECTED_SCORE.name(),
- explicitScoreCorrection);
- }
- dbCorrectionForRace.add(dbCorrectionForCompetitor);
- }
- }
- if (!dbCorrectionForRace.isEmpty()) {
- // using the column name as the key for the score corrections requires re-writing the score corrections
- // of a meta-leaderboard if the name of one of its leaderboards changes
- dbScoreCorrections.put(MongoUtils.escapeDollarAndDot(raceColumn.getName()), dbCorrectionForRace);
- }
- }
- final TimePoint timePointOfLastCorrectionsValidity = scoreCorrection.getTimePointOfLastCorrectionsValidity();
- if (timePointOfLastCorrectionsValidity != null) {
- dbScoreCorrections.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_TIMESTAMP.name(), timePointOfLastCorrectionsValidity.asMillis());
- }
- if (scoreCorrection.getComment() != null) {
- dbScoreCorrections.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_COMMENT.name(), scoreCorrection.getComment());
- }
- }
-
- @Override
- public void removeLeaderboard(String leaderboardName) {
- DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
- BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), leaderboardName);
- leaderboardCollection.remove(query);
- }
-
- @Override
- public void renameLeaderboard(String oldName, String newName) {
- DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
- BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), oldName);
- BasicDBObject renameUpdate = new BasicDBObject("$set", new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), newName));
- leaderboardCollection.update(query, renameUpdate, /* upsert */ true, /* multi */ false, WriteConcern.SAFE);
- }
-
- @Override
- public void storeLeaderboardGroup(LeaderboardGroup leaderboardGroup) {
- DBCollection leaderboardGroupCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name());
- DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
-
- try {
- leaderboardGroupCollection.ensureIndex(FieldNames.LEADERBOARD_GROUP_NAME.name());
- } catch (NullPointerException npe) {
- // sometimes, for reasons yet to be clarified, ensuring an index on the name field causes an NPE
- logger.log(Level.SEVERE, "storeLeaderboardGroup", npe);
- }
- BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), leaderboardGroup.getName());
- BasicDBObject dbLeaderboardGroup = new BasicDBObject();
- dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_NAME.name(), leaderboardGroup.getName());
- dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_DESCRIPTION.name(), leaderboardGroup.getDescription());
- dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_DISPLAY_IN_REVERSE_ORDER.name(), leaderboardGroup.isDisplayGroupsInReverseOrder());
- final Leaderboard overallLeaderboard = leaderboardGroup.getOverallLeaderboard();
- if (overallLeaderboard != null) {
- BasicDBObject overallLeaderboardQuery = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), overallLeaderboard.getName());
- DBObject dbOverallLeaderboard = leaderboardCollection.findOne(overallLeaderboardQuery);
- if (dbOverallLeaderboard == null) {
- storeLeaderboard(overallLeaderboard);
- dbOverallLeaderboard = leaderboardCollection.findOne(overallLeaderboardQuery);
- }
- ObjectId dbOverallLeaderboardId = (ObjectId) dbOverallLeaderboard.get("_id");
- dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_OVERALL_LEADERBOARD.name(), dbOverallLeaderboardId);
- }
- BasicDBList dbLeaderboardIds = new BasicDBList();
- for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
- BasicDBObject leaderboardQuery = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), leaderboard.getName());
- DBObject dbLeaderboard = leaderboardCollection.findOne(leaderboardQuery);
- if (dbLeaderboard == null) {
- storeLeaderboard(leaderboard);
- dbLeaderboard = leaderboardCollection.findOne(leaderboardQuery);
- }
- ObjectId dbLeaderboardId = (ObjectId) dbLeaderboard.get("_id");
- dbLeaderboardIds.add(dbLeaderboardId);
- }
- dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_LEADERBOARDS.name(), dbLeaderboardIds);
- leaderboardGroupCollection.update(query, dbLeaderboardGroup, true, false, WriteConcern.SAFE);
- }
-
- @Override
- public void removeLeaderboardGroup(String groupName) {
- DBCollection leaderboardGroupCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name());
- BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), groupName);
- leaderboardGroupCollection.remove(query);
- }
-
- @Override
- public void renameLeaderboardGroup(String oldName, String newName) {
- DBCollection leaderboardGroupCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name());
- BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), oldName);
- BasicDBObject update = new BasicDBObject("$set", new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), newName));
- leaderboardGroupCollection.update(query, update, /* upsert */ true, /* multi */ false, WriteConcern.SAFE);
- }
-
- @Override
- public void storeEvent(Event event) {
- DBCollection eventCollection = database.getCollection(CollectionNames.EVENTS.name());
- eventCollection.ensureIndex(FieldNames.EVENT_ID.name());
- DBObject query = new BasicDBObject();
- query.put(FieldNames.EVENT_ID.name(), event.getId());
- DBObject eventDBObject = new BasicDBObject();
- eventDBObject.put(FieldNames.EVENT_NAME.name(), event.getName());
- eventDBObject.put(FieldNames.EVENT_ID.name(), event.getId());
- storeTimePoint(event.getStartDate(), eventDBObject, FieldNames.EVENT_START_DATE);
- storeTimePoint(event.getEndDate(), eventDBObject, FieldNames.EVENT_END_DATE);
- eventDBObject.put(FieldNames.EVENT_IS_PUBLIC.name(), event.isPublic());
- DBObject venueDBObject = getVenueAsDBObject(event.getVenue());
- eventDBObject.put(FieldNames.VENUE.name(), venueDBObject);
- eventCollection.update(query, eventDBObject, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
- }
-
- @Override
- public void renameEvent(Serializable id, String newName) {
- DBCollection eventCollection = database.getCollection(CollectionNames.EVENTS.name());
- BasicDBObject query = new BasicDBObject(FieldNames.EVENT_ID.name(), id);
- BasicDBObject renameUpdate = new BasicDBObject("$set", new BasicDBObject(FieldNames.EVENT_NAME.name(), newName));
- eventCollection.update(query, renameUpdate, /* upsert */ true, /* multi */ false, WriteConcern.SAFE);
- }
-
- @Override
- public void removeEvent(Serializable id) {
- DBCollection eventsCollection = database.getCollection(CollectionNames.EVENTS.name());
- BasicDBObject query = new BasicDBObject(FieldNames.EVENT_ID.name(), id);
- eventsCollection.remove(query);
- }
-
- private DBObject getVenueAsDBObject(Venue venue) {
- DBObject result = new BasicDBObject();
- result.put(FieldNames.VENUE_NAME.name(), venue.getName());
- BasicDBList courseAreaList = new BasicDBList();
- result.put(FieldNames.COURSE_AREAS.name(), courseAreaList);
- for (CourseArea courseArea : venue.getCourseAreas()) {
- DBObject dbCourseArea = new BasicDBObject();
- courseAreaList.add(dbCourseArea);
- dbCourseArea.put(FieldNames.COURSE_AREA_NAME.name(), courseArea.getName());
- dbCourseArea.put(FieldNames.COURSE_AREA_ID.name(), courseArea.getId());
- }
- return result;
- }
-
- @Override
- public void storeRegatta(Regatta regatta) {
- DBCollection regattasCollection = database.getCollection(CollectionNames.REGATTAS.name());
- regattasCollection.ensureIndex(FieldNames.REGATTA_NAME.name());
- regattasCollection.ensureIndex(FieldNames.REGATTA_ID.name());
- DBObject dbRegatta = new BasicDBObject();
- DBObject query = new BasicDBObject(FieldNames.REGATTA_NAME.name(), regatta.getName());
- dbRegatta.put(FieldNames.REGATTA_NAME.name(), regatta.getName());
- dbRegatta.put(FieldNames.REGATTA_BASE_NAME.name(), regatta.getBaseName());
- dbRegatta.put(FieldNames.REGATTA_ID.name(), regatta.getId());
- dbRegatta.put(FieldNames.SCORING_SCHEME_TYPE.name(), regatta.getScoringScheme().getType().name());
- if (regatta.getBoatClass() != null) {
- dbRegatta.put(FieldNames.BOAT_CLASS_NAME.name(), regatta.getBoatClass().getName());
- dbRegatta.put(FieldNames.BOAT_CLASS_TYPICALLY_STARTS_UPWIND.name(), regatta.getBoatClass().typicallyStartsUpwind());
- }
- dbRegatta.put(FieldNames.REGATTA_SERIES.name(), storeSeries(regatta.getSeries()));
-
- if (regatta.getDefaultCourseArea() != null) {
- dbRegatta.put(FieldNames.COURSE_AREA_ID.name(), regatta.getDefaultCourseArea().getId().toString());
- } else {
- dbRegatta.put(FieldNames.COURSE_AREA_ID.name(), null);
- }
- if (regatta.getRegattaConfiguration() != null) {
- JsonSerializernull for the device type service finder and hence will be unable to store device identifiers.
+ * Use this constructor only for testing purposes or in cases where there will happen absolutely no access to
+ * {@link DeviceIdentifier} objects.
+ */
+ public MongoObjectFactoryImpl(DB database) {
+ this(database, /* deviceTypeServiceFinder */ null);
+ }
+
+ public MongoObjectFactoryImpl(DB database, TypeBasedServiceFinderFactory serviceFinderFactory) {
+ this.database = database;
+ if (serviceFinderFactory != null) {
+ this.deviceIdentifierServiceFinder = serviceFinderFactory.createServiceFinder(DeviceIdentifierMongoHandler.class);
+ this.deviceIdentifierServiceFinder.setFallbackService(new PlaceHolderDeviceIdentifierMongoHandler());
+ } else {
+ this.deviceIdentifierServiceFinder = null;
+ }
+ }
+
+ @Override
+ public DB getDatabase() {
+ return database;
+ }
+
+ public DBObject storeWind(Wind wind) {
+ DBObject result = new BasicDBObject();
+ storePositioned(wind, result);
+ storeTimed(wind, result);
+ storeSpeedWithBearing(wind, result);
+ return result;
+ }
+
+ public static void storeTimePoint(TimePoint timePoint, DBObject result, String fieldName) {
+ if (timePoint != null) {
+ result.put(fieldName, timePoint.asMillis());
+ }
+ }
+
+ public static void storeTimePoint(TimePoint timePoint, DBObject result, FieldNames field) {
+ storeTimePoint(timePoint, result, field.name());
+ }
+
+ public void storeTimed(Timed timed, DBObject result) {
+ if (timed.getTimePoint() != null) {
+ storeTimePoint(timed.getTimePoint(), result, FieldNames.TIME_AS_MILLIS);
+ }
+ }
+
+ public void storeSpeedWithBearing(SpeedWithBearing speedWithBearing, DBObject result) {
+ storeSpeed(speedWithBearing, result);
+ storeBearing(speedWithBearing.getBearing(), result);
+
+ }
+
+ public void storeBearing(Bearing bearing, DBObject result) {
+ result.put(FieldNames.DEGREE_BEARING.name(), bearing.getDegrees());
+ }
+
+ public void storeSpeed(Speed speed, DBObject result) {
+ result.put(FieldNames.KNOT_SPEED.name(), speed.getKnots());
+ }
+
+ public void storePositioned(Positioned positioned, DBObject result) {
+ if (positioned.getPosition() != null) {
+ result.put(FieldNames.LAT_DEG.name(), positioned.getPosition().getLatDeg());
+ result.put(FieldNames.LNG_DEG.name(), positioned.getPosition().getLngDeg());
+ }
+ }
+
+ @Override
+ public void addWindTrackDumper(TrackedRegatta trackedRegatta, TrackedRace trackedRace, WindSource windSource) {
+ WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource);
+ windTrack.addListener(new MongoWindListener(trackedRace, trackedRegatta.getRegatta().getName(), windSource, this, database));
+ }
+
+ public DBCollection getWindTrackCollection() {
+ DBCollection result = database.getCollection(CollectionNames.WIND_TRACKS.name());
+ result.ensureIndex(new BasicDBObject(FieldNames.REGATTA_NAME.name(), null));
+ return result;
+ }
+
+ public DBCollection getGPSFixCollection() {
+ DBCollection gpsFixCollection = database.getCollection(CollectionNames.GPS_FIXES.name());
+ DBObject index = new BasicDBObject();
+ index.put(FieldNames.DEVICE_ID.name(), null);
+ index.put(FieldNames.TIME_AS_MILLIS.name(), null);
+ return gpsFixCollection;
+ }
+
+ /**
+ * @param regattaName
+ * the regatta name is stored only for human readability purposes because a time stamp may be a bit unhandy for
+ * identifying where the wind fix was collected
+ */
+ public DBObject storeWindTrackEntry(RaceDefinition race, String regattaName, WindSource windSource, Wind wind) {
+ BasicDBObject result = new BasicDBObject();
+ result.put(FieldNames.RACE_ID.name(), race.getId());
+ result.put(FieldNames.REGATTA_NAME.name(), regattaName);
+ result.put(FieldNames.WIND_SOURCE_NAME.name(), windSource.name());
+ if (windSource.getId() != null) {
+ result.put(FieldNames.WIND_SOURCE_ID.name(), windSource.getId());
+ }
+ result.put(FieldNames.WIND.name(), storeWind(wind));
+ return result;
+ }
+
+ private void storeRaceIdentifiers(RaceColumn raceColumn, DBObject dbObject) {
+ BasicDBObject raceIdentifiersPerFleet = new BasicDBObject();
+ for (Fleet fleet : raceColumn.getFleets()) {
+ RaceIdentifier raceIdentifier = raceColumn.getRaceIdentifier(fleet);
+ if (raceIdentifier != null) {
+ DBObject raceIdentifierForFleet = new BasicDBObject();
+ storeRaceIdentifier(raceIdentifierForFleet, raceIdentifier);
+ raceIdentifiersPerFleet.put(MongoUtils.escapeDollarAndDot(fleet.getName()), raceIdentifierForFleet);
+ }
+ }
+ dbObject.put(FieldNames.RACE_IDENTIFIERS.name(), raceIdentifiersPerFleet);
+ }
+
+ private void storeRaceIdentifier(DBObject dbObject, RaceIdentifier raceIdentifier) {
+ if (raceIdentifier != null) {
+ dbObject.put(FieldNames.EVENT_NAME.name(), raceIdentifier.getRegattaName());
+ dbObject.put(FieldNames.RACE_NAME.name(), raceIdentifier.getRaceName());
+ }
+ }
+
+ @Override
+ public void storeLeaderboard(Leaderboard leaderboard) {
+ DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
+ try {
+ leaderboardCollection.ensureIndex(FieldNames.LEADERBOARD_NAME.name());
+ } catch (NullPointerException npe) {
+ // sometimes, for reasons yet to be clarified, ensuring an index on the name field causes an NPE
+ logger.log(Level.SEVERE, "storeLeaderboard", npe);
+ }
+ BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), leaderboard.getName());
+ BasicDBObject dbLeaderboard = new BasicDBObject();
+ dbLeaderboard.put(FieldNames.LEADERBOARD_NAME.name(), leaderboard.getName());
+ if (leaderboard.getDisplayName() != null) {
+ dbLeaderboard.put(FieldNames.LEADERBOARD_DISPLAY_NAME.name(), leaderboard.getDisplayName());
+ }
+ BasicDBList dbSuppressedCompetitorIds = new BasicDBList();
+ for (Competitor suppressedCompetitor : leaderboard.getSuppressedCompetitors()) {
+ dbSuppressedCompetitorIds.add(suppressedCompetitor.getId());
+ }
+ dbLeaderboard.put(FieldNames.LEADERBOARD_SUPPRESSED_COMPETITOR_IDS.name(), dbSuppressedCompetitorIds);
+ if (leaderboard instanceof FlexibleLeaderboard) {
+ storeFlexibleLeaderboard((FlexibleLeaderboard) leaderboard, dbLeaderboard);
+ } else if (leaderboard instanceof RegattaLeaderboard) {
+ storeRegattaLeaderboard((RegattaLeaderboard) leaderboard, dbLeaderboard);
+ } else {
+ // at least store the scoring scheme
+ dbLeaderboard.put(FieldNames.SCORING_SCHEME_TYPE.name(), leaderboard.getScoringScheme().getType().name());
+ }
+ if (leaderboard.getDefaultCourseArea() != null) {
+ dbLeaderboard.put(FieldNames.COURSE_AREA_ID.name(), leaderboard.getDefaultCourseArea().getId().toString());
+ } else {
+ dbLeaderboard.put(FieldNames.COURSE_AREA_ID.name(), null);
+ }
+ storeColumnFactors(leaderboard, dbLeaderboard);
+ storeLeaderboardCorrectionsAndDiscards(leaderboard, dbLeaderboard);
+ leaderboardCollection.update(query, dbLeaderboard, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
+ }
+
+ private void storeColumnFactors(Leaderboard leaderboard, BasicDBObject dbLeaderboard) {
+ DBObject raceColumnFactors = new BasicDBObject();
+ for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
+ Double explicitFactor = raceColumn.getExplicitFactor();
+ if (explicitFactor != null) {
+ raceColumnFactors.put(MongoUtils.escapeDollarAndDot(raceColumn.getName()), explicitFactor);
+ }
+ }
+ dbLeaderboard.put(FieldNames.LEADERBOARD_COLUMN_FACTORS.name(), raceColumnFactors);
+ }
+
+ private void storeRegattaLeaderboard(RegattaLeaderboard leaderboard, DBObject dbLeaderboard) {
+ dbLeaderboard.put(FieldNames.REGATTA_NAME.name(), leaderboard.getRegatta().getName());
+ }
+
+ private void storeFlexibleLeaderboard(FlexibleLeaderboard leaderboard, BasicDBObject dbLeaderboard) {
+ BasicDBList dbRaceColumns = new BasicDBList();
+ dbLeaderboard.put(FieldNames.SCORING_SCHEME_TYPE.name(), leaderboard.getScoringScheme().getType().name());
+ dbLeaderboard.put(FieldNames.LEADERBOARD_COLUMNS.name(), dbRaceColumns);
+ for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
+ BasicDBObject dbRaceColumn = storeRaceColumn(raceColumn);
+ dbRaceColumns.add(dbRaceColumn);
+ }
+ }
+
+ private void storeLeaderboardCorrectionsAndDiscards(Leaderboard leaderboard, BasicDBObject dbLeaderboard) {
+ if (leaderboard.hasCarriedPoints()) {
+ BasicDBList dbCarriedPoints = new BasicDBList();
+ dbLeaderboard.put(FieldNames.LEADERBOARD_CARRIED_POINTS_BY_ID.name(), dbCarriedPoints);
+ for (EntrydbObject's field identified by field if the result discarding
+ * rule is not null and is of type {@link ThresholdBasedResultDiscardingRule}. Otherwise, it is assumed that the
+ * result discarding rule is otherwise implicitly obtained, e.g., from a definition of a regatta with its series, stored elsewhere.
+ */
+ private void storeResultDiscardingRule(DBObject dbObject,
+ final ResultDiscardingRule resultDiscardingRule, FieldNames field) {
+ if (resultDiscardingRule != null && resultDiscardingRule instanceof ThresholdBasedResultDiscardingRule) {
+ BasicDBList dbResultDiscardingThresholds = new BasicDBList();
+ for (int threshold : ((ThresholdBasedResultDiscardingRule) resultDiscardingRule).getDiscardIndexResultsStartingWithHowManyRaces()) {
+ dbResultDiscardingThresholds.add(threshold);
+ }
+ dbObject.put(field.name(), dbResultDiscardingThresholds);
+ }
+ }
+
+ private BasicDBObject storeRaceColumn(RaceColumn raceColumn) {
+ BasicDBObject dbRaceColumn = new BasicDBObject();
+ dbRaceColumn.put(FieldNames.LEADERBOARD_COLUMN_NAME.name(), raceColumn.getName());
+ dbRaceColumn.put(FieldNames.LEADERBOARD_IS_MEDAL_RACE_COLUMN.name(), raceColumn.isMedalRace());
+ storeRaceIdentifiers(raceColumn, dbRaceColumn);
+ return dbRaceColumn;
+ }
+
+ private void storeScoreCorrections(Leaderboard leaderboard, BasicDBObject dbScoreCorrections) {
+ TimePoint now = MillisecondsTimePoint.now();
+ SettableScoreCorrection scoreCorrection = leaderboard.getScoreCorrection();
+ for (RaceColumn raceColumn : scoreCorrection.getRaceColumnsThatHaveCorrections()) {
+ BasicDBList dbCorrectionForRace = new BasicDBList();
+ for (Competitor competitor : scoreCorrection.getCompetitorsThatHaveCorrectionsIn(raceColumn)) {
+ // TODO bug 655: make score corrections time dependent
+ if (scoreCorrection.isScoreCorrected(competitor, raceColumn, now)) {
+ BasicDBObject dbCorrectionForCompetitor = new BasicDBObject();
+ dbCorrectionForCompetitor.put(FieldNames.COMPETITOR_ID.name(), competitor.getId());
+ MaxPointsReason maxPointsReason = scoreCorrection.getMaxPointsReason(competitor, raceColumn, now);
+ if (maxPointsReason != MaxPointsReason.NONE) {
+ dbCorrectionForCompetitor.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON.name(),
+ maxPointsReason.name());
+ }
+ Double explicitScoreCorrection = scoreCorrection
+ .getExplicitScoreCorrection(competitor, raceColumn);
+ if (explicitScoreCorrection != null) {
+ dbCorrectionForCompetitor.put(FieldNames.LEADERBOARD_CORRECTED_SCORE.name(),
+ explicitScoreCorrection);
+ }
+ dbCorrectionForRace.add(dbCorrectionForCompetitor);
+ }
+ }
+ if (!dbCorrectionForRace.isEmpty()) {
+ // using the column name as the key for the score corrections requires re-writing the score corrections
+ // of a meta-leaderboard if the name of one of its leaderboards changes
+ dbScoreCorrections.put(MongoUtils.escapeDollarAndDot(raceColumn.getName()), dbCorrectionForRace);
+ }
+ }
+ final TimePoint timePointOfLastCorrectionsValidity = scoreCorrection.getTimePointOfLastCorrectionsValidity();
+ if (timePointOfLastCorrectionsValidity != null) {
+ dbScoreCorrections.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_TIMESTAMP.name(), timePointOfLastCorrectionsValidity.asMillis());
+ }
+ if (scoreCorrection.getComment() != null) {
+ dbScoreCorrections.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_COMMENT.name(), scoreCorrection.getComment());
+ }
+ }
+
+ @Override
+ public void removeLeaderboard(String leaderboardName) {
+ DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
+ BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), leaderboardName);
+ leaderboardCollection.remove(query);
+ }
+
+ @Override
+ public void renameLeaderboard(String oldName, String newName) {
+ DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
+ BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), oldName);
+ BasicDBObject renameUpdate = new BasicDBObject("$set", new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), newName));
+ leaderboardCollection.update(query, renameUpdate, /* upsert */ true, /* multi */ false, WriteConcern.SAFE);
+ }
+
+ @Override
+ public void storeLeaderboardGroup(LeaderboardGroup leaderboardGroup) {
+ DBCollection leaderboardGroupCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name());
+ DBCollection leaderboardCollection = database.getCollection(CollectionNames.LEADERBOARDS.name());
+
+ try {
+ leaderboardGroupCollection.ensureIndex(FieldNames.LEADERBOARD_GROUP_NAME.name());
+ } catch (NullPointerException npe) {
+ // sometimes, for reasons yet to be clarified, ensuring an index on the name field causes an NPE
+ logger.log(Level.SEVERE, "storeLeaderboardGroup", npe);
+ }
+ BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), leaderboardGroup.getName());
+ BasicDBObject dbLeaderboardGroup = new BasicDBObject();
+ dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_NAME.name(), leaderboardGroup.getName());
+ dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_DESCRIPTION.name(), leaderboardGroup.getDescription());
+ dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_DISPLAY_IN_REVERSE_ORDER.name(), leaderboardGroup.isDisplayGroupsInReverseOrder());
+ final Leaderboard overallLeaderboard = leaderboardGroup.getOverallLeaderboard();
+ if (overallLeaderboard != null) {
+ BasicDBObject overallLeaderboardQuery = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), overallLeaderboard.getName());
+ DBObject dbOverallLeaderboard = leaderboardCollection.findOne(overallLeaderboardQuery);
+ if (dbOverallLeaderboard == null) {
+ storeLeaderboard(overallLeaderboard);
+ dbOverallLeaderboard = leaderboardCollection.findOne(overallLeaderboardQuery);
+ }
+ ObjectId dbOverallLeaderboardId = (ObjectId) dbOverallLeaderboard.get("_id");
+ dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_OVERALL_LEADERBOARD.name(), dbOverallLeaderboardId);
+ }
+ BasicDBList dbLeaderboardIds = new BasicDBList();
+ for (Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) {
+ BasicDBObject leaderboardQuery = new BasicDBObject(FieldNames.LEADERBOARD_NAME.name(), leaderboard.getName());
+ DBObject dbLeaderboard = leaderboardCollection.findOne(leaderboardQuery);
+ if (dbLeaderboard == null) {
+ storeLeaderboard(leaderboard);
+ dbLeaderboard = leaderboardCollection.findOne(leaderboardQuery);
+ }
+ ObjectId dbLeaderboardId = (ObjectId) dbLeaderboard.get("_id");
+ dbLeaderboardIds.add(dbLeaderboardId);
+ }
+ dbLeaderboardGroup.put(FieldNames.LEADERBOARD_GROUP_LEADERBOARDS.name(), dbLeaderboardIds);
+ leaderboardGroupCollection.update(query, dbLeaderboardGroup, true, false, WriteConcern.SAFE);
+ }
+
+ @Override
+ public void removeLeaderboardGroup(String groupName) {
+ DBCollection leaderboardGroupCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name());
+ BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), groupName);
+ leaderboardGroupCollection.remove(query);
+ }
+
+ @Override
+ public void renameLeaderboardGroup(String oldName, String newName) {
+ DBCollection leaderboardGroupCollection = database.getCollection(CollectionNames.LEADERBOARD_GROUPS.name());
+ BasicDBObject query = new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), oldName);
+ BasicDBObject update = new BasicDBObject("$set", new BasicDBObject(FieldNames.LEADERBOARD_GROUP_NAME.name(), newName));
+ leaderboardGroupCollection.update(query, update, /* upsert */ true, /* multi */ false, WriteConcern.SAFE);
+ }
+
+ @Override
+ public void storeEvent(Event event) {
+ DBCollection eventCollection = database.getCollection(CollectionNames.EVENTS.name());
+ eventCollection.ensureIndex(FieldNames.EVENT_ID.name());
+ DBObject query = new BasicDBObject();
+ query.put(FieldNames.EVENT_ID.name(), event.getId());
+ DBObject eventDBObject = new BasicDBObject();
+ eventDBObject.put(FieldNames.EVENT_NAME.name(), event.getName());
+ eventDBObject.put(FieldNames.EVENT_ID.name(), event.getId());
+ storeTimePoint(event.getStartDate(), eventDBObject, FieldNames.EVENT_START_DATE);
+ storeTimePoint(event.getEndDate(), eventDBObject, FieldNames.EVENT_END_DATE);
+ eventDBObject.put(FieldNames.EVENT_IS_PUBLIC.name(), event.isPublic());
+ DBObject venueDBObject = getVenueAsDBObject(event.getVenue());
+ eventDBObject.put(FieldNames.VENUE.name(), venueDBObject);
+ eventCollection.update(query, eventDBObject, /* upsrt */ true, /* multi */ false, WriteConcern.SAFE);
+ }
+
+ @Override
+ public void renameEvent(Serializable id, String newName) {
+ DBCollection eventCollection = database.getCollection(CollectionNames.EVENTS.name());
+ BasicDBObject query = new BasicDBObject(FieldNames.EVENT_ID.name(), id);
+ BasicDBObject renameUpdate = new BasicDBObject("$set", new BasicDBObject(FieldNames.EVENT_NAME.name(), newName));
+ eventCollection.update(query, renameUpdate, /* upsert */ true, /* multi */ false, WriteConcern.SAFE);
+ }
+
+ @Override
+ public void removeEvent(Serializable id) {
+ DBCollection eventsCollection = database.getCollection(CollectionNames.EVENTS.name());
+ BasicDBObject query = new BasicDBObject(FieldNames.EVENT_ID.name(), id);
+ eventsCollection.remove(query);
+ }
+
+ private DBObject getVenueAsDBObject(Venue venue) {
+ DBObject result = new BasicDBObject();
+ result.put(FieldNames.VENUE_NAME.name(), venue.getName());
+ BasicDBList courseAreaList = new BasicDBList();
+ result.put(FieldNames.COURSE_AREAS.name(), courseAreaList);
+ for (CourseArea courseArea : venue.getCourseAreas()) {
+ DBObject dbCourseArea = new BasicDBObject();
+ courseAreaList.add(dbCourseArea);
+ dbCourseArea.put(FieldNames.COURSE_AREA_NAME.name(), courseArea.getName());
+ dbCourseArea.put(FieldNames.COURSE_AREA_ID.name(), courseArea.getId());
+ }
+ return result;
+ }
+
+ @Override
+ public void storeRegatta(Regatta regatta) {
+ DBCollection regattasCollection = database.getCollection(CollectionNames.REGATTAS.name());
+ regattasCollection.ensureIndex(FieldNames.REGATTA_NAME.name());
+ regattasCollection.ensureIndex(FieldNames.REGATTA_ID.name());
+ DBObject dbRegatta = new BasicDBObject();
+ DBObject query = new BasicDBObject(FieldNames.REGATTA_NAME.name(), regatta.getName());
+ dbRegatta.put(FieldNames.REGATTA_NAME.name(), regatta.getName());
+ dbRegatta.put(FieldNames.REGATTA_BASE_NAME.name(), regatta.getBaseName());
+ dbRegatta.put(FieldNames.REGATTA_ID.name(), regatta.getId());
+ dbRegatta.put(FieldNames.SCORING_SCHEME_TYPE.name(), regatta.getScoringScheme().getType().name());
+ if (regatta.getBoatClass() != null) {
+ dbRegatta.put(FieldNames.BOAT_CLASS_NAME.name(), regatta.getBoatClass().getName());
+ dbRegatta.put(FieldNames.BOAT_CLASS_TYPICALLY_STARTS_UPWIND.name(), regatta.getBoatClass().typicallyStartsUpwind());
+ }
+ dbRegatta.put(FieldNames.REGATTA_SERIES.name(), storeSeries(regatta.getSeries()));
+
+ if (regatta.getDefaultCourseArea() != null) {
+ dbRegatta.put(FieldNames.COURSE_AREA_ID.name(), regatta.getDefaultCourseArea().getId().toString());
+ } else {
+ dbRegatta.put(FieldNames.COURSE_AREA_ID.name(), null);
+ }
+ if (regatta.getRegattaConfiguration() != null) {
+ JsonSerializer