Merge branch 'master' into bug4006

This commit is contained in:
Axel Uhl committed 2020-01-28 14:03:43 +01:00
commit e78fa7ce12
415 files changed
+20757 -806

No files matched your search

@@ -41,16 +41,15 @@ import java.io.Serializable;
*/
public interface DeviceIdentifier extends Serializable {
/**
* The returned {@link String} is used to look up corresponding services for serialization
* and persistence.
* The returned {@link String} is used to look up corresponding services for serialization and persistence.
*
* The reason for this design choice is that in future, third parties could easily write their own adapter and only
* have to register new OSGi service, and not touch the SAP Sailing Analytics code.
* The reason for this design choice is that in the future third parties could easily write their own adapter and
* only have to register a new OSGi service, and not touch the SAP Sailing Analytics code.
*/
String getIdentifierType();
/**
* Create a string representation, that can identify this device.
* Create a string representation that can identify this device.
* The returned values should be unique for this identifier within its {@link #getIdentifierType() type},
* but need not include the {@link #getIdentifierType() type} itself in the representation.
*/
@@ -63,7 +63,7 @@ public class RegattaNameAndRaceName extends RegattaName implements RegattaAndRac
@Override
public QualifiedObjectIdentifier getIdentifier() {
return getType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
return getPermissionType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
}
@Override
@@ -76,7 +76,7 @@ public class RegattaNameAndRaceName extends RegattaName implements RegattaAndRac
}
@Override
public HasPermissions getType() {
public HasPermissions getPermissionType() {
return SecuredDomainType.TRACKED_RACE;
}
@@ -122,13 +122,13 @@ public class BoatDTO extends NamedSecuredObjectDTO implements WithID, Serializab
}
@Override
public HasPermissions getType() {
public HasPermissions getPermissionType() {
return SecuredDomainType.BOAT;
}
@Override
public QualifiedObjectIdentifier getIdentifier() {
return getType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
return getPermissionType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
}
public TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier() {
@@ -247,13 +247,13 @@ public class CompetitorDTOImpl extends NamedSecuredObjectDTO implements Competit
}
@Override
public HasPermissions getType() {
public HasPermissions getPermissionType() {
return SecuredDomainType.COMPETITOR;
}
@Override
public QualifiedObjectIdentifier getIdentifier() {
return getType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
return getPermissionType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
}
public TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier() {
@@ -174,13 +174,13 @@ public class PreviousCompetitorDTOImpl extends NamedSecuredObjectDTO implements
}
@Override
public HasPermissions getType() {
public HasPermissions getPermissionType() {
return SecuredDomainType.COMPETITOR;
}
@Override
public QualifiedObjectIdentifier getIdentifier() {
return getType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
return getPermissionType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
}
public TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier() {
@@ -129,13 +129,13 @@ public class RaceDTO extends BasicRaceDTO implements SecuredDTO {
}
@Override
public HasPermissions getType() {
public HasPermissions getPermissionType() {
return SecuredDomainType.TRACKED_RACE;
}
@Override
public QualifiedObjectIdentifier getIdentifier() {
return getType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
return getPermissionType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
}
public TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier() {
@@ -171,11 +171,11 @@ public class MediaTrack implements Serializable, WithQualifiedObjectIdentifier {
@Override
public QualifiedObjectIdentifier getIdentifier() {
return getType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
return getPermissionType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
}
@Override
public HasPermissions getType() {
public HasPermissions getPermissionType() {
return SecuredDomainType.MEDIA_TRACK;
}
@@ -88,4 +88,8 @@ public class SecuredDomainType extends HasPermissionsImpl {
"SWISS_TIMING_ARCHIVE_ACCOUNT");
public static final HasPermissions TRACTRAC_ACCOUNT = new SecuredDomainType("TRACTRAC_ACCOUNT");
public static final HasPermissions WIND_ESTIMATION_MODELS = new SecuredDomainType("WIND_ESTIMATION_MODELS");
public static final HasPermissions MARK_PROPERTIES = new SecuredDomainType("MARK_PROPERTIES");
public static final HasPermissions MARK_TEMPLATE = new SecuredDomainType("MARK_TEMPLATE");
public static final HasPermissions COURSE_TEMPLATE = new SecuredDomainType("COURSE_TEMPLATE");
public static final HasPermissions MARK_ROLE = new SecuredDomainType("MARK_ROLE");
}
@@ -15,7 +15,7 @@ public interface Account extends WithQualifiedObjectIdentifier {
String getCreatorName();
@Override
default HasPermissions getType() {
default HasPermissions getPermissionType() {
return SecuredDomainType.IGTIMI_ACCOUNT;
}
@@ -27,7 +27,7 @@ public interface Account extends WithQualifiedObjectIdentifier {
@Override
default QualifiedObjectIdentifier getIdentifier() {
return getType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
return getPermissionType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
}
@@ -16,7 +16,8 @@ Require-Bundle: com.sap.sse.mongodb,
com.sap.sailing.server.gateway.serialization.shared.android,
com.sap.sse.shared.android,
org.mongodb.mongo-java-driver;bundle-version="3.6.4",
com.sap.sse.security.common
com.sap.sse.security.common,
com.sap.sailing.shared.persistence
Export-Package: com.sap.sailing.domain.persistence,
com.sap.sailing.domain.persistence.impl;
x-friends:="com.sap.sailing.domain.persistence.test,
@@ -120,5 +120,6 @@ public enum CollectionNames {
/**
* Contains the known anniversaries
*/
ANNIVERSARIES
ANNIVERSARIES;
}
@@ -1,6 +1,8 @@
package com.sap.sailing.domain.persistence.impl;
import static com.mongodb.client.model.Filters.eq;
import static com.sap.sailing.shared.persistence.impl.DomainObjectFactoryImpl.loadDeviceId;
import static com.sap.sailing.shared.persistence.impl.DomainObjectFactoryImpl.loadPosition;
import java.io.Serializable;
import java.net.MalformedURLException;
@@ -187,7 +189,6 @@ import com.sap.sailing.domain.common.DeviceIdentifier;
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.RankingMetrics;
import com.sap.sailing.domain.common.RegattaName;
@@ -199,7 +200,6 @@ import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.dto.AnniversaryType;
import com.sap.sailing.domain.common.dto.EventType;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.impl.NauticalMileDistance;
import com.sap.sailing.domain.common.impl.WindImpl;
@@ -211,7 +211,6 @@ import com.sap.sailing.domain.common.orc.ORCPerformanceCurveLegTypes;
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.common.racelog.tracking.TransformationException;
import com.sap.sailing.domain.common.tracking.impl.CompetitorJsonConstants;
import com.sap.sailing.domain.leaderboard.DelayedLeaderboardCorrections;
import com.sap.sailing.domain.leaderboard.EventResolver;
@@ -235,11 +234,8 @@ 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.persistence.MongoRegattaLogStoreFactory;
import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler;
import com.sap.sailing.domain.persistence.racelog.tracking.impl.PlaceHolderDeviceIdentifierMongoHandler;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
import com.sap.sailing.domain.racelog.RaceLogStore;
import com.sap.sailing.domain.racelogtracking.impl.PlaceHolderDeviceIdentifierSerializationHandler;
import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
import com.sap.sailing.domain.ranking.RankingMetricConstructor;
import com.sap.sailing.domain.ranking.RankingMetricsFactory;
@@ -261,11 +257,12 @@ import com.sap.sailing.server.gateway.deserialization.impl.RegattaConfigurationJ
import com.sap.sailing.server.gateway.deserialization.racelog.impl.ImpliedWindSourceDeserializer;
import com.sap.sailing.server.gateway.deserialization.racelog.impl.ORCCertificateJsonDeserializer;
import com.sap.sailing.server.gateway.serialization.impl.DeviceConfigurationJsonSerializer;
import com.sap.sailing.shared.persistence.device.DeviceIdentifierMongoHandler;
import com.sap.sailing.shared.persistence.device.impl.PlaceHolderDeviceIdentifierMongoHandler;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.Color;
import com.sap.sse.common.Distance;
import com.sap.sse.common.Duration;
import com.sap.sse.common.NoCorrespondingServiceRegisteredException;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.TimeRange;
import com.sap.sse.common.TypeBasedServiceFinder;
@@ -339,18 +336,6 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
return new WindImpl(loadPosition(object), loadTimePoint(object), loadSpeedWithBearing(object));
}
public Position loadPosition(Document 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(Document object, String fieldName) {
TimePoint result = null;
Number timePointAsNumber = (Number) object.get(fieldName);
@@ -1913,8 +1898,34 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId,
List<Competitor> competitors, Document dbObject) {
String courseName = (String) dbObject.get(FieldNames.RACE_LOG_COURSE_DESIGN_NAME.name());
UUID courseOriginatingTemplateId = (UUID) dbObject.get(FieldNames.RACE_LOG_COURSE_ORIGINATING_TEMPLATE_ID.name());
Pair<CourseBase, Boolean> courseData = loadCourseData((Iterable<?>) dbObject.get(FieldNames.RACE_LOG_COURSE_DESIGN.name()),
courseName);
courseName, courseOriginatingTemplateId);
// load associated roles
final ArrayList<?> markList = dbObject.get(FieldNames.RACE_LOG_COURSE_ASSOCIATED_ROLES.name(), ArrayList.class);
if (markList != null) {
for (final Object entry : markList) {
if (entry instanceof Document) {
final Document entryObject = (Document) entry;
final UUID markUUID = UUID.fromString(
entryObject.getString(FieldNames.RACE_LOG_COURSE_ASSOCIATED_ROLES_MARK_ID.name()));
final Mark mark = baseDomainFactory.getExistingMarkById(markUUID);
if (mark != null) {
final String roleIdAsStringOrNull = entryObject
.getString(FieldNames.RACE_LOG_COURSE_ASSOCIATED_ROLES_ROLE_ID.name());
if (roleIdAsStringOrNull != null) {
courseData.getA().addRoleMapping(mark, UUID.fromString(roleIdAsStringOrNull));
}
} else {
logger.warning(String.format("Could not resolve mark with id %s for course %s.", markUUID, id));
}
} else {
logger.warning(String.format("Unexpected mark entry found for course %s.", id));
}
}
}
final String courseDesignerModeName = (String) dbObject.get(FieldNames.RACE_LOG_COURSE_DESIGNER_MODE.name());
final CourseDesignerMode courseDesignerMode = courseDesignerModeName == null ? null
: CourseDesignerMode.valueOf(courseDesignerModeName);
@@ -2450,12 +2461,13 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
* been edited "in place" to describe the migration that has happened
*/
@SuppressWarnings("deprecation") // Used to migrate from PASSINGSIDE to the new PASSINGINSTRUCTIONS
private Pair<CourseBase, Boolean> loadCourseData(Iterable<?> dbCourseList, String courseName) {
private Pair<CourseBase, Boolean> loadCourseData(Iterable<?> dbCourseList, String courseName,
UUID originatingCourseTemplateId) {
boolean migrated = false;
if (courseName == null) {
courseName = "Course";
}
CourseBase courseData = new CourseDataImpl(courseName);
CourseBase courseData = new CourseDataImpl(courseName, originatingCourseTemplateId);
int i = 0;
for (Object object : dbCourseList) {
Document dbObject = (Document) object;
@@ -2557,8 +2569,13 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
dbObject.remove(FieldNames.GATE_RIGHT.name());
}
Mark rightMark = loadMark(dbRight);
String shortName = (String) dbObject.get(FieldNames.CONTROLPOINTWITHTWOMARKS_SHORT_NAME.name());
if (shortName == null || shortName.isEmpty()) {
shortName = controlPointName;
}
ControlPointWithTwoMarks gate = baseDomainFactory.createControlPointWithTwoMarks(controlPointId, leftMark,
rightMark, controlPointName);
rightMark, controlPointName, shortName);
return gate;
}
@@ -2567,12 +2584,21 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
String markColorAsString = (String) dbObject.get(FieldNames.MARK_COLOR.name());
Color markColor = AbstractColor.getCssColor(markColorAsString);
String markName = (String) dbObject.get(FieldNames.MARK_NAME.name());
String markShortName = (String) dbObject.get(FieldNames.MARK_SHORT_NAME.name());
String markPattern = (String) dbObject.get(FieldNames.MARK_PATTERN.name());
String markShape = (String) dbObject.get(FieldNames.MARK_SHAPE.name());
Object markTypeRaw = dbObject.get(FieldNames.MARK_TYPE.name());
Object originatingMarkTemplateIdObject = dbObject.get(FieldNames.MARK_ORIGINATING_MARK_TEMPLATE_ID.name());
UUID originatingMarkTemplateId = originatingMarkTemplateIdObject == null ? null
: UUID.fromString(originatingMarkTemplateIdObject.toString());
Object originatingMarkPropertiesIdObject = dbObject
.get(FieldNames.MARK_ORIGINATING_MARK_PROPERTIES_ID.name());
UUID originatingMarkPropertiesId = originatingMarkPropertiesIdObject == null ? null
: UUID.fromString(originatingMarkPropertiesIdObject.toString());
MarkType markType = markTypeRaw == null ? null : MarkType.valueOf((String) markTypeRaw);
Mark mark = baseDomainFactory.getOrCreateMark(markId, markName, markType, markColor, markShape, markPattern);
Mark mark = baseDomainFactory.getOrCreateMark(markId, markName, markShortName, markType, markColor, markShape,
markPattern, originatingMarkTemplateId, originatingMarkPropertiesId);
return mark;
}
@@ -2683,6 +2709,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
return result;
}
private DeviceConfiguration loadConfigurationEntry(Document dbObject) throws JsonDeserializationException, ParseException {
final String idAsString = dbObject.getString(FieldNames.CONFIGURATION_ID_AS_STRING.name());
final DeviceConfiguration configuration;
@@ -2719,22 +2746,6 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
return configuration;
}
private DeviceIdentifier loadDeviceId(
TypeBasedServiceFinder<DeviceIdentifierMongoHandler> deviceIdentifierServiceFinder, Document deviceId)
throws TransformationException, NoCorrespondingServiceRegisteredException {
String deviceType = (String) deviceId.get(FieldNames.DEVICE_TYPE.name());
Object deviceTypeId = deviceId.get(FieldNames.DEVICE_TYPE_SPECIFIC_ID.name());
String stringRepresentation = (String) deviceId.get(FieldNames.DEVICE_STRING_REPRESENTATION.name());
try {
return deviceIdentifierServiceFinder.findService(deviceType).deserialize(deviceTypeId, deviceType,
stringRepresentation);
} catch (TransformationException e) {
return new PlaceHolderDeviceIdentifierSerializationHandler().deserialize(stringRepresentation, deviceType,
stringRepresentation);
}
}
@Override
public Map<String, Set<URL>> loadResultUrls() {
Map<String, Set<URL>> resultUrls = new HashMap<>();
@@ -3051,4 +3062,5 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
}
return fromDb;
}
}
@@ -100,8 +100,9 @@ public enum FieldNames {
RACE_LOG_IDENTIFIER, RACE_LOG_EVENT, RACE_LOG_EVENT_PASS_ID, RACE_LOG_EVENT_ID, RACE_LOG_EVENT_CREATED_AT, RACE_LOG_EVENT_AUTHOR_NAME,
RACE_LOG_EVENT_AUTHOR_PRIORITY, RACE_LOG_EVENT_INVOLVED_BOATS, RACE_LOG_EVENT_FLAG_UPPER, RACE_LOG_EVENT_FLAG_LOWER, RACE_LOG_EVENT_FLAG_DISPLAYED,
RACE_LOG_EVENT_START_TIME, RACE_LOG_EVENT_CLASS, RACE_LOG_EVENT_NEXT_STATUS, RACE_LOG_COURSE_DESIGN, RACE_LOG_COURSE_DESIGNER_MODE, WAYPOINT_ID, WAYPOINT_NAME,
WAYPOINT_PASSINGINSTRUCTIONS, CONTROLPOINT, CONTROLPOINT_CLASS, MARK_ID, MARK_COLOR, MARK_NAME, MARK_PATTERN, MARK_SHAPE, MARK_TYPE,
CONTROLPOINT_VALUE, CONTROLPOINTWITHTWOMARKS_ID, CONTROLPOINTWITHTWOMARKS_NAME, CONTROLPOINTWITHTWOMARKS_LEFT, CONTROLPOINTWITHTWOMARKS_RIGHT,
WAYPOINT_PASSINGINSTRUCTIONS, CONTROLPOINT, CONTROLPOINT_CLASS, MARK_ID, MARK_COLOR, MARK_NAME, MARK_SHORT_NAME, MARK_PATTERN, MARK_SHAPE, MARK_TYPE,
MARK_ORIGINATING_MARK_TEMPLATE_ID, MARK_ORIGINATING_MARK_PROPERTIES_ID, RACE_LOG_COURSE_ORIGINATING_TEMPLATE_ID, RACE_LOG_COURSE_ASSOCIATED_ROLES, RACE_LOG_COURSE_ASSOCIATED_ROLES_MARK_ID, RACE_LOG_COURSE_ASSOCIATED_ROLES_ROLE_ID,
CONTROLPOINT_VALUE, CONTROLPOINTWITHTWOMARKS_ID, CONTROLPOINTWITHTWOMARKS_NAME, CONTROLPOINTWITHTWOMARKS_SHORT_NAME, CONTROLPOINTWITHTWOMARKS_LEFT, CONTROLPOINTWITHTWOMARKS_RIGHT,
RACE_LOG_POSITIONED_COMPETITORS, RACE_LOG_PATHFINDER_ID, RACE_LOG_GATE_LINE_OPENING_TIME, RACE_LOG_GOLF_DOWN_TIME, RACE_LOG_START_PROCEDURE_TYPE,
RACE_LOG_PROTEST_START_TIME, RACE_LOG_PROTEST_END_TIME, RACE_LOG_COURSE_DESIGN_NAME, RACE_LOG_REVOKED_EVENT_ID, RACE_LOG_REVOKED_EVENT_TYPE,
RACE_LOG_REVOKED_EVENT_SHORT_INFO, RACE_LOG_REVOKED_REASON, RACE_LOG_DEVICE_MAPPING_EVENT_ID,
@@ -116,7 +117,7 @@ public enum FieldNames {
/** @deprecated Replaced by CONTROLPOINTWITHTWOMARKS_NAME, still used to migrate old races*/ @Deprecated GATE_NAME,
/** @deprecated Replaced by CONTROLPOINTWITHTWOMARKS_LEFT, still used to migrate old races*/ @Deprecated GATE_LEFT,
/** @deprecated Replaced by CONTROLPOINTWITHTWOMARKS_RIGHT, still used to migrate old races*/ @Deprecated GATE_RIGHT,
//ORC Leg fields
ORC_LEG_NR, ORC_LEG_LENGTH_IN_NAUTICAL_MILES, ORC_LEG_TWA_IN_DEG, ORC_LEG_TYPE,
@@ -141,10 +142,10 @@ public enum FieldNames {
CONFIGURATION_CONFIG_PROCEDURES,
// GPSFixes
DEVICE_TYPE_SPECIFIC_ID, DEVICE_ID, DEVICE_TYPE, GPSFIX_TYPE, GPSFIX, DEVICE_STRING_REPRESENTATION,
DEVICE_ID, GPSFIX_TYPE, GPSFIX,
// GPSFixes metadata
TIMERANGE, NUM_FIXES,
TIMERANGE, NUM_FIXES, LAST_FIX_RECEIVED,
// Timespan
FROM_MILLIS, TO_MILLIS,
@@ -183,5 +184,4 @@ public enum FieldNames {
ORC_CERTIFICATE_R75_PREDICTION, ORC_CERTIFICATE_R90_PREDICTION, ORC_CERTIFICATE_R110_PREDICTION, ORC_CERTIFICATE_R120_PREDICTION,
ORC_CERTIFICATE_R135_PREDICTION, ORC_CERTIFICATE_R150_PREDICTION, ORC_CERTIFICATE_BOATCLASS, ORC_CERTIFICATE_LENGTH, ORC_CERTIFICATE_TWA_SPEED_PREDICTIONS,
ORC_CERTIFICATE_CIRCULAR_RANDOM_SPEED_PREDICTIONS, ORC_CERTIFICATE_NON_SPINNAKER_SPEED_PREDICTIONS, ORC_OTHER_RACE_IDENTIFIER, ORC_IMPLIED_WIND_SOURCE;
}
@@ -1,5 +1,8 @@
package com.sap.sailing.domain.persistence.impl;
import static com.sap.sailing.shared.persistence.impl.MongoObjectFactoryImpl.getPassingInstructions;
import static com.sap.sailing.shared.persistence.impl.MongoObjectFactoryImpl.storeDeviceId;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
@@ -98,7 +101,6 @@ import com.sap.sailing.domain.base.configuration.RegattaConfiguration;
import com.sap.sailing.domain.base.impl.FleetImpl;
import com.sap.sailing.domain.common.DeviceIdentifier;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.PassingInstruction;
import com.sap.sailing.domain.common.Positioned;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.SpeedWithBearing;
@@ -116,8 +118,6 @@ import com.sap.sailing.domain.leaderboard.ResultDiscardingRule;
import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.persistence.MongoObjectFactory;
import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler;
import com.sap.sailing.domain.persistence.racelog.tracking.impl.PlaceHolderDeviceIdentifierMongoHandler;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier;
import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters;
@@ -134,6 +134,8 @@ import com.sap.sailing.server.gateway.serialization.impl.DeviceConfigurationJson
import com.sap.sailing.server.gateway.serialization.impl.RegattaConfigurationJsonSerializer;
import com.sap.sailing.server.gateway.serialization.racelog.impl.ImpliedWindSourceSerializer;
import com.sap.sailing.server.gateway.serialization.racelog.impl.ORCCertificateJsonSerializer;
import com.sap.sailing.shared.persistence.device.DeviceIdentifierMongoHandler;
import com.sap.sailing.shared.persistence.device.impl.PlaceHolderDeviceIdentifierMongoHandler;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.Duration;
import com.sap.sse.common.NoCorrespondingServiceRegisteredException;
@@ -258,9 +260,9 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
dropIndexSafe(gpsFixCollection, "DEVICE_ID_1_GPSFIX.TIME_AS_MILLIS_1");
Document index = new Document();
index.put(FieldNames.TIME_AS_MILLIS.name(), 1);
index.put(FieldNames.DEVICE_ID.name()+"."+FieldNames.DEVICE_TYPE.name(), 1);
index.put(FieldNames.DEVICE_ID.name()+"."+FieldNames.DEVICE_TYPE_SPECIFIC_ID.name(), 1);
index.put(FieldNames.DEVICE_ID.name()+"."+FieldNames.DEVICE_STRING_REPRESENTATION.name(), 1);
index.put(FieldNames.DEVICE_ID.name()+"."+com.sap.sailing.shared.persistence.impl.FieldNames.DEVICE_TYPE.name(), 1);
index.put(FieldNames.DEVICE_ID.name()+"."+com.sap.sailing.shared.persistence.impl.FieldNames.DEVICE_TYPE_SPECIFIC_ID.name(), 1);
index.put(FieldNames.DEVICE_ID.name()+"."+com.sap.sailing.shared.persistence.impl.FieldNames.DEVICE_STRING_REPRESENTATION.name(), 1);
gpsFixCollection.createIndex(index, new IndexOptions().name("fixbytimeanddev"));
return gpsFixCollection;
}
@@ -995,7 +997,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogSuppressedMarkPassingsEvent(event));
return result;
}
public Document storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogORCLegDataEvent event) {
Document result = new Document();
storeRaceLogIdentifier(raceLogIdentifier, result);
@@ -1290,9 +1292,23 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
storeRaceLogEventProperties(courseDesignChangedEvent, result);
result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogCourseDesignChangedEvent.class.getSimpleName());
result.put(FieldNames.RACE_LOG_COURSE_DESIGN_NAME.name(), courseDesignChangedEvent.getCourseDesign().getName());
result.put(FieldNames.RACE_LOG_COURSE_ORIGINATING_TEMPLATE_ID.name(),
courseDesignChangedEvent.getCourseDesign().getOriginatingCourseTemplateIdOrNull());
result.put(FieldNames.RACE_LOG_COURSE_DESIGNER_MODE.name(),
courseDesignChangedEvent.getCourseDesignerMode() == null ? null : courseDesignChangedEvent.getCourseDesignerMode().name());
result.put(FieldNames.RACE_LOG_COURSE_DESIGN.name(), storeCourseBase(courseDesignChangedEvent.getCourseDesign()));
final BasicDBList roleMap = new BasicDBList();
courseDesignChangedEvent.getCourseDesign().getAssociatedRoles().forEach((mark, role) -> {
final Document mapping = new Document();
mapping.put(FieldNames.RACE_LOG_COURSE_ASSOCIATED_ROLES_MARK_ID.name(), mark.getId().toString());
mapping.put(FieldNames.RACE_LOG_COURSE_ASSOCIATED_ROLES_ROLE_ID.name(), role.toString());
roleMap.add(mapping);
});
if (!roleMap.isEmpty()) {
result.put(FieldNames.RACE_LOG_COURSE_ASSOCIATED_ROLES.name(), roleMap);
}
return result;
}
@@ -1301,6 +1317,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
storeRaceLogEventProperties(finishPositioningListChangedEvent, result);
result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogFinishPositioningListChangedEvent.class.getSimpleName());
result.put(FieldNames.RACE_LOG_POSITIONED_COMPETITORS.name(), storePositionedCompetitors(finishPositioningListChangedEvent.getPositionedCompetitorsIDsNamesMaxPointsReasons()));
return result;
}
@@ -1309,6 +1326,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
storeRaceLogEventProperties(finishPositioningConfirmedEvent, result);
result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogFinishPositioningConfirmedEvent.class.getSimpleName());
result.put(FieldNames.RACE_LOG_POSITIONED_COMPETITORS.name(), storePositionedCompetitors(finishPositioningConfirmedEvent.getPositionedCompetitorsIDsNamesMaxPointsReasons()));
return result;
}
@@ -1379,6 +1397,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
Document result = new Document();
result.put(FieldNames.CONTROLPOINTWITHTWOMARKS_ID.name(), cpwtm.getId());
result.put(FieldNames.CONTROLPOINTWITHTWOMARKS_NAME.name(), cpwtm.getName());
result.put(FieldNames.CONTROLPOINTWITHTWOMARKS_SHORT_NAME.name(), cpwtm.getShortName());
result.put(FieldNames.CONTROLPOINTWITHTWOMARKS_LEFT.name(), storeMark(cpwtm.getLeft()));
result.put(FieldNames.CONTROLPOINTWITHTWOMARKS_RIGHT.name(), storeMark(cpwtm.getRight()));
return result;
@@ -1389,22 +1408,15 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
result.put(FieldNames.MARK_ID.name(), mark.getId());
result.put(FieldNames.MARK_COLOR.name(), mark.getColor()==null?null:mark.getColor().getAsHtml());
result.put(FieldNames.MARK_NAME.name(), mark.getName());
result.put(FieldNames.MARK_SHORT_NAME.name(), mark.getShortName());
result.put(FieldNames.MARK_PATTERN.name(), mark.getPattern());
result.put(FieldNames.MARK_SHAPE.name(), mark.getShape());
result.put(FieldNames.MARK_TYPE.name(), mark.getType() == null ? null : mark.getType().name());
result.put(FieldNames.MARK_ORIGINATING_MARK_TEMPLATE_ID.name(), mark.getOriginatingMarkTemplateIdOrNull());
result.put(FieldNames.MARK_ORIGINATING_MARK_PROPERTIES_ID.name(), mark.getOriginatingMarkPropertiesIdOrNull());
return result;
}
private String getPassingInstructions(PassingInstruction passingInstructions) {
final String passing;
if (passingInstructions != null) {
passing = passingInstructions.name();
} else {
passing = null;
}
return passing;
}
@Override
public void storeCompetitor(Competitor competitor) {
if (competitor.hasBoat()) {
@@ -1555,20 +1567,6 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
Document query = new Document(FieldNames.CONFIGURATION_ID_AS_STRING.name(), id.toString());
configurationsCollections.deleteOne(query);
}
public static Document storeDeviceId(
TypeBasedServiceFinder<DeviceIdentifierMongoHandler> deviceIdentifierServiceFinder, DeviceIdentifier device)
throws TransformationException, NoCorrespondingServiceRegisteredException {
String type = device.getIdentifierType();
DeviceIdentifierMongoHandler handler = deviceIdentifierServiceFinder.findService(type);
com.sap.sse.common.Util.Pair<String, ? extends Object> pair = handler.serialize(device);
type = pair.getA();
Object deviceTypeSpecificId = pair.getB();
return new Document()
.append(FieldNames.DEVICE_TYPE.name(), type)
.append(FieldNames.DEVICE_TYPE_SPECIFIC_ID.name(), deviceTypeSpecificId)
.append(FieldNames.DEVICE_STRING_REPRESENTATION.name(), device.getStringRepresentation());
}
void storeRaceLogEventEvent(Document eventEntry) {
getRaceLogCollection().insertOne(eventEntry);
@@ -1886,4 +1884,5 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
document.append(FieldNames.ORC_CERTIFICATE.name(), createORCCertificateObject(event.getCertificate()));
storeRegattaLogEvent(regattaLikeIdentifier, document);
}
}
@@ -10,6 +10,7 @@ import com.sap.sailing.domain.persistence.MongoObjectFactory;
import com.sap.sailing.domain.persistence.impl.DomainObjectFactoryImpl;
import com.sap.sailing.domain.persistence.impl.MongoObjectFactoryImpl;
import com.sap.sailing.domain.persistence.racelog.tracking.FixMongoHandler;
import static com.sap.sailing.shared.persistence.impl.DomainObjectFactoryImpl.loadPosition;
import com.sap.sse.common.TimePoint;
public class GPSFixMongoHandlerImpl implements FixMongoHandler<GPSFix> {
@@ -25,7 +26,7 @@ public class GPSFixMongoHandlerImpl implements FixMongoHandler<GPSFix> {
public Document transformForth(GPSFix fix) throws IllegalArgumentException {
Document result = new Document();
mof.storeTimed(fix, result);
mof.storePositioned(fix, result);
mof.storePositioned(fix, result);
return result;
}
@@ -33,7 +34,7 @@ public class GPSFixMongoHandlerImpl implements FixMongoHandler<GPSFix> {
public GPSFix transformBack(Document object) {
Document dbObject = (Document) object;
TimePoint timePoint = dof.loadTimePoint(dbObject);
Position position = dof.loadPosition(dbObject);
Position position = loadPosition(dbObject);
return new GPSFixImpl(position, timePoint);
}
@@ -11,6 +11,7 @@ import com.sap.sailing.domain.persistence.MongoObjectFactory;
import com.sap.sailing.domain.persistence.impl.DomainObjectFactoryImpl;
import com.sap.sailing.domain.persistence.impl.MongoObjectFactoryImpl;
import com.sap.sailing.domain.persistence.racelog.tracking.FixMongoHandler;
import static com.sap.sailing.shared.persistence.impl.DomainObjectFactoryImpl.loadPosition;
import com.sap.sse.common.TimePoint;
public class GPSFixMovingMongoHandlerImpl implements FixMongoHandler<GPSFixMoving> {
@@ -34,7 +35,7 @@ public class GPSFixMovingMongoHandlerImpl implements FixMongoHandler<GPSFixMovin
@Override
public GPSFixMoving transformBack(Document dbObject) {
TimePoint timePoint = dof.loadTimePoint(dbObject);
Position position = dof.loadPosition(dbObject);
Position position = loadPosition(dbObject);
SpeedWithBearing speed = dof.loadSpeedWithBearing(dbObject);
return new GPSFixMovingImpl(position, timePoint, speed);
}
@@ -1,6 +1,6 @@
package com.sap.sailing.domain.persistence.racelog.tracking.impl;
import static com.sap.sailing.domain.persistence.impl.MongoObjectFactoryImpl.storeDeviceId;
import static com.sap.sailing.shared.persistence.impl.MongoObjectFactoryImpl.storeDeviceId;
import static com.sap.sailing.domain.persistence.impl.MongoObjectFactoryImpl.storeTimeRange;
import java.util.ArrayList;
@@ -31,10 +31,10 @@ import com.sap.sailing.domain.persistence.MongoObjectFactory;
import com.sap.sailing.domain.persistence.impl.DomainObjectFactoryImpl;
import com.sap.sailing.domain.persistence.impl.FieldNames;
import com.sap.sailing.domain.persistence.impl.MongoObjectFactoryImpl;
import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler;
import com.sap.sailing.domain.persistence.racelog.tracking.FixMongoHandler;
import com.sap.sailing.domain.persistence.racelog.tracking.MongoSensorFixStore;
import com.sap.sailing.domain.racelog.tracking.FixReceivedListener;
import com.sap.sailing.shared.persistence.device.DeviceIdentifierMongoHandler;
import com.sap.sse.common.Duration;
import com.sap.sse.common.NoCorrespondingServiceRegisteredException;
import com.sap.sse.common.TimePoint;
@@ -195,37 +195,28 @@ public class MongoSensorFixStoreImpl implements MongoSensorFixStore {
final Object dbDeviceId = storeDeviceId(deviceServiceFinder, device);
final int nrOfTotalFixes = Util.size(fixes);
final ArrayList<Document> dbFixes = new ArrayList<>(nrOfTotalFixes);
TimePoint newFrom = null;
TimePoint newTo = null;
final TimeRange oldTimeRange = getTimeRangeCoveredByFixes(device);
TimeRange newTimeRange = oldTimeRange;
FixT latestFix = null;
for (FixT fix : fixes) {
String type = fix.getClass().getName();
FixMongoHandler<FixT> mongoHandler = findService(type);
Object fixObject = mongoHandler.transformForth(fix);
Document entry = new Document().append(FieldNames.DEVICE_ID.name(), dbDeviceId)
.append(FieldNames.GPSFIX_TYPE.name(), type).append(FieldNames.GPSFIX.name(), fixObject);
if (latestFix == null || latestFix.getTimePoint().before(fix.getTimePoint())) {
latestFix = fix;
}
Document entry = new Document().append(FieldNames.DEVICE_ID.name(), dbDeviceId);
storeFixToDocument(entry, fix);
mongoOF.storeTimed(fix, entry);
dbFixes.add(entry);
TimePoint fixTP = fix.getTimePoint();
if (newFrom == null || newFrom.after(fixTP)) {
newFrom = fixTP;
}
if (newTo == null || newTo.before(fixTP)) {
newTo = fixTP;
}
final TimeRangeImpl fixTimeRange = new TimeRangeImpl(fixTP, fixTP, /* toIsInclusive */ true);
newTimeRange = newTimeRange == null ? fixTimeRange : newTimeRange.extend(fixTimeRange);
}
fixesCollection.withWriteConcern(WriteConcern.UNACKNOWLEDGED).insertMany(dbFixes);
final Document updateOperation = new Document();
final Document newMetadata = new Document();
newMetadata.put(FieldNames.DEVICE_ID.name(), dbDeviceId);
TimeRange oldTimeRange = getTimeRangeCoveredByFixes(device);
if (oldTimeRange != null) {
newFrom = oldTimeRange.from().before(newFrom) ? oldTimeRange.from() : newFrom;
newTo = oldTimeRange.to().after(newTo) ? oldTimeRange.to() : newTo;
if (latestFix != null) {
newMetadata.put(FieldNames.LAST_FIX_RECEIVED.name(), storeFixToDocument(new Document(), latestFix));
}
final TimeRange newTimeRange = new TimeRangeImpl(newFrom, newTo);
storeTimeRange(newTimeRange, newMetadata, FieldNames.TIMERANGE);
updateOperation.append("$set", newMetadata);
updateOperation.append("$inc", new Document(FieldNames.NUM_FIXES.name(), nrOfTotalFixes));
@@ -239,6 +230,20 @@ public class MongoSensorFixStoreImpl implements MongoSensorFixStore {
return maneuverChanged;
}
/**
* Writes the {@code fix} to the {@code entry} document such that {@link #loadFix(Document)} can re-establish
* the fix from that document.
*
* @return for convenience and call chaining, the updated {@code entry} is returned
*/
private <FixT extends Timed> Document storeFixToDocument(Document entry, FixT fix) throws TransformationException {
String type = fix.getClass().getName();
FixMongoHandler<FixT> mongoHandler = findService(type);
Object fixObject = mongoHandler.transformForth(fix);
entry.append(FieldNames.GPSFIX_TYPE.name(), type).append(FieldNames.GPSFIX.name(), fixObject);
return entry;
}
@Override
public <FixT extends Timed> void storeFix(DeviceIdentifier device, FixT fix) {
storeFixes(device, Collections.singletonList(fix));
@@ -294,21 +299,27 @@ public class MongoSensorFixStoreImpl implements MongoSensorFixStore {
@Override
public TimeRange getTimeRangeCoveredByFixes(DeviceIdentifier device)
throws TransformationException, NoCorrespondingServiceRegisteredException {
Document result = findMetadataObject(device);
if (result == null) {
return null;
final Document resultDocument = findMetadataObject(device);
final TimeRange result;
if (resultDocument == null) {
result = null;
} else {
result = DomainObjectFactoryImpl.loadTimeRange(resultDocument, FieldNames.TIMERANGE);
}
return DomainObjectFactoryImpl.loadTimeRange(result, FieldNames.TIMERANGE);
return result;
}
@Override
public long getNumberOfFixes(DeviceIdentifier device)
throws TransformationException, NoCorrespondingServiceRegisteredException {
Document result = findMetadataObject(device);
if (result == null) {
return 0;
final Document resultDocument = findMetadataObject(device);
final long result;
if (resultDocument == null) {
result = 0;
} else {
result = ((Number) resultDocument.get(FieldNames.NUM_FIXES.name())).longValue();
}
return ((Number) result.get(FieldNames.NUM_FIXES.name())).longValue();
return result;
}
/**
@@ -331,20 +342,19 @@ public class MongoSensorFixStoreImpl implements MongoSensorFixStore {
}
@Override
public <FixT extends Timed> Map<DeviceIdentifier, FixT> getLastFix(Iterable<DeviceIdentifier> forDevices)
public <FixT extends Timed> Map<DeviceIdentifier, FixT> getFixLastReceived(Iterable<DeviceIdentifier> forDevices)
throws TransformationException, NoCorrespondingServiceRegisteredException {
Map<DeviceIdentifier, FixT> result = new HashMap<>();
for (final DeviceIdentifier deviceIdentifier : forDevices) {
final Bson deviceQuery = getDeviceQuery(deviceIdentifier);
final Document orderBy = new Document(
FieldNames.GPSFIX.name() + "." + FieldNames.TIME_AS_MILLIS.name(), -1);
FindIterable<Document> lastFixForDeviceCursor = fixesCollection.find(deviceQuery).sort(orderBy).limit(1);
if (lastFixForDeviceCursor.iterator().hasNext()) {
final Document lastFixForDeviceDbObject = lastFixForDeviceCursor.iterator().next();
final Timed lastFixForDevice = loadFix(lastFixForDeviceDbObject);
@SuppressWarnings("unchecked")
final FixT lastFixForDeviceTyped = (FixT) lastFixForDevice;
result.put(deviceIdentifier, lastFixForDeviceTyped);
final Document metadataDoc = findMetadataObject(deviceIdentifier);
if (metadataDoc != null) {
final Document lastFixForDeviceDbObject = (Document) metadataDoc.get(FieldNames.LAST_FIX_RECEIVED.name());
if (lastFixForDeviceDbObject != null) {
final Timed lastFixForDevice = loadFix(lastFixForDeviceDbObject);
@SuppressWarnings("unchecked")
final FixT lastFixForDeviceTyped = (FixT) lastFixForDevice;
result.put(deviceIdentifier, lastFixForDeviceTyped);
}
}
}
return result;
@@ -15,7 +15,7 @@ import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceMa
import com.sap.sailing.domain.abstractlog.regatta.impl.RegattaLogImpl;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.impl.MarkImpl;
import com.sap.sailing.domain.racelogtracking.PingDeviceIdentifierImpl;
import com.sap.sailing.domain.racelogtracking.impl.PingDeviceIdentifierImpl;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import org.junit.Assert;
@@ -30,6 +30,7 @@ import com.sap.sailing.domain.base.impl.CourseImpl;
import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
import com.sap.sailing.domain.base.impl.WaypointImpl;
import com.sap.sailing.domain.common.DeviceIdentifier;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.racelog.tracking.TransformationException;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
@@ -543,7 +544,8 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
store.storeFix(device, createFix(100, 10, 20, 30, 40));
store.storeFix(device, createFix(200, 10, 20, 30, 40));
assertEquals(2, store.getNumberOfFixes(device));
assertEquals(TimeRangeImpl.create(100, 200), store.getTimeRangeCoveredByFixes(device));
assertEquals(TimeRangeImpl.create(100, 200, /* toIsInclusive */ true), store.getTimeRangeCoveredByFixes(device));
assertEquals(new DegreePosition(10, 20), ((GPSFix) store.getFixLastReceived(Collections.singleton(device)).get(device)).getPosition());
}
@Test
@@ -551,28 +553,28 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest {
store.storeFix(device, createFix(100, 10, 20, 30, 40));
store.storeFix(device, createFix(1100, 10, 20, 30, 40));
store.storeFix(device, createFix(2100, 10, 20, 30, 40));
final Map<DeviceIdentifier, Timed> lastFixes = store.getLastFix(Collections.singleton(device));
final Map<DeviceIdentifier, Timed> lastFixes = store.getFixLastReceived(Collections.singleton(device));
assertEquals(1, lastFixes.size());
Timed lastFix = lastFixes.get(device);
assertEquals(2100, lastFix.getTimePoint().asMillis());
store.storeFix(device, createFix(2000, 10, 20, 30, 40));
final Map<DeviceIdentifier, Timed> lastFixes2 = store.getLastFix(Collections.singleton(device));
final Map<DeviceIdentifier, Timed> lastFixes2 = store.getFixLastReceived(Collections.singleton(device));
assertEquals(1, lastFixes2.size());
Timed lastFix2 = lastFixes2.get(device);
assertEquals(2100, lastFix2.getTimePoint().asMillis());
assertEquals(2000, lastFix2.getTimePoint().asMillis()); // not the fix with the latest time point but the fix that was last received by the store
store.storeFix(device, createFix(2200, 10, 20, 30, 40));
final Map<DeviceIdentifier, Timed> lastFixes3 = store.getLastFix(Collections.singleton(device));
final Map<DeviceIdentifier, Timed> lastFixes3 = store.getFixLastReceived(Collections.singleton(device));
assertEquals(1, lastFixes3.size());
Timed lastFix3 = lastFixes3.get(device);
assertEquals(2200, lastFix3.getTimePoint().asMillis());
final DeviceIdentifier device2 = new SmartphoneImeiIdentifier("b");
store.storeFix(device2, createFix(1200, 10, 20, 30, 40));
store.storeFix(device2, createFix(1100, 10, 20, 30, 40));
final Map<DeviceIdentifier, Timed> lastFixes4 = store.getLastFix(Arrays.asList(device, device2));
final Map<DeviceIdentifier, Timed> lastFixes4 = store.getFixLastReceived(Arrays.asList(device, device2));
assertEquals(2, lastFixes4.size());
Timed lastFix4 = lastFixes4.get(device);
assertEquals(2200, lastFix4.getTimePoint().asMillis());
Timed lastFixDevice2 = lastFixes4.get(device2);
assertEquals(1200, lastFixDevice2.getTimePoint().asMillis());
assertEquals(1100, lastFixDevice2.getTimePoint().asMillis()); // not the fix with the latest time point but the fix that was last received by the store
}
}
@@ -20,3 +20,4 @@ Require-Bundle: com.sap.sailing.domain.shared.android,
org.mongodb.mongo-java-driver;bundle-version="3.6.4"
Export-Package: com.sap.sailing.domain.racelog.tracking.test.mock
Automatic-Module-Name: com.sap.sailing.domain.racelogtrackingadapter.testsupport
Import-Package: com.sap.sailing.shared.persistence.device
@@ -3,10 +3,10 @@ package com.sap.sailing.domain.racelog.tracking.test.mock;
import java.util.HashMap;
import java.util.Map;
import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler;
import com.sap.sailing.domain.persistence.racelog.tracking.FixMongoHandler;
import com.sap.sailing.server.gateway.serialization.racelog.tracking.DeviceIdentifierJsonHandler;
import com.sap.sailing.server.gateway.serialization.racelog.tracking.GPSFixJsonHandler;
import com.sap.sailing.shared.persistence.device.DeviceIdentifierMongoHandler;
import com.sap.sse.common.TypeBasedServiceFinder;
import com.sap.sse.common.TypeBasedServiceFinderFactory;
@@ -3,12 +3,12 @@ package com.sap.sailing.domain.racelog.tracking.test.mock;
import java.util.HashMap;
import java.util.Map;
import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler;
import com.sap.sailing.domain.persistence.racelog.tracking.FixMongoHandler;
import com.sap.sailing.domain.racelogtracking.impl.SmartphoneUUIDMongoHandler;
import com.sap.sailing.server.gateway.serialization.racelog.tracking.DeviceIdentifierJsonHandler;
import com.sap.sailing.server.gateway.serialization.racelog.tracking.GPSFixJsonHandler;
import com.sap.sailing.server.gateway.serialization.racelog.tracking.impl.SmartphoneUUIDJsonHandler;
import com.sap.sailing.shared.persistence.device.DeviceIdentifierMongoHandler;
import com.sap.sse.common.TypeBasedServiceFinder;
import com.sap.sse.common.TypeBasedServiceFinderFactory;
@@ -2,7 +2,7 @@ package com.sap.sailing.domain.racelog.tracking.test.mock;
import com.sap.sailing.domain.common.DeviceIdentifier;
import com.sap.sailing.domain.common.racelog.tracking.TransformationException;
import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler;
import com.sap.sailing.shared.persistence.device.DeviceIdentifierMongoHandler;
public class SmartphoneImeiMongoHandler extends SmartphoneImeiSerializationHandler
implements DeviceIdentifierMongoHandler {
@@ -19,7 +19,8 @@ Require-Bundle: com.sap.sailing.domain,
com.sap.sse,
com.sap.sailing.server.gateway.serialization.shared.android,
com.sap.sse.mail,
org.mongodb.mongo-java-driver;bundle-version="3.6.4"
org.mongodb.mongo-java-driver;bundle-version="3.6.4",
com.sap.sailing.shared.persistence;bundle-version="1.0.0"
Import-Package: com.google.zxing;version="3.1.0",
com.mongodb,
com.sap.sse.concurrent,
@@ -13,11 +13,9 @@ import org.osgi.framework.ServiceRegistration;
import org.osgi.util.tracker.ServiceTracker;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler;
import com.sap.sailing.domain.racelog.tracking.SensorFixMapper;
import com.sap.sailing.domain.racelogsensortracking.impl.SensorFixMapperFactoryImpl;
import com.sap.sailing.domain.racelogtracking.DeviceIdentifierStringSerializationHandler;
import com.sap.sailing.domain.racelogtracking.PingDeviceIdentifierImpl;
import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapterFactory;
import com.sap.sailing.domain.racelogtracking.SmartphoneUUIDIdentifier;
import com.sap.sailing.domain.racelogtracking.impl.fixtracker.RegattaLogFixTrackerRegattaListener;
@@ -35,6 +33,7 @@ import com.sap.sailing.server.gateway.serialization.racelog.tracking.GPSFixJsonH
import com.sap.sailing.server.gateway.serialization.racelog.tracking.impl.GPSFixJsonHandlerImpl;
import com.sap.sailing.server.gateway.serialization.racelog.tracking.impl.SmartphoneUUIDJsonHandler;
import com.sap.sailing.server.interfaces.RacingEventService;
import com.sap.sailing.shared.persistence.device.DeviceIdentifierMongoHandler;
import com.sap.sse.MasterDataImportClassLoaderService;
import com.sap.sse.common.TypeBasedServiceFinder;
import com.sap.sse.replication.Replicable;
@@ -2,7 +2,7 @@ package com.sap.sailing.domain.racelogtracking.impl;
import com.sap.sailing.domain.common.DeviceIdentifier;
import com.sap.sailing.domain.common.racelog.tracking.TransformationException;
import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler;
import com.sap.sailing.shared.persistence.device.DeviceIdentifierMongoHandler;
public class PingDeviceIdentifierMongoHandler extends PingDeviceIdentifierSerializationHandler
implements DeviceIdentifierMongoHandler {
@@ -5,7 +5,6 @@ import java.util.UUID;
import com.sap.sailing.domain.common.DeviceIdentifier;
import com.sap.sailing.domain.common.racelog.tracking.TransformationException;
import com.sap.sailing.domain.racelogtracking.PingDeviceIdentifier;
import com.sap.sailing.domain.racelogtracking.PingDeviceIdentifierImpl;
import com.sap.sse.common.Util;
public class PingDeviceIdentifierSerializationHandler {
@@ -262,7 +262,8 @@ public class RaceLogRaceTracker extends AbstractRaceTrackerBaseImpl {
}
try {
final Course course = trackedRace.getRace().getCourse();
course.update(update, params.getDomainFactory());
course.update(update, event.getCourseDesign().getAssociatedRoles(),
event.getCourseDesign().getOriginatingCourseTemplateIdOrNull(), params.getDomainFactory());
if (base.getName() != null) {
course.setName(base.getName());
}
@@ -290,7 +291,8 @@ public class RaceLogRaceTracker extends AbstractRaceTrackerBaseImpl {
courseBase = new CourseDataImpl("Default course for " + raceName);
logger.log(Level.FINE, "Using empty course in creation of race " + raceName);
}
final Course course = new CourseImpl(courseBase.getName() == null ? raceName + " course" : courseBase.getName(), courseBase.getWaypoints());
final Course course = new CourseImpl(courseBase.getName() == null ? raceName + " course" : courseBase.getName(),
courseBase.getWaypoints(), courseBase.getOriginatingCourseTemplateIdOrNull());
if (raceColumn.getTrackedRace(fleet) != null) {
if (event != null) {
try {
@@ -54,7 +54,6 @@ import com.sap.sailing.domain.common.racelog.tracking.RaceLogTrackingState;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
import com.sap.sailing.domain.racelogtracking.PingDeviceIdentifierImpl;
import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapter;
import com.sap.sailing.domain.tracking.RaceHandle;
import com.sap.sailing.domain.tracking.RaceTrackingHandler;
@@ -203,11 +202,10 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
if (course != null) {
course.getWaypoints().forEach(wp -> Util.addAll(wp.getMarks(), marks));
}
for (RaceLog toRaceLog : toRaceLogs) {
if (new RaceLogTrackingStateAnalyzer(toRaceLog).analyze().isForTracking()) {
if (course != null) {
CourseBase newCourse = new CourseDataImpl(course.getName());
CourseBase newCourse = new CourseDataImpl(course.getName(), course.getOriginatingCourseTemplateIdOrNull());
TimePoint now = MillisecondsTimePoint.now();
int i = 0;
for (Waypoint oldWaypoint : course.getWaypoints()) {
@@ -220,7 +218,6 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
passId, newCourse, CourseDesignerMode.ADMIN_CONSOLE);
toRaceLog.add(newCourseEvent);
}
}
}
}
@@ -2,8 +2,8 @@ package com.sap.sailing.domain.racelogtracking.impl;
import com.sap.sailing.domain.common.DeviceIdentifier;
import com.sap.sailing.domain.common.racelog.tracking.TransformationException;
import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler;
import com.sap.sailing.domain.racelogtracking.SmartphoneUUIDSerializationHandler;
import com.sap.sailing.shared.persistence.device.DeviceIdentifierMongoHandler;
public class SmartphoneUUIDMongoHandler extends SmartphoneUUIDSerializationHandler
implements DeviceIdentifierMongoHandler {
@@ -6,9 +6,9 @@ import org.bson.Document;
import com.sap.sailing.domain.common.DeviceIdentifier;
import com.sap.sailing.domain.common.racelog.tracking.TransformationException;
import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler;
import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier;
import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifierImpl;
import com.sap.sailing.shared.persistence.device.DeviceIdentifierMongoHandler;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
@@ -0,0 +1,154 @@
package com.sap.sailing.domain.coursetemplate.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeNoException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.common.MarkType;
import com.sap.sailing.domain.common.PassingInstruction;
import com.sap.sailing.domain.coursetemplate.ControlPointTemplate;
import com.sap.sailing.domain.coursetemplate.CourseTemplate;
import com.sap.sailing.domain.coursetemplate.MarkRole;
import com.sap.sailing.domain.coursetemplate.MarkTemplate;
import com.sap.sailing.domain.coursetemplate.WaypointTemplate;
import com.sap.sailing.domain.coursetemplate.impl.CourseTemplateImpl;
import com.sap.sailing.domain.coursetemplate.impl.MarkRoleImpl;
import com.sap.sailing.domain.coursetemplate.impl.MarkRolePairImpl;
import com.sap.sailing.domain.coursetemplate.impl.MarkTemplateImpl;
import com.sap.sailing.domain.coursetemplate.impl.RepeatablePartImpl;
import com.sap.sailing.domain.coursetemplate.impl.WaypointTemplateImpl;
import com.sap.sse.common.Util;
public class TestLapConfiguration {
private CourseTemplate courseTemplate;
private MarkTemplate startBoat;
private MarkRole startBoatRole;
private MarkTemplate pin;
private MarkRole pinRole;
private MarkTemplate top;
private MarkRole one;
private MarkTemplate gateLeft;
private MarkRole threeP;
private MarkTemplate gateRight;
private MarkRole threeS;
private ControlPointTemplate startFinish;
private ControlPointTemplate gate;
private Map<MarkRole, MarkTemplate> defaultMarkTemplatesForMarkRoles;
@Before
public void setUp() {
startBoat = new MarkTemplateImpl("Start Boat", "SB", /* color */ null, /* shape */ null, /* pattern */ null, MarkType.STARTBOAT);
startBoatRole = new MarkRoleImpl(UUID.randomUUID(), "Start Boat", "SB");
pin = new MarkTemplateImpl("Pin End", "Pin", /* color */ null, /* shape */ null, /* pattern */ null, MarkType.BUOY);
pinRole = new MarkRoleImpl(UUID.randomUUID(), "Pin End", "P");
top = new MarkTemplateImpl("Windward Mark", "1", /* color */ null, /* shape */ null, /* pattern */ null, MarkType.BUOY);
one = new MarkRoleImpl(UUID.randomUUID(), "Windward Mark", "1");
gateLeft = new MarkTemplateImpl("Leeward Gate Port", "4p", /* color */ null, /* shape */ null, /* pattern */ null, MarkType.BUOY);
threeP = new MarkRoleImpl(UUID.randomUUID(), "Leeward Gate Port", "3p");
gateRight = new MarkTemplateImpl("Leeward Gate Starboard", "4s", /* color */ null, /* shape */ null, /* pattern */ null, MarkType.BUOY);
threeS = new MarkRoleImpl(UUID.randomUUID(), "Leeward Gate Starboard", "3s");
startFinish = new MarkRolePairImpl("Start/Finish", "S/F", startBoatRole, pinRole);
gate = new MarkRolePairImpl("Leeward Gate", "LG", threeP, threeS);
defaultMarkTemplatesForMarkRoles = new HashMap<>();
defaultMarkTemplatesForMarkRoles.put(startBoatRole, startBoat);
defaultMarkTemplatesForMarkRoles.put(pinRole, pin);
defaultMarkTemplatesForMarkRoles.put(one, top);
defaultMarkTemplatesForMarkRoles.put(threeP, gateLeft);
defaultMarkTemplatesForMarkRoles.put(threeS, gateRight);
courseTemplate = new CourseTemplateImpl(UUID.randomUUID(), "Windward/Leeward", "L",
/* marks */ Arrays.asList(startBoat, pin, top, gateLeft, gateRight),
/* waypoints */ Arrays.asList(new WaypointTemplateImpl(startFinish, PassingInstruction.Line),
new WaypointTemplateImpl(one, PassingInstruction.Port),
new WaypointTemplateImpl(gate, PassingInstruction.Gate),
new WaypointTemplateImpl(one, PassingInstruction.Port),
new WaypointTemplateImpl(startFinish, PassingInstruction.Line)),
defaultMarkTemplatesForMarkRoles,
/* associatedRoles */ Collections.emptyMap(),
/* optionaImageURL */ null, new RepeatablePartImpl(/* zeroBasedIndexOfRepeatablePartStart */ 1,
/* zeroBasedIndexOfRepeatablePartEnd */ 3), /* Default Number of Laps */ 1);
}
@Test
public void testSimpleCourseWithOneLap() {
final Iterable<WaypointTemplate> waypointsOfOneLapper = courseTemplate.getWaypointTemplates(1);
final List<ControlPointTemplate> expected = Arrays.asList(startFinish, one, startFinish);
verify(waypointsOfOneLapper, expected);
}
private void verify(final Iterable<WaypointTemplate> waypointsOfOneLapper,
final List<ControlPointTemplate> expected) {
assertEquals(expected.size(), Util.size(waypointsOfOneLapper));
final List<ControlPointTemplate> controlPoints = new ArrayList<>();
for (final WaypointTemplate waypoint : waypointsOfOneLapper) {
controlPoints.add(waypoint.getControlPointTemplate());
}
assertEquals(expected, controlPoints);
}
@Test
public void testSimpleCourseWithTwoLaps() {
final Iterable<WaypointTemplate> waypointsOfOneLapper = courseTemplate.getWaypointTemplates(2);
final List<ControlPointTemplate> expected = Arrays.asList(startFinish, one, gate, one, startFinish);
verify(waypointsOfOneLapper, expected);
}
@Test
public void testSimpleCourseWithThreeLaps() {
final Iterable<WaypointTemplate> waypointsOfOneLapper = courseTemplate.getWaypointTemplates(3);
final List<ControlPointTemplate> expected = Arrays.asList(startFinish, one, gate, one, gate, one, startFinish);
verify(waypointsOfOneLapper, expected);
}
@Test
public void testExceptionInCaseMarkIsMissing() {
startBoat = new MarkTemplateImpl("Start Boat", "SB", /* color */ null, /* shape */ null, /* pattern */ null, MarkType.STARTBOAT);
pin = new MarkTemplateImpl("Pin End", "Pin", /* color */ null, /* shape */ null, /* pattern */ null, MarkType.BUOY);
startFinish = new MarkRolePairImpl("Start/Finish","Start/Finish", startBoatRole, pinRole);
try {
courseTemplate = new CourseTemplateImpl("Test", "T",
/* marks */ Arrays.asList(startBoat),
/* waypoints */ Arrays.asList(new WaypointTemplateImpl(startFinish, PassingInstruction.Line)),
/* defaultMarkRolesForMarkTemplates */ Collections.emptyMap(),
/* defaultMarkTemplatesForRoles */ Collections.emptyMap(), /* optionaImageURL */ null);
fail("Expected an IllegalArgumentException due to missing mark <pin> but it wasn't thrown");
} catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void testZeroLapsOkIfNoRepeatablePart() {
courseTemplate = new CourseTemplateImpl("Test", "T",
/* marks */ Arrays.asList(startBoat, pin),
/* waypoints */ Arrays.asList(new WaypointTemplateImpl(startFinish, PassingInstruction.Line)),
defaultMarkTemplatesForMarkRoles,
/* defaultMarkTemplatesForRoles */ Collections.emptyMap(), /* optionaImageURL */ null);
try {
courseTemplate.getWaypointTemplates(0);
} catch (IllegalArgumentException e) {
assumeNoException("No IllegalArgumentException should have been thrown for zero laps because the course has no repeatable part", e);
}
}
@Test
public void testIllegalArgumentExceptionForZeroLaps() {
try {
courseTemplate
.getWaypointTemplates(/* illegal to request 0 laps if course template defines repeatable part */ 0);
fail("Expected an IllegalArgumentException but none was thrown");
} catch (IllegalArgumentException e) {
// expected
}
}
}
@@ -50,6 +50,8 @@ Export-Package: com.sap.sailing.domain.abstractlog,
com.sap.sailing.domain.base.impl,
com.sap.sailing.domain.base.racegroup,
com.sap.sailing.domain.base.racegroup.impl,
com.sap.sailing.domain.coursetemplate,
com.sap.sailing.domain.coursetemplate.impl;
com.sap.sailing.domain.racelogtracking,
com.sap.sailing.domain.racelogtracking.impl,
com.sap.sailing.domain.statistics,
@@ -4,4 +4,6 @@ import com.sap.sse.common.NamedWithID;
public interface ControlPoint extends NamedWithID {
Iterable<Mark> getMarks();
String getShortName();
}
@@ -1,7 +1,10 @@
package com.sap.sailing.domain.base;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import com.sap.sailing.domain.coursetemplate.MarkRole;
import com.sap.sse.common.Named;
/**
@@ -47,8 +50,23 @@ public interface CourseBase extends Named {
void removeWaypoint(int zeroBasedPosition);
Leg getFirstLeg();
/**
* @return the id of the course template that was used to create this course. This reference can remain in place
* even if the actual course is being manipulated in ways incompatible with the original course template.
*/
UUID getOriginatingCourseTemplateIdOrNull();
/**
* @return an unmodifiable map with the IDs of the associated roles required for checking course template validity
* and when creating a new course template from this course. See also {@link MarkRole}.
*/
Map<Mark, UUID> getAssociatedRoles();
void addRoleMapping(Mark mark, UUID role);
default String internalToString() {
StringBuilder result = new StringBuilder(getName());
StringBuilder result = new StringBuilder(getName()==null?"":getName());
result.append(": ");
boolean first = true;
for (Waypoint waypoint : getWaypoints()) {
@@ -1,7 +1,8 @@
package com.sap.sailing.domain.base;
import com.sap.sailing.domain.common.MarkType;
import com.sap.sse.common.Color;
import java.util.UUID;
import com.sap.sailing.domain.coursetemplate.CommonMarkProperties;
import com.sap.sse.common.IsManagedByCache;
@@ -12,9 +13,8 @@ import com.sap.sse.common.IsManagedByCache;
* @author Axel Uhl (D043530)
*
*/
public interface Mark extends ControlPoint, IsManagedByCache<SharedDomainFactory<?>> {
public Color getColor();
public String getShape();
public String getPattern();
public MarkType getType();
public interface Mark extends CommonMarkProperties, ControlPoint, IsManagedByCache<SharedDomainFactory<?>> {
UUID getOriginatingMarkTemplateIdOrNull();
UUID getOriginatingMarkPropertiesIdOrNull();
}
@@ -17,7 +17,7 @@ public interface SharedDomainFactory<RLR extends RaceLogResolver> extends Compet
Nationality getOrCreateNationality(String threeLetterIOCCode);
/**
* The name will also be used as the mark's ID. If you have a unique ID, use {@link #getOrCreateMark(Serializable, String)} instead.
* The name will also be used as the mark's ID. If you have a unique ID, use {@link #getOrCreateMark(Serializable, String, String)} instead.
*/
Mark getOrCreateMark(String name);
@@ -30,32 +30,39 @@ public interface SharedDomainFactory<RLR extends RaceLogResolver> extends Compet
* <p>
*
* This method first looks up the actual ID whose string representation is <code>toStringRepresentationOfID</code>
* and then calls {@link #getOrCreateMark(Serializable, String)} with the result and the <code>name</code>
* and then calls {@link #getOrCreateMark(Serializable, String, String)} with the result and the <code>name</code>
* parameter, or with <code>ToStringRepresentationOfID</code> and <code>name</code> in case the string
* representation of the ID is not known. So in the latter case, the string is used as the ID for the new mark.
* @param shortName TODO
*/
Mark getOrCreateMark(String toStringRepresentationOfID, String name);
Mark getOrCreateMark(String toStringRepresentationOfID, String name, String shortName);
Mark getOrCreateMark(Serializable id, String name);
Mark getOrCreateMark(Serializable id, String name, String shortName);
Mark getOrCreateMark(Serializable id, String name, String shortName, UUID originatingMarkTemplateId, UUID originatingMarkPropertiesId);
/**
* If the single mark with ID <code>id</code> already exists, it is returned. Its color may differ from <code>color</code>
* in that case. Otherwise, a new {@link Mark} is created with <code>color</code> as its {@link Mark#getColor()}
* and <code>shape</code> as its {@link Mark#getShape()}.
*/
Mark getOrCreateMark(Serializable id, String name, MarkType type, Color color, String shape, String pattern);
Mark getOrCreateMark(Serializable id, String name, String shortName, MarkType type, Color color, String shape, String pattern);
Mark getOrCreateMark(Serializable id, String name, String shortName, MarkType type, Color color, String shape,
String pattern, UUID originatingMarkTemplateId, UUID originatingMarkPropertiesId);
/**
* @see #getOrCreateMark(String, String)
* @param shortName TODO
* @see #getOrCreateMark(String, String, String)
*/
Mark getOrCreateMark(String toStringRepresentationOfID, String name, MarkType type, Color color, String shape, String pattern);
Mark getOrCreateMark(String toStringRepresentationOfID, String name, String shortName, MarkType type, Color color, String shape, String pattern);
/**
* @param name also uses the name as the gate's ID; if you have a real ID, use {@link #createControlPointWithTwoMarks(Serializable, Mark, Mark, String)} instead
*/
ControlPointWithTwoMarks createControlPointWithTwoMarks(Mark left, Mark right, String name);
ControlPointWithTwoMarks createControlPointWithTwoMarks(Mark left, Mark right, String name, String shortName);
ControlPointWithTwoMarks createControlPointWithTwoMarks(Serializable id, Mark left, Mark right, String name);
ControlPointWithTwoMarks createControlPointWithTwoMarks(Serializable id, Mark left, Mark right, String name,
String shortName);
/**
* The waypoint created is weakly cached so that when requested again by
@@ -103,9 +110,11 @@ public interface SharedDomainFactory<RLR extends RaceLogResolver> extends Compet
Mark getExistingMarkById(Serializable id);
ControlPointWithTwoMarks getOrCreateControlPointWithTwoMarks(Serializable id, String name, Mark left, Mark right);
ControlPointWithTwoMarks getOrCreateControlPointWithTwoMarks(Serializable id, String name, Mark left, Mark right,
String shortName);
ControlPointWithTwoMarks getOrCreateControlPointWithTwoMarks(String id, String name, Mark left, Mark right);
ControlPointWithTwoMarks getOrCreateControlPointWithTwoMarks(String id, String name, Mark left, Mark right,
String shortName);
RLR getRaceLogResolver();
@@ -79,11 +79,11 @@ public class DeviceConfigurationImpl extends NamedImpl implements DeviceConfigur
@Override
public QualifiedObjectIdentifier getIdentifier() {
return getType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
return getPermissionType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
}
@Override
public HasPermissions getType() {
public HasPermissions getPermissionType() {
return SecuredDomainType.RACE_MANAGER_APP_DEVICE_CONFIGURATION;
}
@@ -129,11 +129,11 @@ public class BoatImpl extends RenamableImpl implements DynamicBoat {
@Override
public QualifiedObjectIdentifier getIdentifier() {
return getType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
return getPermissionType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
}
@Override
public HasPermissions getType() {
public HasPermissions getPermissionType() {
return SecuredDomainType.BOAT;
}
@@ -257,11 +257,11 @@ public class CompetitorImpl implements DynamicCompetitor {
@Override
public QualifiedObjectIdentifier getIdentifier() {
return getType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
return getPermissionType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
}
@Override
public HasPermissions getType() {
public HasPermissions getPermissionType() {
return SecuredDomainType.COMPETITOR;
}
@@ -8,24 +8,39 @@ import com.sap.sailing.domain.base.ControlPointWithTwoMarks;
import com.sap.sailing.domain.base.Mark;
public class ControlPointWithTwoMarksImpl implements ControlPointWithTwoMarks {
private static final long serialVersionUID = 2807354812133070574L;
private static final long serialVersionUID = -6407078290366911597L;
private final Mark left;
private final Mark right;
private final String name;
private final Serializable id;
private final String shortName;
/**
* @param name also used as ID for the mark; if you have a better ID, use {@link GateImpl(Serializable, Mark, Mark, String)} instead.
*/
/** Use {@link #ControlPointWithTwoMarksImpl(Mark, Mark, String, String)} instead. */
@Deprecated
public ControlPointWithTwoMarksImpl(Mark left, Mark right, String name) {
this(/* ID */ name, left, right, name);
this(/* ID */ name, left, right, name, name);
}
/** Use {@link #ControlPointWithTwoMarksImpl(Serializable, Mark, Mark, String, String)} instead. */
@Deprecated
public ControlPointWithTwoMarksImpl(Serializable id, Mark left, Mark right, String name) {
this(id, left, right, name, name);
}
public ControlPointWithTwoMarksImpl(Mark left, Mark right, String name, String shortName) {
this(/* ID */ name, left, right, name, shortName);
}
public ControlPointWithTwoMarksImpl(Serializable id, Mark left, Mark right, String name) {
public ControlPointWithTwoMarksImpl(Serializable id, Mark left, Mark right, String name, String shortName) {
this.id = id;
this.left = left;
this.right = right;
this.name = name;
this.shortName = shortName;
}
@Override
@@ -60,4 +75,8 @@ public class ControlPointWithTwoMarksImpl implements ControlPointWithTwoMarks {
public Serializable getId() {
return id;
}
public String getShortName() {
return shortName;
}
}
@@ -1,23 +1,36 @@
package com.sap.sailing.domain.base.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import com.sap.sailing.domain.base.ControlPoint;
import com.sap.sailing.domain.base.CourseBase;
import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sse.common.impl.NamedImpl;
public class CourseDataImpl extends NamedImpl implements CourseBase {
private static final long serialVersionUID = 1389216430392253253L;
private static final long serialVersionUID = 2749443048689453078L;
private final List<Waypoint> waypoints;
private final UUID originatingCourseTemplateId;
private final Map<Mark, UUID> associatedRoles = new HashMap<>();
public CourseDataImpl(String name) {
this(name, null);
}
public CourseDataImpl(String name, UUID originatingCourseTemplateId) {
super(name);
this.waypoints = new ArrayList<Waypoint>();
this.originatingCourseTemplateId = originatingCourseTemplateId;
}
@Override
@@ -73,6 +86,19 @@ public class CourseDataImpl extends NamedImpl implements CourseBase {
return null;
}
@Override
public UUID getOriginatingCourseTemplateIdOrNull() {
return originatingCourseTemplateId;
}
public void addRoleMapping(Mark mark, UUID role) {
associatedRoles.put(mark, role);
}
public Map<Mark, UUID> getAssociatedRoles() {
return associatedRoles;
}
@Override
public String toString() {
return internalToString();
@@ -384,11 +384,11 @@ public abstract class EventBaseImpl implements EventBase {
@Override
public QualifiedObjectIdentifier getIdentifier() {
return getType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
return getPermissionType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
}
@Override
public HasPermissions getType() {
public HasPermissions getPermissionType() {
return SecuredDomainType.EVENT;
}
@@ -68,7 +68,7 @@ public abstract class LeaderboardGroupBaseImpl implements LeaderboardGroupBase {
}
public QualifiedObjectIdentifier getIdentifier() {
return getType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
return getPermissionType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier());
}
public TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier() {
@@ -80,7 +80,7 @@ public abstract class LeaderboardGroupBaseImpl implements LeaderboardGroupBase {
}
@Override
public HasPermissions getType() {
public HasPermissions getPermissionType() {
return SecuredDomainType.LEADERBOARD_GROUP;
}
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.base.impl;
import java.io.Serializable;
import java.util.Collections;
import java.util.UUID;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.SharedDomainFactory;
@@ -10,13 +11,17 @@ import com.sap.sse.common.Color;
import com.sap.sse.common.impl.NamedImpl;
public class MarkImpl extends NamedImpl implements Mark {
private static final long serialVersionUID = 1900673146064411979L;
private static final long serialVersionUID = -4817053057647606900L;
private final Color color;
private final String shape;
private final String pattern;
private final MarkType type;
private final String shortName;
private final Serializable id;
private final UUID originatingMarkTemplateId;
private final UUID originatingMarkPropertiesId;
public MarkImpl(String name) {
this(name, name);
@@ -27,12 +32,27 @@ public class MarkImpl extends NamedImpl implements Mark {
}
public MarkImpl(Serializable id, String name, MarkType type, Color color, String shape, String pattern) {
this(id, name, /* use name as short name, too, by default */ name, type, color, shape, pattern,
/* originatingMarkTemplateId */ null, /* originatingMarkPropertiesId */ null);
}
public MarkImpl(Serializable id, String name, MarkType type, Color color, String shape, String pattern,
UUID originatingMarkTemplateId, UUID originatingMarkPropertiesId) {
this(id, name, /* use name as short name, too, by default */ name, type, color, shape, pattern,
originatingMarkTemplateId, originatingMarkPropertiesId);
}
public MarkImpl(Serializable id, String name, String shortName, MarkType type, Color color, String shape,
String pattern, UUID originatingMarkTemplateId, UUID originatingMarkPropertiesId) {
super(name);
this.shortName = shortName;
this.id = id;
this.type = type;
this.color = color;
this.shape = shape;
this.pattern = pattern;
this.originatingMarkTemplateId = originatingMarkTemplateId;
this.originatingMarkPropertiesId = originatingMarkPropertiesId;
}
@Override
@@ -49,7 +69,7 @@ public class MarkImpl extends NamedImpl implements Mark {
@Override
public Mark resolve(SharedDomainFactory<?> domainFactory) {
Mark result = domainFactory.getOrCreateMark(getId(), getName(), type, color, shape, pattern);
Mark result = domainFactory.getOrCreateMark(getId(), getName(), shortName, type, color, shape, pattern);
return result;
}
@@ -73,6 +93,21 @@ public class MarkImpl extends NamedImpl implements Mark {
return type;
}
@Override
public String getShortName() {
return shortName;
}
@Override
public UUID getOriginatingMarkTemplateIdOrNull() {
return originatingMarkTemplateId;
}
@Override
public UUID getOriginatingMarkPropertiesIdOrNull() {
return originatingMarkPropertiesId;
}
public String toString() {
return getId() + " " + (getColor() == null ? "" : (getColor() + " ")) + super.toString();
}
@@ -147,7 +147,7 @@ public class SharedDomainFactoryImpl<RLR extends RaceLogResolver> implements Sha
@Override
public Mark getOrCreateMark(String name) {
return getOrCreateMark(name, name);
return getOrCreateMark(name, name, name);
}
@Override
@@ -157,21 +157,29 @@ public class SharedDomainFactoryImpl<RLR extends RaceLogResolver> implements Sha
@Override
public Mark getOrCreateMark(Serializable id, String name, MarkType markType) {
return getOrCreateMark(id, name, markType, /* color */ null, /* shape */ null, /* pattern */ null);
return getOrCreateMark(id, name, /* no separate short name available here */ name, markType, /* color */ null, /* shape */ null, /* pattern */ null);
}
@Override
public Mark getOrCreateMark(Serializable id, String name) {
return getOrCreateMark(id, name, /* type */ null, /* color */ null, /* shape */ null, /* pattern */ null);
public Mark getOrCreateMark(Serializable id, String name, String shortName) {
return getOrCreateMark(id, name, shortName, /* originatingMarkTemplateId */ null,
/* originatingMarkPropertiesId */ null);
}
@Override
public Mark getOrCreateMark(String toStringRepresentationOfID, String name) {
return getOrCreateMark(toStringRepresentationOfID, name, /* type */ null, /* color */ null, /* shape */ null, /* pattern */ null);
public Mark getOrCreateMark(Serializable id, String name, String shortName, UUID originatingMarkTemplateId,
UUID originatingMarkPropertiesId) {
return getOrCreateMark(id, name, shortName, /* type */ null, /* color */ null, /* shape */ null,
/* pattern */ null, originatingMarkTemplateId, originatingMarkPropertiesId);
}
@Override
public Mark getOrCreateMark(String toStringRepresentationOfID, String name, String shortName) {
return getOrCreateMark(toStringRepresentationOfID, name, shortName, /* type */ null, /* color */ null, /* shape */ null, /* pattern */ null);
}
@Override
public Mark getOrCreateMark(Serializable id, String name, MarkType type, Color color, String shape, String pattern) {
public Mark getOrCreateMark(Serializable id, String name, String shortName, MarkType type, Color color, String shape, String pattern) {
Mark result = markCache.get(id);
if (result == null) {
result = new MarkImpl(id, name, type, color, shape, pattern);
@@ -179,37 +187,51 @@ public class SharedDomainFactoryImpl<RLR extends RaceLogResolver> implements Sha
}
return result;
}
@Override
public Mark getOrCreateMark(String toStringRepresentationOfID, String name, MarkType type,
Color color, String shape, String pattern) {
public Mark getOrCreateMark(Serializable id, String name, String shortName, MarkType type, Color color,
String shape, String pattern, UUID originatingMarkTemplateId,
UUID originatingMarkPropertiesId) {
Mark result = markCache.get(id);
if (result == null) {
result = new MarkImpl(id, name, shortName, type, color, shape, pattern, originatingMarkTemplateId,
originatingMarkPropertiesId);
cacheMark(id, result);
}
return result;
}
@Override
public Mark getOrCreateMark(String toStringRepresentationOfID, String name, String shortName,
MarkType type, Color color, String shape, String pattern) {
Serializable id = toStringRepresentationOfID;
if (markIdCache.containsKey(toStringRepresentationOfID)) {
id = markIdCache.get(toStringRepresentationOfID);
}
return getOrCreateMark(id, name, type, color, shape, pattern);
return getOrCreateMark(id, name, shortName, type, color, shape, pattern);
}
@Override
public ControlPointWithTwoMarks getOrCreateControlPointWithTwoMarks(Serializable id, String name, Mark left, Mark right) {
public ControlPointWithTwoMarks getOrCreateControlPointWithTwoMarks(Serializable id, String name, Mark left,
Mark right, String shortName) {
final ControlPointWithTwoMarks result;
final ControlPointWithTwoMarks fromCache = controlPointWithTwoMarksCache.get(id);
if (fromCache != null) {
result = fromCache;
} else {
result = createControlPointWithTwoMarks(id, left, right, name);
result = createControlPointWithTwoMarks(id, left, right, name, shortName);
}
return result;
}
@Override
public ControlPointWithTwoMarks getOrCreateControlPointWithTwoMarks(
String toStringRepresentationOfID, String name, Mark left, Mark right) {
String toStringRepresentationOfID, String name, Mark left, Mark right, String shortName) {
Serializable id = toStringRepresentationOfID;
if (controlPointWithTwoMarksIdCache.containsKey(toStringRepresentationOfID)) {
id = controlPointWithTwoMarksIdCache.get(toStringRepresentationOfID);
}
return getOrCreateControlPointWithTwoMarks(id, name, left, right);
return getOrCreateControlPointWithTwoMarks(id, name, left, right, shortName);
}
private void cacheMark(Serializable id, Mark result) {
@@ -218,13 +240,18 @@ public class SharedDomainFactoryImpl<RLR extends RaceLogResolver> implements Sha
}
@Override
public ControlPointWithTwoMarks createControlPointWithTwoMarks(Mark left, Mark right, String name) {
return createControlPointWithTwoMarks(name, left, right, name);
public ControlPointWithTwoMarks createControlPointWithTwoMarks(Mark left, Mark right, String name,
String shortName) {
return createControlPointWithTwoMarks(name, left, right, name, shortName);
}
@Override
public ControlPointWithTwoMarks createControlPointWithTwoMarks(Serializable id, Mark left, Mark right, String name) {
ControlPointWithTwoMarks result = new ControlPointWithTwoMarksImpl(id, left, right, name);
public ControlPointWithTwoMarks createControlPointWithTwoMarks(Serializable id, Mark left, Mark right, String name,
String shortName) {
if (shortName == null || shortName.isEmpty()) {
shortName = name;
}
ControlPointWithTwoMarks result = new ControlPointWithTwoMarksImpl(id, left, right, name, shortName);
controlPointWithTwoMarksCache.put(id, result);
controlPointWithTwoMarksIdCache.put(id.toString(), id);
return result;
@@ -0,0 +1,30 @@
package com.sap.sailing.domain.coursetemplate;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.common.MarkType;
import com.sap.sse.common.Color;
import com.sap.sse.common.Named;
import com.sap.sse.common.Util;
/**
* The properties that a {@link Mark}, a {@link MarkTemplate}, and a {@link MarkProperties}
* object have in common.
*
* @author Axel Uhl (d043530)
*
*/
public interface CommonMarkProperties extends Named {
Color getColor();
String getShape();
String getPattern();
MarkType getType();
String getShortName();
default boolean hasEqualAppeareanceWith(CommonMarkProperties properties) {
return Util.equalsWithNull(getColor(), properties.getColor())
&& Util.equalsWithNull(getShape(), properties.getShape())
&& Util.equalsWithNull(getPattern(), properties.getPattern())
&& Util.equalsWithNull(getType(), properties.getType())
&& Util.equalsWithNull(getShortName(), properties.getShortName());
};
}
@@ -0,0 +1,19 @@
package com.sap.sailing.domain.coursetemplate;
import com.sap.sailing.domain.base.ControlPoint;
import com.sap.sse.common.Named;
/**
* What a {@link WaypointTemplate} references. It is a template for the construction of a {@link ControlPoint} and
* references the {@link MarkRole}s that describe the "logical" marks. The {@link MarkTemplate}s from which the mark
* properties are to be drawn during construction of the actual {@link ControlPoint} are provided by the
* {@link CourseTemplate} (see {@link CourseTemplate#getDefaultMarkTemplateForMarkRole(MarkRole)}).
*
* @author Axel Uhl (d043530)
*
*/
public interface ControlPointTemplate extends Named {
Iterable<MarkRole> getMarkRoles();
String getShortName();
}
@@ -0,0 +1,21 @@
package com.sap.sailing.domain.coursetemplate;
import com.sap.sailing.domain.base.ControlPoint;
import com.sap.sse.common.Named;
/**
* What a {@link WaypointTemplate} references. It is a template for the construction of a {@link ControlPoint} and
* references the {@link MarkTemplate}s from which this control point shall be constructed.
*
* @author Axel Uhl (d043530)
*
* @param <P>
* type of the annotation used to convey positioning-related information on the {@link MarkConfiguration}
* objects contains; typical instantiations would, e.g., be with {@link MarkConfigurationRequestAnnotation} and
* {@link MarkConfigurationResponseAnnotation}.
*/
public interface ControlPointWithMarkConfiguration<P> extends Named {
Iterable<MarkConfiguration<P>> getMarkConfigurations();
String getShortName();
}
@@ -0,0 +1,123 @@
package com.sap.sailing.domain.coursetemplate;
import java.net.URL;
import java.util.Map;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sailing.domain.base.Mark;
import com.sap.sse.common.Named;
/**
* A course that does not necessarily consist of {@link Mark}s existing in a regatta but instead is defined by
* {@link MarkConfiguration}s. This means changes to the model are easily possible without requiring to pollute the
* {@link RegattaLog}. The effective Marks will then be created upon Course creation based on the
* {@link MarkConfiguration}s.
* <p>
*
* This is the model represented in a course editor which deals with editing the waypoint sequence as well as changing
* how marks are mapped to {@link MarkTemplate}s and how marks are generally configured in terms of their base
* properties and tracking/positioning information. This configuration can reference a non-{@code null}
* {@link CourseTemplate} (returned by {@link #getOptionalCourseTemplate()}). Being an "editor model" this means that
* the actual {@link #getWaypoints() waypoint sequence} may be inconsistent with what the course template demands. When
* requesting a course update, the waypoint sequence takes precedence over a course template. Likewise, the
* {@link #getNumberOfLaps() number of laps} in a request to create a course may be inconsistent with the waypoint
* sequence which then should be interpreted such that the request processor shall adjust the waypoint sequence such
* that the number of laps requested is reflected in the waypoint sequence consistently.
* <p>
*
* When used in a request to create a course template, the {@link #getRepeatablePart()} will be used as the
* new course template's {@link CourseTemplate#getRepeatablePart() repeatable part}.<p>
*
* In particular, the base {@link #getWaypoints() waypoints sequence}, when returned by a request, has at least one
* occurrence of a repeatable part, should the configuration specify a course template, be consistent with it, and
* should the course template specify a repeatable part. As a response, this configuration will tell the
* {@link #getNumberOfLaps()} found or created during the request.
* <p>
*
* The configuration for all marks are returned by {@link #getAllMarks()} because when pushing this to a Regatta the
* marks for all these configurations have to be established.
*
* @param <P>
* the type used to {@link MarkConfiguration#getAnnotationInfo() annotate} the {@link MarkProperties} used in
* this course configuration. Different annotations are expected to be used for course configurations used in
* requests sent to a service and for responses produced by a service.
*/
public interface CourseConfiguration<P> extends WithOptionalRepeatablePart, Named {
String getShortName();
/**
* If this course configuration has been obtained from a {@link CourseTemplate} without modifying the sequence of
* waypoints and their mapping to {@link MarkTemplate}s, this method will return that {@link CourseTemplate}. In all
* other cases it will return {@code null}. If not {@code null}, the methods of {@link WithOptionalRepeatablePart}
* delegate to the {@link CourseTemplate} returned by this method.
*/
CourseTemplate getOptionalCourseTemplate();
URL getOptionalImageURL();
/**
* Returns all marks that are part of this course configuration. This consists of:
* <ul>
* <li>All {@link MarkConfiguration} that are used by at least one {@link WaypointWithMarkConfiguration}.</li>
* <li>Any additional {@link MarkConfiguration} that is intended to be used as a spare mark.</li>
* </ul>
*/
Iterable<MarkConfiguration<P>> getAllMarks();
/**
* The waypoint sequence. If a repeatable part is specified, the sequence returned must contain at least one
* occurrence of the repeatable part. All {@link MarkConfiguration} objects referenced are part of the result of
* {@link #getAllMarks()}. If you want the waypoint sequence including as many occurrences
* of any repeatable part, call {@link #getWaypoints(int) getWaypoints(}{@link #getNumberOfLaps() getNumberOfLaps())}.
*/
Iterable<WaypointWithMarkConfiguration<P>> getWaypoints();
/**
* Returns all {@link MarkConfiguration MarkConfigurations} that are part of the {@link CourseConfigurationBase} and
* have a role explicitly associated. A mark role links back to a {@link CourseTemplate} and makes it possible to
* recognize congruence between the course described by this configuration and the course described by a course
* template, regardless of the actual marks that are used in certain roles. This enables, for example, to use two
* different marks for the same role in different laps and still find the course configuration to be congruent to
* its original course template. This information is also useful when creating another course template from this
* configuration ("save as...") because then the roles to which the mark configurations refer can be (re-)used in
* the new course template constructed from this configuration.
*/
Map<MarkConfiguration<P>, MarkRole> getAssociatedRoles();
/**
* Returns a map whose key set equals the result of {@link #getAllMarks()} and whose values are the result
* of mapping the key through the result of {@link #getAssociatedRoles()}. If a mark configuration does not
* have a role assigned, {@code null} will be the associated value in the map returned by this method.
*/
Map<MarkConfiguration<P>, MarkRole> getAllMarksWithOptionalRoles();
/**
* A {@link CourseConfigurationBase} having a {@link #getOptionalCourseTemplate() course template} can optionally
* also tell the number of laps in case this configuration is used to create or update a course. In a request this
* number can be used to override the {@link CourseTemplate#getDefaultNumberOfLaps() default number of laps} that
* the course template would use. Depending on the use-case this may be ignored (saving a course template) or is
* required (creating a regatta course from a configuration with a course template that specifies a repeatable
* part).
* <p>
*
* In a response the number of laps is reported as {@code null} if a non-{@code null}
* {@link #getOptionalCourseTemplate() course template} is provided but the course is not compatible with that
* template; {@code -1} if there is no course template specified, or the course template has no
* {@link CourseTemplate#getRepeatablePart() repeatable part}; otherwise the number of laps (number of occurrences
* of the repeatable part plus one).
*/
Integer getNumberOfLaps();
/**
* The waypoint sequence with lap count applied. If this course configuration has no {@link #hasRepeatablePart()
* repeatable part}, the result is the same as that of {@link #getWaypoints()}. The result has
* {@link MarkConfiguration configuration information} for the marks used. All {@link MarkConfiguration} objects
* referenced are part of the result of {@link #getAllMarks()}.
*
* @param numberOfLaps
* the repeatable part will be inserted {@code numberOfLaps-1} times. For example, a two-lap course will
* have the repeatable part exactly once. Use {@link #getNumberOfLaps()} to
* obtain the waypoint sequence as specified by this course configuration.
*/
Iterable<WaypointWithMarkConfiguration<P>> getWaypoints(int numberOfLaps);
}
@@ -0,0 +1,188 @@
package com.sap.sailing.domain.coursetemplate;
import java.net.URL;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.common.security.SecuredDomainType;
import com.sap.sse.common.NamedWithUUID;
import com.sap.sse.common.Util;
import com.sap.sse.security.shared.HasPermissions;
import com.sap.sse.security.shared.QualifiedObjectIdentifier;
import com.sap.sse.security.shared.TypeRelativeObjectIdentifier;
import com.sap.sse.security.shared.WithQualifiedObjectIdentifier;
/**
* A {@link Course} can be created from this template. The template defines {@link MarkRole}s, {@link MarkTemplate}s,
* {@link ControlPointTemplate}s, and {@link WaypointTemplate}s and assembles the latter into a sequence. The sequence
* of waypoint templates can optionally have a {@link #getRepeatablePart() repeatable part}. When increasing or reducing
* the number of laps, more or, respectively, fewer occurrences of this repeatable part will be inserted into the
* {@link Course}.
* <p>
*
* The course template can define additional mark templates that are not part of the waypoint template sequence. This
* can be used, e.g., to define spare marks. Those additional {@link MarkTemplate}s can optionally tell a
* {@link MarkRole} through the {@link #getDefaultMarkRolesForMarkTemplates()} map as their preferred role for which they
* define a spare.
* <p>
*
* All {@link MarkRole}s reachable through the {@link #getWaypointTemplates() waypoint template sequence} must refer to
* a corresponding {@link MarkTemplate}. This link is represented by the {@link #getDefaultMarkTemplatesForMarkRoles()}
* method. For all {@link MarkTemplate}s connected to a role in this way, {@link #getDefaultMarkRolesForMarkTemplates()}
* must contain the reverse link.
* <p>
*
* The course template has a {@link #getId() globally unique ID} and with this can have a life cycle. Its
* {@link #getWaypointTemplates() waypoint template sequence} is immutable. If a logical copy is created, the new copy
* will have a different ID ("Save as...").
* <p>
*
* The {@link MarkTemplate}s and {@link MarkRole}s also have their globally unique IDs. When creating a variant, the new
* copy can reference the <em>same</em> set or a subset of the mark templates that the original references. This is
* helpful in case tracking options or bindings to physical marks are created and remembered. For example, a course
* template for an "I" (inner loop) course and a course template for an "O" (outer loop) course may share the mark
* templates for the start line and the "1" top mark, and when tracker bindings are established they can automatically
* be applied to all marks created from the same {@link MarkTemplate}.
*
* @author Axel Uhl (d043530)
*
*/
public interface CourseTemplate extends WithOptionalRepeatablePart, NamedWithUUID, HasTags, WithQualifiedObjectIdentifier {
/**
* A short name, as typically used in sailing instructions documents, such as "L" for a Windward/Leeward course template
* with leeward finish. When combined with the number of laps, a well-known short name such as "L2" or "L3" can result.
*/
String getShortName();
/**
* The templates for all the marks that shall be made available in the regatta when applying this template. All
* marks required to construct the waypoint sequence must be produced from this set of mark templates. There may be
* additional mark templates returned by this method, for constructing marks not immediately required for the
* waypoint sequence but, e.g., as proposals for spare or alternative marks. For example, templates for alternative
* marks for the windward mark may be returned to quickly accommodate for wind shifts.
*/
Iterable<MarkTemplate> getMarkTemplates();
/**
* Returns all {@link MarkTemplate}s from {@link #getMarkTemplates()} which are not the default mark template
* for {@link #getMarkRoles() any mark role used by this course template}.
*/
default Iterable<MarkTemplate> getMarkTemplatesNotIncludedInCourse() {
final Set<MarkTemplate> result = new HashSet<>();
Util.addAll(getMarkTemplates(), result);
for (final MarkRole markRole : getMarkRoles()) {
result.remove(getDefaultMarkTemplateForMarkRole(markRole));
}
return result;
}
/**
* The set of {@link MarkRole}s one gets when enumerating all {@link #getWaypointTemplates() waypoint templates},
* fetching their {@link WaypointTemplate#getControlPointTemplate() control point template} and from it all
* {@link ControlPointTemplate#getMarkRoles() mark roles}.
*/
default Iterable<MarkRole> getMarkRoles() {
final Set<MarkRole> result = new HashSet<>();
for (final WaypointTemplate wpt : getWaypointTemplates()) {
for (final MarkRole markRole : wpt.getControlPointTemplate().getMarkRoles()) {
result.add(markRole);
}
}
return result;
}
MarkTemplate getMarkTemplateByIdIfContainedInCourseTemplate(UUID markTemplateId);
/**
* Returns a sequence of {@link WaypointTemplate}s that can be use to construct a course. If this course template
* defines a repeatable waypoint sub-sequence, the {@code numberOfLaps} parameter is used to decide how many times
* to repeat this sub-sequence. Typically, the repeatable sub-sequence will be repeated one times fewer than the
* {@code numberOfLaps}. For example, in a typical windward-leeward "L" course we would have
* {@code Start/Finish, [1, 4p/4s], 1, Start/Finish}. For an "L1" course with only one lap, we'd like to have
* {@code Start/Finish, 1, Start/Finish}, so the repeatable sub-sequence, enclosed by the brackets in the example
* above, will occur zero times. For an "L2" the repeatable sub-sequence will occur once, and so on. However, an
* implementation is free to choose an interpretation of {@code numberOfLaps} that meets callers' expectations.
* <p>
*
* Note that usually a value of {@code 1} for the {@code numberOfLaps} parameter will lead to no occurrence of a
* repeatable part in the result. When building a course configuration for this course template, waypoint
* configurations for at least one occurrence of the repeatable part are expected to be present, meaning that at
* least a lap count of 2 would be required to meet that requirement.
* <p>
*
* Note: this method is mostly intended for testing purposes; a client / UI would normally use a
* {@link CourseConfiguration} that refers to a {@link CourseTemplate} and then invoke
* {@link CourseConfiguration#getWaypoints(int)}.
*
* @param numberOfLaps
* if the course defines a repeatable part, the number of laps at least needs to be {@code 1} for the
* default implementation, and an {@link IllegalArgumentException} shall be thrown in case a value less
* than {@code 1} is used if this template specifies a repeatable part. Note again that the number of
* repetitions of the repeatable part is usually one less than the number of laps, therefore this
* limitation.
*/
Iterable<WaypointTemplate> getWaypointTemplates(int numberOfLaps);
/**
* The sequence of waypoint templates that are used to define the course. If this course template defines
* a {@link #getRepeatablePart() repeatable part} then this repeatable part refers to this waypoint sequence
* by indices. It is therefore essential that the repeatable part then occurs in this waypoint template
* sequence at least (and usually exactly) once.
*/
Iterable<WaypointTemplate> getWaypointTemplates();
/**
* @return the value set contains at least all {@link MarkRole}s reachable through the {@link #getWaypointTemplates()
* waypoint template sequence}; the keys for those values are what clients get when calling
* {@link #getDefaultMarkTemplateForMarkRole(MarkRole)} for the value. Optionally, additional default role
* assignment for "spare" marks, such as the "1" role for alternative windward mark templates may be returned
* by this method.
*/
Map<MarkTemplate, MarkRole> getDefaultMarkRolesForMarkTemplates();
/**
* Short for {@link #getDefaultMarkRolesForMarkTemplates()}.{@link Map#get(Object) get(markTemplate)}
*/
MarkRole getOptionalAssociatedRole(MarkTemplate markTemplate);
/**
* The key set contains exactly those {@link MarkRole}s one gets when enumerating all roles reachable through the
* {@link #getWaypointTemplates()} and their {@link WaypointTemplate#getControlPointTemplate()}'s
* {@link ControlPointTemplate#getMarkRoles() mark roles}, which is the same as calling {@link #getMarkRoles()}. The
* values tell the {@link MarkTemplate} to use when instantiating a course from this template in place of the key
* role. All mark templates from the value set are guaranteed to be in {@link #getMarkTemplates()}, but
* {@link #getMarkTemplates()} may contain more elements, e.g., for spare marks.
*/
Map<MarkRole, MarkTemplate> getDefaultMarkTemplatesForMarkRoles();
/**
* Obtains the {@link MarkTemplate} that, when instantiating a course from this template, shall be used
* to create the {@link Mark} that acts in the role identified by {@code markRole}. Short for
* {@link #getDefaultMarkTemplatesForMarkRoles()}.{@link Map#get(Object) get(markRole)}. For all {@link MarkRole}
* objects from {@link #getMarkRoles()} this has to return a valid non-{@code null} result.
*/
MarkTemplate getDefaultMarkTemplateForMarkRole(MarkRole markRole);
MarkRole getMarkRoleByIdIfContainedInCourseTemplate(UUID markRoleId);
URL getOptionalImageURL();
Integer getDefaultNumberOfLaps();
public static TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier(UUID courseTemplateUUID) {
return new TypeRelativeObjectIdentifier(courseTemplateUUID.toString());
}
@Override
default QualifiedObjectIdentifier getIdentifier() {
return getPermissionType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier(getId()));
}
@Override
default HasPermissions getPermissionType() {
return SecuredDomainType.COURSE_TEMPLATE;
}
}
@@ -0,0 +1,116 @@
package com.sap.sailing.domain.coursetemplate;
import java.util.Iterator;
import com.sap.sailing.domain.base.CourseBase;
import com.sap.sse.common.Util;
/**
* Checks compatibility of a course with a {@link CourseTemplate}. As a generic type, subclasses
* can specify what to use as a course, simply by defining how to get to the waypoint sequence of
* the course and from the waypoints to the marks and from the marks to the {@link MarkRole}s.
* This way, the core method {@link #isCourseInstanceOfCourseTemplate()} becomes re-usable across
* types as different as {@link CourseBase} or {@link CourseConfiguration}.
*
* @param <C>
* the type of the actual course to check for compatibility with a {@link CourseTemplate}
* @param <M>
* type type of the marks used by the course of type {@code C}
* @param <W>
* type of the waypoints produced by the course of type {@code C}
*/
public abstract class CourseTemplateCompatibilityChecker<C, M, W> {
private final C course;
private final CourseTemplate courseTemplate;
public CourseTemplateCompatibilityChecker(C course, CourseTemplate courseTemplate) {
super();
assert course != null;
assert courseTemplate != null;
this.course = course;
this.courseTemplate = courseTemplate;
}
/**
* Takes a {@code course} and a {@link CourseTemplate} and checks whether the course is a valid "instance" of the
* {@link CourseTemplate}. A course is a valid instance of a course template if for all marks in the course (as
* defined by the {@link #getMarks(Object)} method) there is a role associated with the mark (as defined by the
* {@link #getMarkRole(Object)} method), and that role is consistent with the role at the corresponding place in the
* course template. Furthermore, the waypoint sequence of the course (as defined by the
* {@link #getWaypoints(Object)} method) needs to conform to the course template's waypoint sequence, modulo the
* number of laps. The result is the number of laps if the {@link CourseTemplate} has a
* {@link CourseTemplate#getRepeatablePart() repeatable part}, or {@code -1} if the course does not have a
* repeatable part, and {@code null} if the course is not a valid instance of the course template.
* <p>
*
* This means in particular that the course can use different marks for the same role in different laps and still
* conform to the course template.
* <p>
*
* @return the number of laps if the {@link CourseTemplate} has a {@link CourseTemplate#getRepeatablePart()
* repeatable part}, or {@code -1} if the course does not have a repeatable part, and {@code null} if the
* course is not a valid instance of the course template
*/
public Integer isCourseInstanceOfCourseTemplate() {
int numberOfLaps = -1;
boolean validCourseTemplateUsage = true;
final Iterable<WaypointTemplate> effectiveCourseSequence;
if (courseTemplate.hasRepeatablePart()) {
final RepeatablePart optionalRepeatablePart = courseTemplate.getRepeatablePart();
final int numberOfWaypointsInTemplate = Util.size(courseTemplate.getWaypointTemplates());
final int numberOfWaypointsInCourse = Util.size(getWaypoints(course));
final int lengthOfRepeatablePart = optionalRepeatablePart.getZeroBasedIndexOfRepeatablePartEnd()
- optionalRepeatablePart.getZeroBasedIndexOfRepeatablePartStart();
final int lengthOfNonRepeatablePart = numberOfWaypointsInTemplate - lengthOfRepeatablePart;
final int lengthOfRepetitions = numberOfWaypointsInCourse - lengthOfNonRepeatablePart;
if (lengthOfRepetitions % lengthOfRepeatablePart == 0) {
numberOfLaps = lengthOfRepetitions / lengthOfRepeatablePart + 1;
effectiveCourseSequence = courseTemplate.getWaypointTemplates(numberOfLaps);
} else {
validCourseTemplateUsage = false;
effectiveCourseSequence = courseTemplate.getWaypointTemplates();
}
} else {
effectiveCourseSequence = courseTemplate.getWaypointTemplates();
}
if (validCourseTemplateUsage) {
final Iterator<WaypointTemplate> waypointTemplateIterator = effectiveCourseSequence.iterator();
final Iterator<W> waypointIterator = getWaypoints(course).iterator();
while (waypointTemplateIterator.hasNext() && validCourseTemplateUsage) {
final WaypointTemplate waypointTemplate = waypointTemplateIterator.next();
final Iterable<MarkRole> markRolesOfControlPoint = waypointTemplate.getControlPointTemplate().getMarkRoles();
final W waypoint = waypointIterator.next();
final Iterable<M> marksOfControlPoint = getMarks(waypoint);
if (Util.size(markRolesOfControlPoint) != Util.size(marksOfControlPoint)) {
validCourseTemplateUsage = false;
} else {
final Iterator<MarkRole> markRoleIterator = markRolesOfControlPoint.iterator();
final Iterator<M> markIterator = marksOfControlPoint.iterator();
while (markRoleIterator.hasNext()) {
final MarkRole markRoleFromCourseTemplate = markRoleIterator.next();
final M markFromRegatta = markIterator.next();
final MarkRole roleForMarkOrNull = getMarkRole(markFromRegatta);
if (!Util.equalsWithNull(markRoleFromCourseTemplate, roleForMarkOrNull)) {
validCourseTemplateUsage = false;
}
}
}
}
}
return validCourseTemplateUsage ? numberOfLaps : null;
}
protected C getCourse() {
return course;
}
protected CourseTemplate getCourseTemplate() {
return courseTemplate;
}
protected abstract MarkRole getMarkRole(M markFromCourse);
protected abstract Iterable<M> getMarks(W waypoint);
protected abstract Iterable<W> getWaypoints(C course);
}
@@ -0,0 +1,10 @@
package com.sap.sailing.domain.coursetemplate;
import com.sap.sailing.domain.common.Position;
public interface FixedPositioning extends Positioning {
Position getFixedPosition();
default <T> T accept(PositioningVisitor<T> visitor) {
return visitor.visit(this);
}
}
@@ -0,0 +1,24 @@
package com.sap.sailing.domain.coursetemplate;
/**
* Describes a configuration with an optional {@link #getOptionalMarkTemplate() mark template} that delivers
* explicit {@link CommonMarkPropertiesWithOptionalPositioning}. The object can be marked for
* adding to the inventory when applied. The {@link #getFreestyleProperties() freestyle properties}
* take precedence.
*
* @author Axel Uhl (D043530)
*
*/
public interface FreestyleMarkConfiguration<P> extends MarkConfiguration<P> {
CommonMarkProperties getFreestyleProperties();
/**
* @return a non-{@code null} object if this mark configuration originated from a {@link MarkProperties} object
* where the {@link #getFreestyleProperties() freestyle properties} override one or more of the
* {@link MarkProperties}' attributes. If {@link #isStoreToInventory()} is {@code true}, this means
* that the {@link MarkProperties} object returned by this method shall be updated accordingly to
* reflect the {@link #getFreestyleProperties() freestyle properties} after the update.
*/
@Override
MarkProperties getOptionalMarkProperties();
}
@@ -0,0 +1,6 @@
package com.sap.sailing.domain.coursetemplate;
public interface HasTags {
public Iterable<String> getTags();
}
@@ -0,0 +1,57 @@
package com.sap.sailing.domain.coursetemplate;
import java.util.Collections;
import com.sap.sailing.domain.base.Mark;
/**
* Describes how a {@link Mark} in a regatta shall be configured, in terms of its basic attributes as well as regarding
* its positioning / tracking / location information.
* <p>
*
* The information may come from one or more sources:
* <ul>
* <li>A {@link MarkTemplate} can provide {@link CommonMarkProperties}</li>
* <li>An existing {@link Mark} from the regatta may be selected; the mark may or may not be used to fill the role
* defined by the {@link #getOptionalMarkTemplate() mark template}. Regarding the {@link #getEffectiveProperties()
* effective properties}, the {@link Mark}'s properties take precedence over the {@link MarkTemplate}'s properties.</li>
* <li>A {@link MarkProperties} object may be selected from the inventory or may have been
* {@link #getOptionalMarkProperties() identified} as having been used while creating the {@link #getMark() mark} in the
* regatta, to provide optional base configuration ({@link CommonMarkProperties}) as well as tracking/location/device
* information. All of those are optional in a {@link MarkProperties} object. Those present will take precedence and
* will be merged with the properties obtained from the {@link #getOptionalMarkTemplate() mark template} and the
* properties obtained from an existing {@link #getMark() mark}.</li>
* </ul>
*
* The combined set of properties merged according to the rules above can be obtained by calling
* {@link #getEffectiveProperties()}.
* <p>
*
* The {@link #isStoreToInventory()} method tells whether, when applying this configuration to create or use a
* {@link Mark}, the {@link #getEffectiveProperties() effective mark properties} shall be recorded as a new
* {@link MarkProperties} object in the inventory.
*
* @param <P>
* type of the annotation used to convey positioning-related information; typical instantiations would, e.g.,
* be with {@link MarkConfigurationRequestAnnotation} and {@link MarkConfigurationResponseAnnotation}.
*
* @author Axel Uhl (D043530)
*
*/
public interface MarkConfiguration<P> extends ControlPointWithMarkConfiguration<P> {
<T> T accept(MarkConfigurationVisitor<T, P> visitor);
MarkTemplate getOptionalMarkTemplate();
MarkProperties getOptionalMarkProperties();
CommonMarkProperties getEffectiveProperties();
P getAnnotationInfo();
@Override
default Iterable<MarkConfiguration<P>> getMarkConfigurations() {
final Iterable<MarkConfiguration<P>> result = (Iterable<MarkConfiguration<P>>) Collections.singleton(this);
return result;
}
}
@@ -0,0 +1,43 @@
package com.sap.sailing.domain.coursetemplate;
import com.sap.sse.common.Util;
/**
* Additional data for a {@link MarkConfiguration} when using it in a request, e.g., to create a course. It may provide
* {@link #getOptionalPositioning() positioning information} that the receiver of this object in expected to implement.
* In contrast, this object does not contain information about the actual positioning situation as the server would see
* it.
*
* @author Axel Uhl (D043530)
*
*/
public interface MarkConfigurationRequestAnnotation {
boolean isStoreToInventory();
Positioning getOptionalPositioning();
/**
* Can be used to provide the name/short name for a {@link MarkRole} to use for this mark when a
* {@link CourseTemplate} is created from the {@link CourseConfiguration} containing the {@link MarkConfiguration}
* that is annotated with {@code this} object.<p>
*
* An existing {@link MarkRole} object would be referenced by the {@link CourseConfiguration}'s
* {@link CourseConfiguration#getAssociatedRoles()} map and therefore wouldn't need any mark role
* creation request as part of this request annotation.
*/
MarkRoleCreationRequest getOptionalMarkRoleCreationRequest();
/**
* Describes a request to create a {@link MarkRole} in the context of a {@link CourseTemplate}. If several
* {@link MarkConfiguration}s request the creation of {@link MarkRole}s using a {@link MarkRoleCreationRequest} then
* those requests will be merged based on name and short name equality during {@link CourseTemplate} creation. Both,
* name and short name, have to be {@link Util#equalsWithNull(Object, Object) equal} in order to cause a merge.
*
* @author Axel Uhl (d043530)
*
*/
interface MarkRoleCreationRequest {
String getMarkRoleName();
String getMarkRoleShortName();
}
}
@@ -0,0 +1,19 @@
package com.sap.sailing.domain.coursetemplate;
import com.sap.sailing.domain.common.DeviceIdentifier;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sse.common.TimeRange;
import com.sap.sse.common.Util.Triple;
/**
* Used on a {@link MarkConfiguration} as a response to a client, providing a result-oriented view on a mark
* configuration. In particular, positioning data may be contained that informs clients about the current tracking and
* positioning state of the mark in question.
*
* @author Axel Uhl (D043530)
*
*/
public interface MarkConfigurationResponseAnnotation {
GPSFix getLastKnownPosition();
Iterable<Triple<DeviceIdentifier, TimeRange, GPSFix>> getDeviceMappings();
}
@@ -0,0 +1,8 @@
package com.sap.sailing.domain.coursetemplate;
public interface MarkConfigurationVisitor<T, P> {
T visit(FreestyleMarkConfiguration<P> markConfiguration);
T visit(MarkPropertiesBasedMarkConfiguration<P> markConfiguration);
T visit(MarkTemplateBasedMarkConfiguration<P> markConfiguration);
T visit(RegattaMarkConfiguration<P> markConfiguration);
}
@@ -0,0 +1,25 @@
package com.sap.sailing.domain.coursetemplate;
import java.util.Arrays;
import com.sap.sailing.domain.base.ControlPointWithTwoMarks;
/**
* A template that can be used to construct a {@link ControlPointWithTwoMarks}.
*
* @author Axel Uhl (d043530)
*
* @param <P>
* type of the annotation used to convey positioning-related information; typical instantiations would, e.g.,
* be with {@link MarkConfigurationRequestAnnotation} and {@link MarkConfigurationResponseAnnotation}.
*/
public interface MarkPairWithConfiguration<P> extends ControlPointWithMarkConfiguration<P> {
MarkConfiguration<P> getLeft();
MarkConfiguration<P> getRight();
@Override
default Iterable<MarkConfiguration<P>> getMarkConfigurations() {
return Arrays.asList(getLeft(), getRight());
}
}
@@ -0,0 +1,105 @@
package com.sap.sailing.domain.coursetemplate;
import java.util.Map;
import java.util.UUID;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.common.MarkType;
import com.sap.sailing.domain.common.security.SecuredDomainType;
import com.sap.sse.common.Color;
import com.sap.sse.common.NamedWithUUID;
import com.sap.sse.common.TimePoint;
import com.sap.sse.security.shared.HasPermissions;
import com.sap.sse.security.shared.QualifiedObjectIdentifier;
import com.sap.sse.security.shared.TypeRelativeObjectIdentifier;
import com.sap.sse.security.shared.WithQualifiedObjectIdentifier;
/**
* Stores properties that can be applied to a mark in the context of an event or a regatta, including the mark's own
* attributes as well as tracking-related information such as a reference to the tracking device used to track the mark,
* or a fixed mark position, such as for a land mark, an official lateral or cardinal buoy, a regatta mark in a fixed
* position or a lighthouse.
* <p>
*
* Such a properties object can be linked to zero or more {@link MarkTemplate}s. When the user creates a course from a
* {@link CourseTemplate}, linked {@link MarkProperties} can be offered to configure the {@link Mark} created from the
* {@link CourseTemplate}'s {@link MarkTemplate}s.
* <p>
*
* {@link MarkProperties} objects can be tagged, and the tags may be used to search and filter a library of such
* {@link MarkProperties}. The tags can, e.g., express a venue name or a course area name or the name of a set of
* tracking devices typically used to track a course. A user may have more than one {@link MarkProperties} object
* available that is linked to a particular {@link MarkTemplate}. One of those could be selected depending on
* {@link #getPreviousUsage() when it was used last} for the {@link MarkTemplate}, or the user may be presented with the
* set of {@link MarkProperties} available and can select, search and filter.<p>
*
* These properties objects are entities and can be updated. For example, if the device used to track the mark
* shall be replaced permanently, also for future uses, the device ID could be updated.
*
* @author Axel Uhl (d043530)
*
*/
public interface MarkProperties extends CommonMarkProperties, NamedWithUUID, HasTags, WithQualifiedObjectIdentifier {
void setColor(Color color);
void setShape(String shape);
void setPattern(String pattern);
void setType(MarkType type);
void setShortName(String shortName);
/**
* {@code null} or a specification as to how to obtain position data for the mark; could, e.g., be
* a fixed position specification or the specification of a tracking device identifier. See the
* specializations of {@link Positioning} for an overview.
*/
Positioning getPositioningInformation();
/**
* Updates this properties object such that the next call to {@link #getPositioningInformation()} returns the
* {@code positioningInformation} object provided to this call.
*/
void setPositioningInformation(Positioning positioningInformation);
/**
* Returns an immutable map that tells all {@link #addMarkTemplateUsage(MarkTemplate, TimePoint) recorded}
* usages of this {@link MarkProperties} object for a {@link MarkTemplate}.
*/
Map<MarkTemplate, TimePoint> getLastUsedMarkTemplate();
/**
* Records a usage of this {@link MarkProperties} object in conjunction with the
* {@code markTemplate} provided. If this usage is later than that in
* {@link #getLastUsedMarkTemplate()}, it will replace the previously last usage.
*/
void addMarkTemplateUsage(MarkTemplate markTemplate, TimePoint usageTimePoint);
/**
* Returns an immutable map that tells all {@link #addMarkRoleUsage(MarkRole, TimePoint) recorded}
* usages of this {@link MarkProperties} object for a {@link MarkRole}.
*/
Map<MarkRole, TimePoint> getLastUsedMarkRole();
/**
* Records a usage of this {@link MarkProperties} object in conjunction with the
* {@code markRole} provided. If this usage is later than that in
* {@link #getLastUsedMarkRole()}, it will replace the previously last usage.
*/
void addMarkRoleUsage(MarkRole markRole, TimePoint usageTmePoint);
public static TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier(UUID markPropertiesUUID) {
return new TypeRelativeObjectIdentifier(markPropertiesUUID.toString());
}
@Override
default QualifiedObjectIdentifier getIdentifier() {
return getPermissionType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier(getId()));
}
@Override
default HasPermissions getPermissionType() {
return SecuredDomainType.MARK_PROPERTIES;
}
}
@@ -0,0 +1,20 @@
package com.sap.sailing.domain.coursetemplate;
/**
* Configures a mark based on a {@link MarkProperties} object returned from {@link #getOptionalMarkProperties()}.
* Optionally, a non-{@code null} {@link MarkTemplate} may be returned from {@link #getOptionalMarkTemplate()} which
* then means that the mark to create and configure assumes the "role" defined by the mark template. The
* {@link MarkProperties} attributes, if present, take precedence over their respective {@link MarkTemplate}
* counterparts.
*
* @author Axel Uhl (D043530)
*
*/
public interface MarkPropertiesBasedMarkConfiguration<P> extends MarkConfiguration<P> {
/**
* The "optional" {@link MarkProperties} for this type of mark configuration are the {@link MarkProperties}
* referenced by this object and hence never {@code null}.
*/
@Override
MarkProperties getOptionalMarkProperties();
}
@@ -0,0 +1,79 @@
package com.sap.sailing.domain.coursetemplate;
import java.util.Map;
import java.util.UUID;
import com.sap.sailing.domain.common.DeviceIdentifier;
import com.sap.sailing.domain.common.MarkType;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.coursetemplate.impl.FixedPositioningImpl;
import com.sap.sailing.domain.coursetemplate.impl.MarkPropertiesImpl;
import com.sap.sailing.domain.coursetemplate.impl.TrackingDeviceBasedPositioningImpl;
import com.sap.sse.common.Color;
import com.sap.sse.common.TimePoint;
public class MarkPropertiesBuilder {
private final UUID id;
private final String name;
private final String shortName;
private final Color color;
private final String shape;
private final String pattern;
private final MarkType type;
private Iterable<String> tags;
private Positioning positioningInformation;
private Map<MarkTemplate, TimePoint> lastUsedTemplate;
private Map<MarkRole, TimePoint> lastUsedRole;
public MarkPropertiesBuilder(UUID id, String name, String shortName, Color color, String shape, String pattern,
MarkType type) {
this.id = id;
this.name = name;
this.shortName = shortName;
this.color = color;
this.shape = shape;
this.pattern = pattern;
this.type = type;
}
public MarkPropertiesBuilder withTags(Iterable<String> tags) {
this.tags = tags;
return this;
}
public MarkPropertiesBuilder withDeviceId(DeviceIdentifier deviceId) {
this.positioningInformation = new TrackingDeviceBasedPositioningImpl(deviceId);
return this;
}
public MarkPropertiesBuilder withFixedPosition(Position position) {
this.positioningInformation = new FixedPositioningImpl(position);
return this;
}
public MarkProperties build() {
MarkPropertiesImpl impl = new MarkPropertiesImpl(id, name, shortName, color, shape, pattern, type);
if (tags != null) {
impl.setTags(tags);
}
if (positioningInformation != null) {
impl.setPositioningInformation(positioningInformation);
}
if (lastUsedTemplate != null) {
impl.setLastUsedMarkTemplate(lastUsedTemplate);
}
if (lastUsedRole != null) {
impl.setLastUsedMarkRole(lastUsedRole);
}
return impl;
}
public void withLastUsedTemplate(Map<MarkTemplate, TimePoint> lastUsedTemplate) {
this.lastUsedTemplate = lastUsedTemplate;
}
public void withLastUsedRole(Map<MarkRole, TimePoint> lastUsedRole) {
this.lastUsedRole = lastUsedRole;
}
}
@@ -0,0 +1,55 @@
package com.sap.sailing.domain.coursetemplate;
import java.util.Collections;
import java.util.UUID;
import com.sap.sailing.domain.common.security.SecuredDomainType;
import com.sap.sse.common.Named;
import com.sap.sse.common.NamedWithUUID;
import com.sap.sse.security.shared.HasPermissions;
import com.sap.sse.security.shared.QualifiedObjectIdentifier;
import com.sap.sse.security.shared.TypeRelativeObjectIdentifier;
import com.sap.sse.security.shared.WithQualifiedObjectIdentifier;
/**
* A mark role defines the purpose of a mark used in the waypoint sequences of a regatta course or course template and
* allows users to swap out marks or mark templates without changing the the effective waypoint sequence. Having this, a
* course template and regatta course may define a compatible waypoint sequence while being based on different mark
* definitions.<p>
*
* An example of a role would be "1", representing the windward mark in a course sequence diagram.
*
* TODO after removing IsMarkRole, all needs to revert to MarkRole. The original idea
* was to represent mark role "proxies" by objects of a type compatible to an
* interface that MarkRole and the proxy type have in common, leading to
* MarkRoleName[Impl]. However, I think it would be better to represent the
* request for the creation of a role with a specific name during the creation
* of a course template from a CourseConfiguration by annotating the MarkConfiguration
* not only with storeToInventory and an optional Positioning object but also
* by an optional mark role creation specification. This can be part of the
* annotation type used for MarkConfiguration objects during the "request phase"
* as implemented on branch bug5168 and already merged to bug5085. The merge
* of those changes into branch bug5165 currently is causing conflicts that should
* be solved.
*/
public interface MarkRole extends Named, NamedWithUUID, WithQualifiedObjectIdentifier, ControlPointTemplate {
public static TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier(UUID markPropertiesUUID) {
return new TypeRelativeObjectIdentifier(markPropertiesUUID.toString());
}
@Override
default Iterable<MarkRole> getMarkRoles() {
return Collections.singleton(this);
}
@Override
default QualifiedObjectIdentifier getIdentifier() {
return getPermissionType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier(getId()));
}
@Override
default HasPermissions getPermissionType() {
return SecuredDomainType.MARK_ROLE;
}
}
@@ -0,0 +1,37 @@
package com.sap.sailing.domain.coursetemplate;
import java.util.HashMap;
import java.util.Map;
import com.sap.sailing.domain.base.ControlPointWithTwoMarks;
import com.sap.sailing.domain.coursetemplate.impl.MarkRolePairImpl;
/**
* A template that can be used to construct a {@link ControlPointWithTwoMarks}.
*
* @author Axel Uhl (d043530)
*
*/
public interface MarkRolePair extends ControlPointTemplate {
MarkRole getLeft();
MarkRole getRight();
/**
* In the context of one {@link CourseTemplate}, this helps to reuse {@link MarkRolePair} instances that are equal
* to each other. The factory maintains a cache of {@link MarkRolePair} objects, and a lookup is performed if a pair
* is {@link #create(String, String, MarkRole, MarkRole) requested}; if an equal mark role pair already exists in
* the cache, it is returned. Otherwise, a new pair is created, entered into the cache and then returned.
*/
public class MarkRolePairFactory {
private final Map<MarkRolePair, MarkRolePair> markPairs = new HashMap<>();
public MarkRolePair create(String name, String shortName, MarkRole left, MarkRole right) {
// use name as short name if none is provided
final MarkRolePair markRolePair = new MarkRolePairImpl(name,
shortName != null && !shortName.isEmpty() ? shortName : name, left, right);
// usage of computeIfAbsent ensures recycling of identical MarkPairTemplate instances in the CourseTemplate
return markPairs.computeIfAbsent(markRolePair, mrp -> mrp);
}
}
}
@@ -0,0 +1,48 @@
package com.sap.sailing.domain.coursetemplate;
import java.util.UUID;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.common.security.SecuredDomainType;
import com.sap.sailing.domain.coursetemplate.MarkTemplate.MarkTemplateResolver;
import com.sap.sse.common.IsManagedByCache;
import com.sap.sse.common.NamedWithUUID;
import com.sap.sse.security.shared.HasPermissions;
import com.sap.sse.security.shared.QualifiedObjectIdentifier;
import com.sap.sse.security.shared.TypeRelativeObjectIdentifier;
import com.sap.sse.security.shared.WithQualifiedObjectIdentifier;
/**
* A template for creating a {@link Mark}. It has a globally unique ID and can be used in zero or more
* {@link CourseTemplate}s. It is immutable and contains all {@link CommonMarkProperties common properties for a mark}.
* In the context of a {@link CourseTemplate} it can be {@link CourseTemplate#getDefaultMarkRolesForMarkTemplates() linked} to a {@link MarkRole}
* that it wants to assume by default.
*
* @author Axel Uhl (d043530)
*
*/
public interface MarkTemplate extends NamedWithUUID, CommonMarkProperties,
WithQualifiedObjectIdentifier, IsManagedByCache<MarkTemplateResolver> {
public interface MarkTemplateResolver {
MarkTemplate getOrCreate(MarkTemplate markTemplate);
}
@Override
default IsManagedByCache<MarkTemplateResolver> resolve(MarkTemplateResolver resolver) {
return resolver.getOrCreate(this);
}
public static TypeRelativeObjectIdentifier getTypeRelativeObjectIdentifier(UUID markTemplateUUID) {
return new TypeRelativeObjectIdentifier(markTemplateUUID.toString());
}
@Override
default QualifiedObjectIdentifier getIdentifier() {
return getPermissionType().getQualifiedObjectIdentifier(getTypeRelativeObjectIdentifier(getId()));
}
@Override
default HasPermissions getPermissionType() {
return SecuredDomainType.MARK_TEMPLATE;
}
}
@@ -0,0 +1,11 @@
package com.sap.sailing.domain.coursetemplate;
/**
* The {@link #getOptionalMarkTemplate()} method will always return a non-{@code null} {@link MarkTemplate}
* which is the only source of the properties returned by {@link #getEffectiveProperties()}.
*
* @author Axel Uhl (D043530)
*
*/
public interface MarkTemplateBasedMarkConfiguration<P> extends MarkConfiguration<P> {
}
@@ -0,0 +1,14 @@
package com.sap.sailing.domain.coursetemplate;
import com.sap.sailing.domain.common.DeviceIdentifier;
/**
* A specification how a mark's position shall be obtained. Typical cases can be a fixed position,
* or a {@link DeviceIdentifier} telling which device to use to track the object.
*
* @author Axel Uhl (D043530)
*
*/
public interface Positioning {
<T> T accept(PositioningVisitor<T> visitor);
}
@@ -0,0 +1,7 @@
package com.sap.sailing.domain.coursetemplate;
public interface PositioningVisitor<T> {
T visit(FixedPositioning fixedPositioning);
T visit(TrackingDeviceBasedPositioning trackingDeviceBasedPositioning);
}
@@ -0,0 +1,30 @@
package com.sap.sailing.domain.coursetemplate;
import com.sap.sailing.domain.base.Mark;
/**
* Configures a mark solely by referencing an already existing mark. No additional configuration beyond what already
* exists in the regatta can be specified. The {@link #getEffectiveProperties() effective properties} are simply all
* aspects of the {@link #getMark() mark} in its {@link CommonMarkProperties} role, without any additional
* location/position/tracking data.
* <p>
*
* An optional non-{@code null} {@link MarkTemplate} may be returned from {@link #getOptionalMarkTemplate()} which then
* leads to recording a usage relation between the regatta {@link Mark} and said {@link MarkTemplate}. This relation can be
* requested using {@link Mark#getOriginatingMarkTemplateIdOrNull()}.
* {@link #getOptionalMarkProperties()} can reference a {@link MarkProperties} object if the {@link #getMark mark} was
* originally created from that {@link MarkProperties} object. Again, the resulting usage relation can be requested using
* {@link Mark#getOriginatingMarkPropertiesIdOrNull()}.
* <p>
*
* The {@link #getEffectiveProperties() effective properties} will contain
* {@link CommonMarkPropertiesWithOptionalPositioning#getFixedPosition() fixed position data} or a
* {@link CommonMarkPropertiesWithOptionalPositioning#getTrackingDeviceIdentifier() tracking device ID} in case the mark
* has such information on the server side.
*
* @author Axel Uhl (D043530)
*
*/
public interface RegattaMarkConfiguration<P> extends MarkConfiguration<P> {
Mark getMark();
}
@@ -0,0 +1,89 @@
package com.sap.sailing.domain.coursetemplate;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.sap.sse.common.Util;
/**
* Reusable implementation of the semantics for the repeatable part of a course sequence. The sequence is defined by the
* start and end of the zero-based repeatable part of the complete sequence. The following requirements need to be
* fulfilled:
* <ul>
* <li>The {@link #getZeroBasedIndexOfRepeatablePartStart()} and {@link #getZeroBasedIndexOfRepeatablePartEnd()} are
* required to be >=0</li>
* <li>{@link #getZeroBasedIndexOfRepeatablePartStart()} is truly less than {@link #getZeroBasedIndexOfRepeatablePartEnd()}</li>
* </ul>
*/
public interface RepeatablePart extends Serializable {
/**
* The index into {@link #waypoints} of the first waypoint that is to be cloned for repetitive laps.
*/
int getZeroBasedIndexOfRepeatablePartStart();
/**
* The index into {@link #waypoints} of the first waypoint that comes after the sub-sequence to be cloned for
* repetitive laps.
*/
int getZeroBasedIndexOfRepeatablePartEnd();
default int length() {
return getZeroBasedIndexOfRepeatablePartEnd()-getZeroBasedIndexOfRepeatablePartStart();
}
/**
* Returns a sequence of waypoints (T) that can be use to construct a course. If this course template
* defines a repeatable waypoint sub-sequence, the {@code numberOfLaps} parameter is used to decide how many times
* to repeat this sub-sequence. Typically, the repeatable sub-sequence will be repeated one times fewer than the
* {@code numberOfLaps}. For example, in a typical windward-leeward "L" course we would have
* {@code Start/Finish, [1, 4p/4s], 1, Start/Finish}. For an "L1" course with only one lap, we'd like to have
* {@code Start/Finish, 1, Start/Finish}, so the repeatable sub-sequence, enclosed by the brackets in the example
* above, will occur zero times. For an "L2" the repeatable sub-sequence will occur once, and so on.
*
* @throws IllegalArgumentException
* in case the given {@code sequenceWithRepeatablePart} contains less or equal elements to
* {@link #getZeroBasedIndexOfRepeatablePartEnd()}.
*/
default <T> Iterable<T> createSequence(int laps, Iterable<T> sequenceWithRepeatablePart) {
validateRepeatablePartForSequence(sequenceWithRepeatablePart);
final int zeroBasedIndexOfRepeatablePartStart = getZeroBasedIndexOfRepeatablePartStart();
final int zeroBasedIndexOfRepeatablePartEnd = getZeroBasedIndexOfRepeatablePartEnd();
final List<T> sequenceWithRepeatablePartAsList = new ArrayList<>();
Util.addAll(sequenceWithRepeatablePart, sequenceWithRepeatablePartAsList);
final int sequenceLength = sequenceWithRepeatablePartAsList.size();
final List<T> result = new ArrayList<>();
// Non-repeatable start of the sequence
for (int i = 0; i < zeroBasedIndexOfRepeatablePartStart; i++) {
result.add(sequenceWithRepeatablePartAsList.get(i));
}
// Repeatable part of the sequence
for (int lap = 1; lap < laps; lap++) {
for (int i = zeroBasedIndexOfRepeatablePartStart; i < zeroBasedIndexOfRepeatablePartEnd; i++) {
result.add(sequenceWithRepeatablePartAsList.get(i));
}
}
// Non-repeatable end of the sequence
for (int i = zeroBasedIndexOfRepeatablePartEnd; i < sequenceLength; i++) {
result.add(sequenceWithRepeatablePartAsList.get(i));
}
return result;
}
/**
* Validates that this repeatable part is compatible with the given {@code sequenceWithRepeatablePart} and causes an
* exception in case it isn't.
*
* @throws IllegalArgumentException
* in case the given {@code sequenceWithRepeatablePart} contains less or equal elements to
* {@link #getZeroBasedIndexOfRepeatablePartEnd()}.
*/
default <T> void validateRepeatablePartForSequence(Iterable<T> sequenceWithRepeatablePart) {
final int zeroBasedIndexOfRepeatablePartEnd = getZeroBasedIndexOfRepeatablePartEnd();
final int sequenceLength = Util.size(sequenceWithRepeatablePart);
if (sequenceLength < zeroBasedIndexOfRepeatablePartEnd) {
throw new IllegalArgumentException("Repeatable part (" + getZeroBasedIndexOfRepeatablePartStart() + ", "
+ zeroBasedIndexOfRepeatablePartEnd + ") is out of range for sequence of length " + sequenceLength);
}
}
}
@@ -0,0 +1,10 @@
package com.sap.sailing.domain.coursetemplate;
import com.sap.sailing.domain.common.DeviceIdentifier;
public interface TrackingDeviceBasedPositioning extends Positioning {
DeviceIdentifier getDeviceIdentifier();
default <T> T accept(PositioningVisitor<T> visitor) {
return visitor.visit(this);
}
}
@@ -0,0 +1,10 @@
package com.sap.sailing.domain.coursetemplate;
import java.io.Serializable;
import com.sap.sailing.domain.common.PassingInstruction;
public interface WaypointTemplate extends Serializable {
ControlPointTemplate getControlPointTemplate();
PassingInstruction getPassingInstruction();
}
@@ -0,0 +1,8 @@
package com.sap.sailing.domain.coursetemplate;
import com.sap.sailing.domain.common.PassingInstruction;
public interface WaypointWithMarkConfiguration<P> {
ControlPointWithMarkConfiguration<P> getControlPoint();
PassingInstruction getPassingInstruction();
}
@@ -0,0 +1,9 @@
package com.sap.sailing.domain.coursetemplate;
public interface WithOptionalRepeatablePart {
default boolean hasRepeatablePart() {
return getRepeatablePart() != null;
}
RepeatablePart getRepeatablePart();
}
@@ -0,0 +1,54 @@
package com.sap.sailing.domain.coursetemplate.impl;
import com.sap.sailing.domain.common.MarkType;
import com.sap.sailing.domain.coursetemplate.CommonMarkProperties;
import com.sap.sse.common.Color;
import com.sap.sse.common.impl.RenamableImpl;
public class CommonMarkPropertiesImpl extends RenamableImpl implements CommonMarkProperties {
private static final long serialVersionUID = 5599464471737104833L;
protected String shortName;
protected Color color;
protected String shape;
protected String pattern;
protected MarkType type;
public CommonMarkPropertiesImpl(String name, String shortName, Color color, String shape, String pattern, MarkType type) {
super(name);
this.shortName = shortName;
this.color = color;
this.shape = shape;
this.pattern = pattern;
this.type = type;
}
public CommonMarkPropertiesImpl(final CommonMarkProperties commonMarkProperties) {
this(commonMarkProperties.getName(), commonMarkProperties.getShortName(), commonMarkProperties.getColor(),
commonMarkProperties.getShape(), commonMarkProperties.getPattern(), commonMarkProperties.getType());
}
@Override
public Color getColor() {
return color;
}
@Override
public String getShape() {
return shape;
}
@Override
public String getPattern() {
return pattern;
}
@Override
public MarkType getType() {
return type;
}
@Override
public String getShortName() {
return shortName;
}
}
@@ -0,0 +1,212 @@
package com.sap.sailing.domain.coursetemplate.impl;
import java.net.URL;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import com.sap.sailing.domain.coursetemplate.CourseConfiguration;
import com.sap.sailing.domain.coursetemplate.CourseTemplate;
import com.sap.sailing.domain.coursetemplate.CourseTemplateCompatibilityChecker;
import com.sap.sailing.domain.coursetemplate.MarkConfiguration;
import com.sap.sailing.domain.coursetemplate.MarkRole;
import com.sap.sailing.domain.coursetemplate.RepeatablePart;
import com.sap.sailing.domain.coursetemplate.WaypointWithMarkConfiguration;
public class CourseConfigurationImpl<P> implements CourseConfiguration<P> {
private static final long serialVersionUID = -9189989170055144298L;
private final String shortName;
private final CourseTemplate optionalCourseTemplate;
// TODO decide if we should combine markConfigurations and roleMapping to one field
private final Iterable<MarkConfiguration<P>> markConfigurations;
private final Map<MarkConfiguration<P>, MarkRole> associatedRoles;
/**
* The waypoints sequence, with at least a single occurrence of any {@link #getRepeatablePart() repeatable part},
* in order to obtain a sequence for more than only one lap (meaning zero occurrences of the repeatable part).
* There may be more occurrences of the repeatable part than only one, in particular to allow subsequent repetitions
* to use different mark configurations in their lap. See also {@link #getWaypoints()} and
* {@link #getWaypoints(int)}. So, if {@link #numberOfLaps} is less than or equal to {@code 1}, an occurrence
* of the repeatable part will be in this sequence although it isn't used in the current configuration.
* This is, however, required in order to interpret the index-based repeatable part in case one or more
* occurrences of the repeatable part will be requested.<p>
*
* This sequence may contain more occurrences of the repeatable part than requested through {@link #numberOfLaps}
* even if one or more occurrences of the repeatable part are requested this way. These extra occurrences
* will then be used if {@link #getWaypoints(int)} is called with more laps than {@link #numberOfLaps}. This way,
* a course configuration can prepare for more laps being sailed and prepare a configuration for extra laps
* that differ from the configurations used for the laps currently selected.
*/
private final List<WaypointWithMarkConfiguration<P>> waypoints;
/**
* Refers to a repeatable part in {@link #waypoints}. Other than in a {@link CourseTemplate} that always
* has exactly one occurrence of a repeatable part in its waypoint template sequence, here the {@link #waypoints}
* sequence must have at least one but may have multiple occurrences of the repeatable part. The special handling
* that happens in this class is that when more than one occurrence already exists in {@link #waypoints}, upon
* adding more repetitions the <em>last</em> occurrence will be repeated. See also {@link #getWaypoints(int)}.
*/
private final RepeatablePart optionalRepeatablePart;
/**
* The number of laps this course configuration has, in case there is a {@link #getRepeatablePart() repeatable part}
* defined for a valid {@link #optionalCourseTemplate}. In this case, {@link #getWaypoints()} will consider this
* number of laps and will return
*/
private final Integer numberOfLaps;
private final String name;
private final URL optionalImageURL;
/**
* @param numberOfLaps
* {@code null} if a non-{@code null} {@code optionalCourseTemplate} is provided but the course is not
* compatible with that template; {@code -1} if there is no course template specified, or the course
* template has a {@code null} {@link CourseTemplate#getRepeatablePart() repeatable part}; otherwise the
* number of laps (number of occurrences of the repeatable part plus one).
*/
public CourseConfigurationImpl(CourseTemplate optionalCourseTemplate,
Iterable<MarkConfiguration<P>> markConfigurations, Map<MarkConfiguration<P>, MarkRole> associatedRoles,
List<WaypointWithMarkConfiguration<P>> waypoints,
RepeatablePart optionalRepeatablePart, Integer numberOfLaps, String name, String shortName, URL optionalImageURL) {
super();
assert !associatedRoles.values().contains(null);
this.optionalCourseTemplate = optionalCourseTemplate;
this.markConfigurations = markConfigurations;
this.associatedRoles = associatedRoles;
this.waypoints = waypoints;
if (optionalRepeatablePart != null && optionalCourseTemplate == null) {
throw new IllegalArgumentException("Inconsistent course configuration "+name+": When a repeatable part is defined, a course template must be provided");
}
this.optionalRepeatablePart = optionalRepeatablePart;
this.numberOfLaps = numberOfLaps;
this.name = name;
this.shortName = shortName;
this.optionalImageURL = optionalImageURL;
}
@Override
public String getShortName() {
return shortName;
}
@Override
public RepeatablePart getRepeatablePart() {
return optionalRepeatablePart;
}
@Override
public CourseTemplate getOptionalCourseTemplate() {
return optionalCourseTemplate;
}
@Override
public Iterable<MarkConfiguration<P>> getAllMarks() {
return markConfigurations;
}
@Override
public Iterable<WaypointWithMarkConfiguration<P>> getWaypoints() {
return waypoints;
}
@Override
public Map<MarkConfiguration<P>, MarkRole> getAssociatedRoles() {
return associatedRoles;
}
@Override
public Map<MarkConfiguration<P>, MarkRole> getAllMarksWithOptionalRoles() {
final Map<MarkConfiguration<P>, MarkRole> result = new HashMap<>();
for (MarkConfiguration<P> mc : markConfigurations) {
result.put(mc, associatedRoles.get(mc));
}
return result;
}
@Override
public Integer getNumberOfLaps() {
return numberOfLaps;
}
private class CourseTemplateCompatibilityCheckerForCourseConfiguration extends
CourseTemplateCompatibilityChecker<CourseConfiguration<P>, MarkConfiguration<P>, WaypointWithMarkConfiguration<P>> {
public CourseTemplateCompatibilityCheckerForCourseConfiguration() {
super(CourseConfigurationImpl.this, optionalCourseTemplate);
}
@Override
protected MarkRole getMarkRole(MarkConfiguration<P> markFromCourse) {
return getAssociatedRoles().get(markFromCourse);
}
@Override
protected Iterable<MarkConfiguration<P>> getMarks(WaypointWithMarkConfiguration<P> waypoint) {
return waypoint.getControlPoint().getMarkConfigurations();
}
@Override
protected Iterable<WaypointWithMarkConfiguration<P>> getWaypoints(CourseConfiguration<P> course) {
return waypoints;
}
}
/**
* @return the number of repeatable part occurrences if the {@link CourseTemplate} has a {@link CourseTemplate#getRepeatablePart()
* repeatable part}, or {@code -1} if the course does not have a repeatable part, and {@code null} if the
* course is not a valid instance of the course template
*/
private Integer getNumberOfRepeatablePartOccurrencesInWaypoints() {
final Integer numberOfLaps = new CourseTemplateCompatibilityCheckerForCourseConfiguration().isCourseInstanceOfCourseTemplate();
return numberOfLaps == null ? null : numberOfLaps == -1 ? -1 : numberOfLaps-1;
}
@Override
public Iterable<WaypointWithMarkConfiguration<P>> getWaypoints(int numberOfLaps) {
final Iterable<WaypointWithMarkConfiguration<P>> result;
if (hasRepeatablePart()) {
if (numberOfLaps < 1) {
throw new IllegalArgumentException("The course template "+this+" has a repeatable part, hence the number of laps needs to be at least 1.");
}
final Integer numberOfRepeatablePartOccurrences = getNumberOfRepeatablePartOccurrencesInWaypoints();
assert numberOfRepeatablePartOccurrences != null; // expected to be enforced by a check in the constructor
final List<WaypointWithMarkConfiguration<P>> waypointsWithCorrectNumberOfLaps = new LinkedList<>();
// Copy the prefix of the repeatable part without modifications:
for (int i=0; i<getRepeatablePart().getZeroBasedIndexOfRepeatablePartStart(); i++) {
waypointsWithCorrectNumberOfLaps.add(waypoints.get(i));
}
// Copy as many occurrences of repeatable parts as required (numberbOfLaps-1) and keep repeating
// the last occurrence from waypoints if more are requested than are present there:
for (int repetitions=0; repetitions<numberOfLaps-1; repetitions++) {
final int occurrenceInWaypointsToUse = Math.min(repetitions, numberOfRepeatablePartOccurrences-1);
for (int i=getRepeatablePart().getZeroBasedIndexOfRepeatablePartStart(); i<getRepeatablePart().getZeroBasedIndexOfRepeatablePartEnd(); i++) {
waypointsWithCorrectNumberOfLaps.add(waypoints.get(
getRepeatablePart().length()*occurrenceInWaypointsToUse + i));
}
}
// Add the postfix from after the last repeatable part occurrence in waypoints:
for (int i=getRepeatablePart().getZeroBasedIndexOfRepeatablePartStart()+
numberOfRepeatablePartOccurrences*getRepeatablePart().length(); i<waypoints.size(); i++) {
waypointsWithCorrectNumberOfLaps.add(waypoints.get(i));
}
assert waypointsWithCorrectNumberOfLaps.size() == waypoints.size() +
(numberOfLaps-1-numberOfRepeatablePartOccurrences)*getRepeatablePart().length();
result = waypointsWithCorrectNumberOfLaps;
} else {
result = waypoints;
}
return result;
}
@Override
public String getName() {
return this.name;
}
@Override
public URL getOptionalImageURL() {
return optionalImageURL;
}
}
@@ -0,0 +1,242 @@
package com.sap.sailing.domain.coursetemplate.impl;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import com.sap.sailing.domain.coursetemplate.CourseTemplate;
import com.sap.sailing.domain.coursetemplate.MarkRole;
import com.sap.sailing.domain.coursetemplate.MarkTemplate;
import com.sap.sailing.domain.coursetemplate.RepeatablePart;
import com.sap.sailing.domain.coursetemplate.WaypointTemplate;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.NamedWithUUIDImpl;
public class CourseTemplateImpl extends NamedWithUUIDImpl implements CourseTemplate {
private static final long serialVersionUID = -183875832585632806L;
private final String shortName;
private final Set<MarkTemplate> marks;
private final ArrayList<WaypointTemplate> waypoints;
private final Map<MarkTemplate, MarkRole> defaultMarkRolesForMarkTemplates;
private final Map<MarkRole, MarkTemplate> defaultMarkTemplatesForMarkRoles;
private Iterable<String> tags = new ArrayList<>();
private final URL optionalImageURL;
private final RepeatablePart optionalRepeatablePart;
private final Integer defaultNumberOfLaps;
/**
* Creates a course template with a random UUID and no repeatable part.
* @param marks
* all mark templates made available in this course template
* @param waypoints
* may refer only to {@link MarkTemplate}s provided in the {@code marks} parameter
* @param defaultMarkTemplatesForMarkRoles
* all {@link MarkRole}s reachable through {@code waypoints} must appear as keys in this map's key set;
* the values represent the {@link MarkTemplate} to use when instantiating this course template for the
* key role
* @param defaultMarkRolesForMarkTemplates
* for those mark templates referenced as values in {@code defaultMarkTemplatesForRoles}, the value in
* this map has to be the key in {@code defaultMarkTemplatesForRoles}; for all other
* {@link MarkTemplates}s from {@code marks} it is optional whether a mapping to a default
* {@link MarkRole} is provided in this map
*/
public CourseTemplateImpl(String name, String shortName, Iterable<MarkTemplate> marks,
Iterable<WaypointTemplate> waypoints, Map<MarkRole, MarkTemplate> defaultMarkTemplatesForMarkRoles,
Map<MarkTemplate, MarkRole> defaultMarkRolesForMarkTemplates, URL optionalImageURL) {
this(UUID.randomUUID(), name, shortName, marks, waypoints, defaultMarkTemplatesForMarkRoles,
defaultMarkRolesForMarkTemplates, optionalImageURL);
}
/**
* Creates a course with the given UUID and no repeatable part.
*/
public CourseTemplateImpl(UUID id, String name, String shortName, Iterable<MarkTemplate> marks,
Iterable<WaypointTemplate> waypoints, Map<MarkRole, MarkTemplate> defaultMarkTemplatesForMarkRoles,
Map<MarkTemplate, MarkRole> defaultMarkRolesForMarkTemplates, URL optionalImageURL) {
this(id, name, shortName, marks, waypoints, defaultMarkTemplatesForMarkRoles, defaultMarkRolesForMarkTemplates,
optionalImageURL, /* optionalRepeatablePart */ null, /* default number of laps */ null);
}
/**
* @param defaultMarkRolesForMarkTemplates
* will be extended according to and checked for consistency against {@code defaultMarkTemplatesForMarkRoles}
*/
public CourseTemplateImpl(UUID id, String name, String shortName, Iterable<MarkTemplate> marks,
Iterable<WaypointTemplate> waypoints, Map<MarkRole, MarkTemplate> defaultMarkTemplatesForMarkRoles,
Map<MarkTemplate, MarkRole> defaultMarkRolesForMarkTemplates, URL optionalImageURL,
RepeatablePart optionalRepeatablePart, Integer defaultNumberOfLaps) {
super(name, id);
this.shortName = shortName;
this.defaultNumberOfLaps = defaultNumberOfLaps;
if (optionalRepeatablePart != null) {
optionalRepeatablePart.validateRepeatablePartForSequence(waypoints);
}
this.waypoints = new ArrayList<>();
Util.addAll(waypoints, this.waypoints);
this.marks = new HashSet<>();
Util.addAll(marks, this.marks);
this.optionalImageURL = optionalImageURL;
this.optionalRepeatablePart = optionalRepeatablePart;
this.defaultMarkRolesForMarkTemplates = new HashMap<>(defaultMarkRolesForMarkTemplates);
this.defaultMarkTemplatesForMarkRoles = new HashMap<>(defaultMarkTemplatesForMarkRoles);
for (final Entry<MarkRole, MarkTemplate> e : defaultMarkTemplatesForMarkRoles.entrySet()) {
final MarkRole previousDefaultMarkRole = this.defaultMarkRolesForMarkTemplates.put(e.getValue(), e.getKey());
if (previousDefaultMarkRole != null && previousDefaultMarkRole != e.getKey()) {
throw new IllegalArgumentException("Inconsistent default mark role assignment: mark template "+e.getValue()+
" was assigned to mark role "+previousDefaultMarkRole+" but now shall default to mark role "+e.getKey());
}
}
validateWaypointsAgainstRolesAndMappingSymmetry();
}
/**
* Throws an {@link IllegalArgumentException} in case a waypoint from {@link #waypoints} uses a mark role that uses
* a default mark template that is not in {@link #marks}; or if a mark role used in the waypoint sequence does not
* define a default mark template; or if the mark template used as the default for a mark role does not refer back
* to that same mark role as its default.
*/
private void validateWaypointsAgainstRolesAndMappingSymmetry() {
for (final WaypointTemplate waypoint : waypoints) {
for (final MarkRole markRole : waypoint.getControlPointTemplate().getMarkRoles()) {
if (!defaultMarkTemplatesForMarkRoles.containsKey(markRole)) {
throw new IllegalArgumentException("Mark role "+markRole+" used by waypoint template "+
waypoint+" in course template "+this+" is not providing a default mark template");
}
if (!Util.contains(marks, defaultMarkTemplatesForMarkRoles.get(markRole))) {
throw new IllegalArgumentException("Mark template " + defaultMarkTemplatesForMarkRoles.get(markRole)
+ " used by role " + markRole + " used by waypoint template " + waypoint
+ " in course template " + this + " is not provided in the collection of marks");
}
if (!Util.equalsWithNull(defaultMarkRolesForMarkTemplates.get(defaultMarkTemplatesForMarkRoles.get(markRole)), markRole)) {
throw new IllegalArgumentException("Mark template " + defaultMarkTemplatesForMarkRoles.get(markRole)
+ " used as default for mark role " + markRole
+ " does not use that same mark role as its default but instead refers to "
+ defaultMarkRolesForMarkTemplates.get(defaultMarkTemplatesForMarkRoles.get(markRole))
+ " as its default role");
}
}
}
}
@Override
public String getShortName() {
return shortName;
}
@Override
public MarkTemplate getDefaultMarkTemplateForMarkRole(MarkRole markRole) {
return defaultMarkTemplatesForMarkRoles.get(markRole);
}
@Override
public Map<MarkRole, MarkTemplate> getDefaultMarkTemplatesForMarkRoles() {
return defaultMarkTemplatesForMarkRoles;
}
@Override
public Iterable<MarkTemplate> getMarkTemplates() {
return marks;
}
@Override
public MarkTemplate getMarkTemplateByIdIfContainedInCourseTemplate(UUID markTemplateId) {
MarkTemplate result = null;
for (MarkTemplate markTemplate : marks) {
if (markTemplate.getId().equals(markTemplateId)) {
result = markTemplate;
break;
}
}
return result;
}
@Override
public Iterable<WaypointTemplate> getWaypointTemplates() {
return waypoints;
}
/**
* Similar code can be found in {@link CourseConfigurationImpl#getWaypoints(int)}; both methods
* rely on {@link RepeatablePart#createSequence(int, Iterable)} to do the actual work.
*/
@Override
public Iterable<WaypointTemplate> getWaypointTemplates(int numberOfLaps) {
final Iterable<WaypointTemplate> result;
if (hasRepeatablePart()) {
if (numberOfLaps < 1) {
throw new IllegalArgumentException("The course template "+this+" has a repeatable part, hence the number of laps needs to be at least 1.");
}
result = optionalRepeatablePart.createSequence(numberOfLaps, waypoints);
} else {
result = waypoints;
}
return result;
}
@Override
public RepeatablePart getRepeatablePart() {
return optionalRepeatablePart;
}
@Override
public Map<MarkTemplate, MarkRole> getDefaultMarkRolesForMarkTemplates() {
return defaultMarkRolesForMarkTemplates;
}
@Override
public MarkRole getOptionalAssociatedRole(MarkTemplate markTemplate) {
return defaultMarkRolesForMarkTemplates.get(markTemplate);
}
@Override
public MarkRole getMarkRoleByIdIfContainedInCourseTemplate(UUID markRoleId) {
MarkRole result = null;
for (MarkRole markRole : defaultMarkRolesForMarkTemplates.values()) {
if (markRole.getId().equals(markRoleId)) {
result = markRole;
break;
}
}
return result;
}
public void setAssociatedRoles(Map<MarkTemplate, MarkRole> associatedRoles) {
this.defaultMarkRolesForMarkTemplates.clear();
this.defaultMarkRolesForMarkTemplates.putAll(associatedRoles);
}
@Override
public URL getOptionalImageURL() {
return optionalImageURL;
}
@Override
public Iterable<String> getTags() {
return this.tags;
}
public void setTags(Iterable<String> tags) {
this.tags = tags;
}
@Override
public Integer getDefaultNumberOfLaps() {
return defaultNumberOfLaps;
}
}
@@ -0,0 +1,18 @@
package com.sap.sailing.domain.coursetemplate.impl;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.coursetemplate.FixedPositioning;
public class FixedPositioningImpl implements FixedPositioning {
private final Position fixedPosition;
public FixedPositioningImpl(Position fixedPosition) {
super();
this.fixedPosition = fixedPosition;
}
@Override
public Position getFixedPosition() {
return fixedPosition;
}
}
@@ -0,0 +1,51 @@
package com.sap.sailing.domain.coursetemplate.impl;
import com.sap.sailing.domain.coursetemplate.CommonMarkProperties;
import com.sap.sailing.domain.coursetemplate.FreestyleMarkConfiguration;
import com.sap.sailing.domain.coursetemplate.MarkConfigurationVisitor;
import com.sap.sailing.domain.coursetemplate.MarkProperties;
import com.sap.sailing.domain.coursetemplate.MarkTemplate;
public class FreestyleMarkConfigurationImpl<P> extends MarkConfigurationImpl<P> implements FreestyleMarkConfiguration<P> {
private static final long serialVersionUID = 8167307943855869321L;
private final MarkProperties optionalMarkProperties;
private final CommonMarkProperties freestyleProperties;
public FreestyleMarkConfigurationImpl(MarkTemplate optionalMarkTemplate, MarkProperties optionalMarkProperties,
CommonMarkProperties freestyleProperties, P annotation) {
super(optionalMarkTemplate, annotation);
this.optionalMarkProperties = optionalMarkProperties;
this.freestyleProperties = freestyleProperties;
}
@Override
public <T> T accept(MarkConfigurationVisitor<T, P> visitor) {
return visitor.visit(this);
}
@Override
public String getShortName() {
return freestyleProperties.getShortName();
}
@Override
public String getName() {
return freestyleProperties.getName();
}
@Override
public CommonMarkProperties getFreestyleProperties() {
return freestyleProperties;
}
@Override
public CommonMarkProperties getEffectiveProperties() {
return freestyleProperties;
}
@Override
public MarkProperties getOptionalMarkProperties() {
return optionalMarkProperties;
}
}
@@ -0,0 +1,26 @@
package com.sap.sailing.domain.coursetemplate.impl;
import com.sap.sailing.domain.coursetemplate.MarkConfiguration;
import com.sap.sailing.domain.coursetemplate.MarkTemplate;
public abstract class MarkConfigurationImpl<P> implements MarkConfiguration<P> {
private static final long serialVersionUID = -1130451024516101231L;
private final MarkTemplate optionalMarkTemplate;
private final P annotationInfo;
public MarkConfigurationImpl(MarkTemplate optionalMarkTemplate, P annotationInfo) {
super();
this.optionalMarkTemplate = optionalMarkTemplate;
this.annotationInfo = annotationInfo;
}
@Override
public MarkTemplate getOptionalMarkTemplate() {
return optionalMarkTemplate;
}
@Override
public P getAnnotationInfo() {
return annotationInfo;
}
}
@@ -0,0 +1,50 @@
package com.sap.sailing.domain.coursetemplate.impl;
import com.sap.sailing.domain.coursetemplate.MarkConfigurationRequestAnnotation;
import com.sap.sailing.domain.coursetemplate.Positioning;
public class MarkConfigurationRequestAnnotationImpl implements MarkConfigurationRequestAnnotation {
private final boolean storeToInventory;
private final Positioning optionalPositioning;
private final MarkRoleCreationRequest optionalMarkRoleCreationRequest;
public static class MarkRoleCreationRequestImpl implements MarkRoleCreationRequest {
private final String markRoleName;
private final String markRoleShortName;
public MarkRoleCreationRequestImpl(String markRoleName, String markRoleShortName) {
super();
this.markRoleName = markRoleName;
this.markRoleShortName = markRoleShortName;
}
@Override
public String getMarkRoleName() {
return markRoleName;
}
@Override
public String getMarkRoleShortName() {
return markRoleShortName;
}
}
public MarkConfigurationRequestAnnotationImpl(boolean storeToInventory, Positioning optionalPositioning, MarkRoleCreationRequest optionalMarkRoleCreationRequest) {
super();
this.storeToInventory = storeToInventory;
this.optionalPositioning = optionalPositioning;
this.optionalMarkRoleCreationRequest = optionalMarkRoleCreationRequest;
}
@Override
public boolean isStoreToInventory() {
return storeToInventory;
}
@Override
public Positioning getOptionalPositioning() {
return optionalPositioning;
}
@Override
public MarkRoleCreationRequest getOptionalMarkRoleCreationRequest() {
return optionalMarkRoleCreationRequest;
}
}
@@ -0,0 +1,40 @@
package com.sap.sailing.domain.coursetemplate.impl;
import java.util.Collections;
import com.sap.sailing.domain.common.DeviceIdentifier;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.coursetemplate.MarkConfigurationResponseAnnotation;
import com.sap.sse.common.TimeRange;
import com.sap.sse.common.Util.Triple;
import com.sap.sse.common.impl.TimeRangeImpl;
public class MarkConfigurationResponseAnnotationImpl implements MarkConfigurationResponseAnnotation {
private final GPSFix lastKnownPosition;
private final Iterable<Triple<DeviceIdentifier, TimeRange, GPSFix>> deviceMappings;
public MarkConfigurationResponseAnnotationImpl(GPSFix lastKnownPosition) {
this(lastKnownPosition, Collections.emptySet());
}
public MarkConfigurationResponseAnnotationImpl(GPSFix lastKnownPosition, DeviceIdentifier currentTrackingDevice) {
this(lastKnownPosition, Collections.singleton(new Triple<>(currentTrackingDevice, new TimeRangeImpl(null, null), null)));
}
public MarkConfigurationResponseAnnotationImpl(GPSFix lastKnownPosition,
Iterable<Triple<DeviceIdentifier, TimeRange, GPSFix>> deviceMappings) {
super();
this.lastKnownPosition = lastKnownPosition;
this.deviceMappings = deviceMappings;
}
@Override
public GPSFix getLastKnownPosition() {
return lastKnownPosition;
}
@Override
public Iterable<Triple<DeviceIdentifier, TimeRange, GPSFix>> getDeviceMappings() {
return deviceMappings;
}
}
@@ -0,0 +1,77 @@
package com.sap.sailing.domain.coursetemplate.impl;
import com.sap.sailing.domain.coursetemplate.MarkConfiguration;
import com.sap.sailing.domain.coursetemplate.MarkPairWithConfiguration;
import com.sap.sse.common.impl.NamedImpl;
public class MarkPairWithConfigurationImpl<P>
extends NamedImpl implements MarkPairWithConfiguration<P> {
private static final long serialVersionUID = 159552983009183991L;
private final MarkConfiguration<P> right;
private final MarkConfiguration<P> left;
private final String shortName;
public MarkPairWithConfigurationImpl(String name, MarkConfiguration<P> left, MarkConfiguration<P> right,
String shortName) {
super(name);
this.left = left;
this.right = right;
this.shortName = shortName;
}
@Override
public String getShortName() {
return this.shortName;
}
@Override
public MarkConfiguration<P> getLeft() {
return left;
}
@Override
public MarkConfiguration<P> getRight() {
return right;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getName() == null) ? 0 : getName().hashCode());
result = prime * result + ((left == null) ? 0 : left.hashCode());
result = prime * result + ((right == null) ? 0 : right.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MarkPairWithConfigurationImpl<?> other = (MarkPairWithConfigurationImpl<?>) obj;
if (getName() == null) {
if (other.getName() != null)
return false;
} else if (!getName().equals(other.getName()))
return false;
if (left == null) {
if (other.left != null)
return false;
} else if (!left.equals(other.left))
return false;
if (right == null) {
if (other.right != null)
return false;
} else if (!right.equals(other.right))
return false;
return true;
}
}
@@ -0,0 +1,44 @@
package com.sap.sailing.domain.coursetemplate.impl;
import com.sap.sailing.domain.coursetemplate.CommonMarkProperties;
import com.sap.sailing.domain.coursetemplate.MarkConfigurationVisitor;
import com.sap.sailing.domain.coursetemplate.MarkProperties;
import com.sap.sailing.domain.coursetemplate.MarkPropertiesBasedMarkConfiguration;
import com.sap.sailing.domain.coursetemplate.MarkTemplate;
public class MarkPropertiesBasedMarkConfigurationImpl<P> extends MarkConfigurationImpl<P>
implements MarkPropertiesBasedMarkConfiguration<P> {
private static final long serialVersionUID = -1371019872646970791L;
private final MarkProperties markProperties;
public MarkPropertiesBasedMarkConfigurationImpl(MarkProperties markProperties, MarkTemplate optionalMarkTemplate, P annotation) {
super(optionalMarkTemplate, annotation);
this.markProperties = markProperties;
}
@Override
public <T> T accept(MarkConfigurationVisitor<T, P> visitor) {
return visitor.visit(this);
}
@Override
public String getShortName() {
return markProperties.getShortName();
}
@Override
public String getName() {
return markProperties.getName();
}
@Override
public MarkProperties getOptionalMarkProperties() {
return markProperties;
}
@Override
public CommonMarkProperties getEffectiveProperties() {
return markProperties;
}
}
@@ -0,0 +1,129 @@
package com.sap.sailing.domain.coursetemplate.impl;
import java.util.Collections;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import com.sap.sailing.domain.common.MarkType;
import com.sap.sailing.domain.coursetemplate.CommonMarkProperties;
import com.sap.sailing.domain.coursetemplate.MarkProperties;
import com.sap.sailing.domain.coursetemplate.MarkRole;
import com.sap.sailing.domain.coursetemplate.MarkTemplate;
import com.sap.sailing.domain.coursetemplate.Positioning;
import com.sap.sse.common.Color;
import com.sap.sse.common.TimePoint;
public class MarkPropertiesImpl extends CommonMarkPropertiesImpl implements MarkProperties {
private static final long serialVersionUID = -5588202720707030502L;
private Positioning positioningInformation;
private Iterable<String> tags;
private UUID id;
private final ConcurrentHashMap<MarkTemplate, TimePoint> lastUsedMarkTemplate = new ConcurrentHashMap<>();
private final ConcurrentHashMap<MarkRole, TimePoint> lastUsedMarkRole = new ConcurrentHashMap<>();
public MarkPropertiesImpl(String name, String shortName, Color color, String shape, String pattern, MarkType type) {
this(UUID.randomUUID(), name, shortName, color, shape, pattern, type);
}
public MarkPropertiesImpl(UUID id, String name, String shortName, Color color, String shape, String pattern,
MarkType type) {
super(name, shortName, color, shape, pattern, type);
this.id = id;
}
public MarkPropertiesImpl(UUID id, final CommonMarkProperties commonMarkProperties) {
super(commonMarkProperties);
this.id = id;
}
@Override
public UUID getId() {
return id;
}
@Override
public void setColor(Color color) {
this.color = color;
}
@Override
public void setShape(String shape) {
this.shape = shape;
}
@Override
public void setPattern(String pattern) {
this.pattern = pattern;
}
@Override
public void setType(MarkType type) {
this.type = type;
}
@Override
public void setShortName(String shortName) {
this.shortName = shortName;
}
@Override
public Positioning getPositioningInformation() {
return positioningInformation;
}
@Override
public void setPositioningInformation(Positioning positioningInformation) {
this.positioningInformation = positioningInformation;
}
@Override
public Iterable<String> getTags() {
return this.tags;
}
public void setTags(Iterable<String> tags) {
this.tags = tags;
}
@Override
public Map<MarkTemplate, TimePoint> getLastUsedMarkTemplate() {
return Collections.unmodifiableMap(lastUsedMarkTemplate);
}
private <T> void recordUsageTimePoint(T object, TimePoint usageTimePoint, Map<T, TimePoint> lastUsedMap) {
if (usageTimePoint == null) {
throw new IllegalArgumentException("Usage time point must not be null");
}
if (!lastUsedMap.containsKey(object) || usageTimePoint.after(lastUsedMap.get(object))) {
lastUsedMap.put(object, usageTimePoint);
}
}
@Override
public void addMarkTemplateUsage(MarkTemplate markTemplate, TimePoint usageTimePoint) {
recordUsageTimePoint(markTemplate, usageTimePoint, lastUsedMarkTemplate);
}
@Override
public Map<MarkRole, TimePoint> getLastUsedMarkRole() {
return Collections.unmodifiableMap(lastUsedMarkRole);
}
@Override
public void addMarkRoleUsage(MarkRole markRole, TimePoint usageTimePoint) {
recordUsageTimePoint(markRole, usageTimePoint, lastUsedMarkRole);
}
public void setLastUsedMarkRole(Map<MarkRole, TimePoint> lastUsedRole) {
this.lastUsedMarkRole.clear();
this.lastUsedMarkRole.putAll(lastUsedRole);
}
public void setLastUsedMarkTemplate(Map<MarkTemplate, TimePoint> lastUsedTemplate) {
this.lastUsedMarkTemplate.clear();
this.lastUsedMarkTemplate.putAll(lastUsedTemplate);
}
}
@@ -0,0 +1,21 @@
package com.sap.sailing.domain.coursetemplate.impl;
import java.util.UUID;
import com.sap.sailing.domain.coursetemplate.MarkRole;
import com.sap.sse.common.impl.NamedWithUUIDImpl;
public class MarkRoleImpl extends NamedWithUUIDImpl implements MarkRole {
private static final long serialVersionUID = 3661478108460413196L;
private final String shortName;
public MarkRoleImpl(UUID id, String name, String shortName) {
super(name, id);
this.shortName = shortName;
}
@Override
public String getShortName() {
return this.shortName;
}
}
@@ -0,0 +1,85 @@
package com.sap.sailing.domain.coursetemplate.impl;
import java.util.Arrays;
import com.sap.sailing.domain.coursetemplate.MarkRole;
import com.sap.sailing.domain.coursetemplate.MarkRolePair;
import com.sap.sse.common.impl.NamedImpl;
public class MarkRolePairImpl extends NamedImpl implements MarkRolePair {
private static final long serialVersionUID = -4966456947099578789L;
private final MarkRole left;
private final MarkRole right;
private final String shortName;
public MarkRolePairImpl(String name, String shortName, MarkRole left, MarkRole right) {
super(name);
this.shortName = shortName;
this.left = left;
this.right = right;
}
@Override
public Iterable<MarkRole> getMarkRoles() {
return Arrays.asList(getLeft(), getRight());
}
@Override
public MarkRole getLeft() {
return left;
}
@Override
public MarkRole getRight() {
return right;
}
@Override
public String getShortName() {
return shortName;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getName() == null) ? 0 : getName().hashCode());
result = prime * result + ((getShortName() == null) ? 0 : getShortName().hashCode());
result = prime * result + ((left == null) ? 0 : left.hashCode());
result = prime * result + ((right == null) ? 0 : right.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MarkRolePairImpl other = (MarkRolePairImpl) obj;
if (getName() == null) {
if (other.getName() != null)
return false;
} else if (!getName().equals(other.getName()))
return false;
if (getShortName() == null) {
if (other.getShortName() != null)
return false;
} else if (!getShortName().equals(other.getShortName()))
return false;
if (left == null) {
if (other.left != null)
return false;
} else if (!left.equals(other.left))
return false;
if (right == null) {
if (other.right != null)
return false;
} else if (!right.equals(other.right))
return false;
return true;
}
}
@@ -0,0 +1,50 @@
package com.sap.sailing.domain.coursetemplate.impl;
import com.sap.sailing.domain.coursetemplate.CommonMarkProperties;
import com.sap.sailing.domain.coursetemplate.MarkConfigurationVisitor;
import com.sap.sailing.domain.coursetemplate.MarkProperties;
import com.sap.sailing.domain.coursetemplate.MarkPropertiesBasedMarkConfiguration;
import com.sap.sailing.domain.coursetemplate.MarkTemplate;
import com.sap.sailing.domain.coursetemplate.MarkTemplateBasedMarkConfiguration;
public class MarkTemplateBasedMarkConfigurationImpl<P> extends MarkConfigurationImpl<P>
implements MarkTemplateBasedMarkConfiguration<P> {
private static final long serialVersionUID = -6683111363486434715L;
public MarkTemplateBasedMarkConfigurationImpl(MarkTemplate markTemplate, P annotation) {
super(markTemplate, annotation);
}
@Override
public <T> T accept(MarkConfigurationVisitor<T, P> visitor) {
return visitor.visit(this);
}
@Override
public CommonMarkProperties getEffectiveProperties() {
return getOptionalMarkTemplate();
}
@Override
public String getName() {
return getOptionalMarkTemplate().getName();
}
@Override
public String getShortName() {
return getOptionalMarkTemplate().getShortName();
}
/**
* When configuring a mark from a {@link MarkTemplate} using an instance of this type, no
* {@link MarkProperties} can be specified, and therefore this method always returns {@code null}.
* To provide {@link MarkProperties} that configure a mark and connect it with other properties
* that are based on a {@link MarkTemplate}, use {@link MarkPropertiesBasedMarkConfiguration}
* with a non-{@code null} {@link MarkPropertiesBasedMarkConfiguration#getOptionalMarkTemplate()}
* return value.
*/
@Override
public MarkProperties getOptionalMarkProperties() {
return null;
}
}
@@ -0,0 +1,71 @@
package com.sap.sailing.domain.coursetemplate.impl;
import java.util.UUID;
import com.sap.sailing.domain.common.MarkType;
import com.sap.sailing.domain.coursetemplate.CommonMarkProperties;
import com.sap.sailing.domain.coursetemplate.MarkTemplate;
import com.sap.sse.common.Color;
import com.sap.sse.common.impl.NamedWithUUIDImpl;
public class MarkTemplateImpl extends NamedWithUUIDImpl implements MarkTemplate {
private static final long serialVersionUID = 620307743421809258L;
private final String shortName;
private final Color color;
private final String shape;
private final String pattern;
private final MarkType type;
public MarkTemplateImpl(String name, String shortName, Color color, String shape, String pattern, MarkType type) {
super(name);
this.shortName = shortName;
this.color = color;
this.shape = shape;
this.pattern = pattern;
this.type = type;
}
public MarkTemplateImpl(UUID id, String name, String shortName, Color color, String shape, String pattern, MarkType type) {
super(name, id);
this.shortName = shortName;
this.color = color;
this.shape = shape;
this.pattern = pattern;
this.type = type;
}
public MarkTemplateImpl(UUID id, CommonMarkProperties commonMarkProperties) {
super(commonMarkProperties.getName(), id);
this.shortName = commonMarkProperties.getShortName();
this.color = commonMarkProperties.getColor();
this.shape = commonMarkProperties.getShape();
this.pattern = commonMarkProperties.getPattern();
this.type = commonMarkProperties.getType();
}
@Override
public Color getColor() {
return color;
}
@Override
public String getShape() {
return shape;
}
@Override
public String getPattern() {
return pattern;
}
@Override
public MarkType getType() {
return type;
}
@Override
public String getShortName() {
return shortName;
}
}
@@ -0,0 +1,52 @@
package com.sap.sailing.domain.coursetemplate.impl;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.coursetemplate.CommonMarkProperties;
import com.sap.sailing.domain.coursetemplate.MarkConfigurationVisitor;
import com.sap.sailing.domain.coursetemplate.MarkProperties;
import com.sap.sailing.domain.coursetemplate.MarkTemplate;
import com.sap.sailing.domain.coursetemplate.RegattaMarkConfiguration;
public class RegattaMarkConfigurationImpl<P> extends MarkConfigurationImpl<P> implements RegattaMarkConfiguration<P> {
private static final long serialVersionUID = 329929810377510675L;
private final Mark mark;
private final MarkProperties optionalMarkProperties;
public RegattaMarkConfigurationImpl(Mark mark, P additionalInfo, MarkTemplate optionalMarkTemplate,
MarkProperties optionalMarkProperties) {
super(optionalMarkTemplate, additionalInfo);
this.mark = mark;
this.optionalMarkProperties = optionalMarkProperties;
}
@Override
public <T> T accept(MarkConfigurationVisitor<T, P> visitor) {
return visitor.visit(this);
}
@Override
public String getShortName() {
return mark.getShortName();
}
@Override
public String getName() {
return mark.getName();
}
@Override
public Mark getMark() {
return mark;
}
@Override
public CommonMarkProperties getEffectiveProperties() {
return mark;
}
@Override
public MarkProperties getOptionalMarkProperties() {
return optionalMarkProperties;
}
}
@@ -0,0 +1,39 @@
package com.sap.sailing.domain.coursetemplate.impl;
import com.sap.sailing.domain.coursetemplate.RepeatablePart;
public class RepeatablePartImpl implements RepeatablePart {
private static final long serialVersionUID = -7432656529810302123L;
private final int zeroBasedIndexOfRepeatablePartStart;
private final int zeroBasedIndexOfRepeatablePartEnd;
public RepeatablePartImpl(int zeroBasedIndexOfRepeatablePartStart, int zeroBasedIndexOfRepeatablePartEnd) {
super();
if (zeroBasedIndexOfRepeatablePartStart < 0 || zeroBasedIndexOfRepeatablePartEnd < 0) {
throw new IllegalArgumentException("No negative indices are possible for the repeatable part.");
}
if (zeroBasedIndexOfRepeatablePartStart >= zeroBasedIndexOfRepeatablePartEnd) {
throw new IllegalArgumentException(
"The start of the repeatable part needs to be less than the end such that the repeatable part has at least one element.");
}
this.zeroBasedIndexOfRepeatablePartStart = zeroBasedIndexOfRepeatablePartStart;
this.zeroBasedIndexOfRepeatablePartEnd = zeroBasedIndexOfRepeatablePartEnd;
}
@Override
public int getZeroBasedIndexOfRepeatablePartStart() {
return zeroBasedIndexOfRepeatablePartStart;
}
@Override
public int getZeroBasedIndexOfRepeatablePartEnd() {
return zeroBasedIndexOfRepeatablePartEnd;
}
@Override
public String toString() {
return "["+getZeroBasedIndexOfRepeatablePartStart()+".."+getZeroBasedIndexOfRepeatablePartEnd()+"]";
}
}
@@ -0,0 +1,18 @@
package com.sap.sailing.domain.coursetemplate.impl;
import com.sap.sailing.domain.common.DeviceIdentifier;
import com.sap.sailing.domain.coursetemplate.TrackingDeviceBasedPositioning;
public class TrackingDeviceBasedPositioningImpl implements TrackingDeviceBasedPositioning {
private final DeviceIdentifier deviceIdentifier;
public TrackingDeviceBasedPositioningImpl(DeviceIdentifier deviceIdentifier) {
super();
this.deviceIdentifier = deviceIdentifier;
}
@Override
public DeviceIdentifier getDeviceIdentifier() {
return deviceIdentifier;
}
}
@@ -0,0 +1,33 @@
package com.sap.sailing.domain.coursetemplate.impl;
import com.sap.sailing.domain.common.PassingInstruction;
import com.sap.sailing.domain.coursetemplate.ControlPointTemplate;
import com.sap.sailing.domain.coursetemplate.WaypointTemplate;
public class WaypointTemplateImpl implements WaypointTemplate {
private static final long serialVersionUID = -4708322032365234982L;
private final PassingInstruction passingInstruction;
private final ControlPointTemplate controlPointTemplate;
public WaypointTemplateImpl(ControlPointTemplate controlPoint, PassingInstruction passingInstruction) {
this.passingInstruction = passingInstruction;
this.controlPointTemplate = controlPoint;
}
@Override
public ControlPointTemplate getControlPointTemplate() {
return controlPointTemplate;
}
@Override
public PassingInstruction getPassingInstruction() {
return passingInstruction;
}
@Override
public String toString() {
return "WaypointTemplateImpl [passingInstruction=" + passingInstruction + ", controlPointTemplate="
+ controlPointTemplate + "]";
}
}
@@ -0,0 +1,28 @@
package com.sap.sailing.domain.coursetemplate.impl;
import com.sap.sailing.domain.common.PassingInstruction;
import com.sap.sailing.domain.coursetemplate.ControlPointWithMarkConfiguration;
import com.sap.sailing.domain.coursetemplate.WaypointWithMarkConfiguration;
public class WaypointWithMarkConfigurationImpl<P> implements WaypointWithMarkConfiguration<P> {
private final ControlPointWithMarkConfiguration<P> controlPoint;
private final PassingInstruction passingInstruction;
public WaypointWithMarkConfigurationImpl(ControlPointWithMarkConfiguration<P> controlPoint,
PassingInstruction passingInstruction) {
super();
this.controlPoint = controlPoint;
this.passingInstruction = passingInstruction;
}
@Override
public ControlPointWithMarkConfiguration<P> getControlPoint() {
return controlPoint;
}
@Override
public PassingInstruction getPassingInstruction() {
return passingInstruction;
}
}
@@ -1,7 +1,9 @@
package com.sap.sailing.domain.racelogtracking;
package com.sap.sailing.domain.racelogtracking.impl;
import java.util.UUID;
import com.sap.sailing.domain.racelogtracking.PingDeviceIdentifier;
public class PingDeviceIdentifierImpl implements PingDeviceIdentifier {
private static final long serialVersionUID = -4049961972156611640L;
private final UUID id;
@@ -79,7 +79,8 @@ public interface DomainFactory {
long delayToLiveInMillis, SwissTimingFactory swissTimingFactory, DomainFactory domainFactory,
RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, boolean useInternalMarkPassingAlgorithm, boolean trackWind, boolean correctWindDirectionByMagneticDeclination, String updateURL, String updateUsername, String updatePassword);
ControlPoint getOrCreateControlPoint(String description, Iterable<Serializable> deviceIds, MarkType markType);
ControlPoint getOrCreateControlPoint(String description, Iterable<Serializable> deviceIds, MarkType markType,
String shortNameOfGate);
RaceDefinition createRaceDefinition(Regatta regatta, String swissTimingRaceID, Map<Competitor, Boat> competitorsAndBoats,
List<ControlPoint> courseDefinition, String raceName, String raceIdForRaceDefinition,
Loaded 100 of 415 files, more files were not shown because too many files have changed in this diff. Show more