diff --git a/java/com.sap.sailing.domain.common/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.common/META-INF/MANIFEST.MF
index 366d8cd72b9..b6f50c6f473 100644
--- a/java/com.sap.sailing.domain.common/META-INF/MANIFEST.MF
+++ b/java/com.sap.sailing.domain.common/META-INF/MANIFEST.MF
@@ -17,17 +17,19 @@ Export-Package: com.sap.sailing.domain.common,
com.sap.sailing.domain.common.quadtree,
com.sap.sailing.domain.common.quadtree.impl;x-friends:="com.sap.sailing.domain.test",
com.sap.sailing.domain.common.racelog,
- com.sap.sailing.domain.common.sharding,
com.sap.sailing.domain.common.racelog.tracking,
com.sap.sailing.domain.common.racelog.utils,
com.sap.sailing.domain.common.scalablevalue.impl,
com.sap.sailing.domain.common.security,
com.sap.sailing.domain.common.sensordata,
+ com.sap.sailing.domain.common.sharding,
+ com.sap.sailing.domain.common.tagging,
com.sap.sailing.domain.common.trackfiles,
com.sap.sailing.domain.common.tracking,
com.sap.sailing.domain.common.tracking.impl,
com.sap.sailing.domain.common.windfinder
Require-Bundle: com.sap.sse.common,
com.sap.sse.security.common,
- com.sap.sse.datamining.annotations;bundle-version="1.0.0"
+ com.sap.sse.datamining.annotations;bundle-version="1.0.0",
+ org.json.simple;bundle-version="1.1.0"
Automatic-Module-Name: com.sap.sailing.domain.common
diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/TagDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/TagDTO.java
new file mode 100644
index 00000000000..39dc7489cad
--- /dev/null
+++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/TagDTO.java
@@ -0,0 +1,296 @@
+package com.sap.sailing.domain.common.dto;
+
+import java.io.Serializable;
+
+import com.sap.sse.common.TimePoint;
+
+/**
+ * Tags are used to link input of users in form of "tags" (extended comments) to a specific race. These tags consists of
+ * {@link #tag title}, optional {@link #comment}, optional {@link imageURL image URL} and a {@link #visibleForPublic
+ * visibility status}. They can be set to be visible only for the author or visible for everybody. Private tags will be
+ * stored in the user store, public tags will be stored as event in the racelog. Not every user has write access to the
+ * racelog therefor the default visibility should be private. The {@link com.sap.sailing.server.tagging.TaggingService
+ * TaggingService} is able to perform all CRUD operations on tags and also checks for permissions.
+ *
+ * Tags should be unique for the key consisting of: {@link #tag title}, {@link #comment}, {@link imageURL image URL},
+ * {@link #visibleForPublic visibility status}, {@link #username} and {@link #raceTimepoint}. NOT part of
+ * the key are {@link #createdAt} and {@link #revokedAt}.
+ */
+public class TagDTO implements Serializable {
+
+ private static final long serialVersionUID = 3907411584518452300L;
+
+ public final static String TAG_URL_PARAMETER = "tag";
+
+ private String tag, comment, username, imageURL, resizedImageURL;
+ private boolean visibleForPublic;
+ /**
+ * By default every tag should have set the attributes {@link #raceTimepoint} and {@link #createdAt}.
+ * raceTimepoint: absolute timepoint of race when the tag got created
+ * createdAt: actual absolute creation timepoint of tag
+ *
+ * The racelog does not allow to delete events. Instead these events can only be revoked which is implemented as a
+ * new revocation event. This applies also for tags so it is not possible to "delete" public tags. To mark a tag as
+ * revoked, set the attribute {@link #revokedAt} to a value which does not equal null. So every tag
+ * where {@link #revokedAt} equals null can be seen as valid and NOT revoked.
+ */
+ private TimePoint raceTimepoint, createdAt, revokedAt;
+
+ /**
+ * Required by GWT, do NOT use this constructor! Use the other constructors instead to allow information
+ * transfer.
+ */
+ @Deprecated
+ TagDTO() {
+ }
+
+ /**
+ * Creates tag which is not revoked and therefor valid.
+ *
+ * @param tag
+ * title of tag
+ * @param comment
+ * comment of tag, may be null as comment is optional
+ * @param imageURL
+ * image URL of tag, may be null as image is optional
+ * @param resizedImageURL
+ * resized image URL of tag, may be null as image is optional
+ * @param visibleForPublic
+ * should be true if everybody should see this tag, otherwise false
+ * @param username
+ * name of user who created the tag
+ * @param raceTimepoint
+ * timepoint of race where tag ot created
+ * @param createdAt
+ * timepoint where username created the tag
+ */
+ public TagDTO(String tag, String comment, String imageURL, String resizedImageURL, boolean visibleForPublic,
+ String username, TimePoint raceTimepoint, TimePoint createdAt) {
+ this(tag, comment, imageURL, resizedImageURL, visibleForPublic, username, raceTimepoint, createdAt, null);
+ }
+
+ /**
+ * Creates tag which may be revoked.
+ *
+ * @param tag
+ * title of tag
+ * @param comment
+ * comment of tag, may be null as comment is optional
+ * @param imageURL
+ * image URL of tag, may be null as image is optional
+ * @param resizedImageURL
+ * resized image URL of tag, may be null as image is optional
+ * @param visibleForPublic
+ * should be true if everbidy should see this tag, otherwise false
+ * @param username
+ * name of user who created the tag
+ * @param raceTimepoint
+ * timepoint of race where tag ot created
+ * @param createdAt
+ * timepoint where username created the tag
+ * @param revokedAt
+ * timepoint where tag got revoked, may be null if tag is not revoked
+ */
+ public TagDTO(String tag, String comment, String imageURL, String resizedImageURL, boolean visibleForPublic,
+ String username, TimePoint raceTimepoint, TimePoint createdAt, TimePoint revokedAt) {
+ this.tag = tag;
+ this.comment = comment;
+ this.imageURL = imageURL;
+ this.resizedImageURL = resizedImageURL;
+ this.visibleForPublic = visibleForPublic;
+ this.username = username;
+ this.raceTimepoint = raceTimepoint;
+ this.createdAt = createdAt;
+ this.revokedAt = revokedAt;
+ }
+
+ /**
+ * Returns title of tag.
+ *
+ * @return title of tag
+ */
+ public String getTag() {
+ return tag;
+ }
+
+ /**
+ * Returns optional comment of tag.
+ *
+ * @return comment of tag, may be null
+ */
+ public String getComment() {
+ return comment;
+ }
+
+ /**
+ * Returns optional image URL of tag.
+ *
+ * @return image URLs of tag, may be null
+ */
+ public String getImageURL() {
+ return imageURL;
+ }
+
+ /**
+ * Returns optional image URL of tag.
+ *
+ * @return resized image URL of tag, may be null
+ */
+ public String getResizedImageURL() {
+ return resizedImageURL;
+ }
+
+ /**
+ * Returns visibilty of tag.
+ *
+ * @return true if everybody should see the tag, otherwise false
+ */
+ public boolean isVisibleForPublic() {
+ return visibleForPublic;
+ }
+
+ /**
+ * Returns name of user who created this tag.
+ *
+ * @return username
+ */
+ public String getUsername() {
+ return username;
+ }
+
+ /**
+ * Returns logical timepoint of race where tag was created.
+ *
+ * @return logical timepoint
+ */
+ public TimePoint getRaceTimepoint() {
+ return raceTimepoint;
+ }
+
+ /**
+ * Returns creation timestamp of tag.
+ *
+ * @return creation timestamp
+ */
+ public TimePoint getCreatedAt() {
+ return createdAt;
+ }
+
+ /**
+ * Returns revokaction timestamp if tag is already revoked.
+ *
+ * @return timepoint if tag is already revoked, otherwise null
+ */
+ public TimePoint getRevokedAt() {
+ return revokedAt;
+ }
+
+ /**
+ * Compares attributes {@link #tag}, {@link #comment}, {@link #imageURL}, {@link #resizedImageURL},
+ * {@link #visibleForPublic}, {@link #username} and {@link #raceTimepoint}, but NOT attributes
+ * {@link #createdAt} and {@link #revokedAt}.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ TagDTO other = (TagDTO) obj;
+ if (tag == null) {
+ if (other.tag != null)
+ return false;
+ } else if (!tag.equals(other.tag))
+ return false;
+ if (comment == null) {
+ if (other.comment != null)
+ return false;
+ } else if (!comment.equals(other.comment))
+ return false;
+ if (imageURL == null) {
+ if (other.imageURL != null)
+ return false;
+ } else if (!imageURL.equals(other.imageURL))
+ return false;
+ if (resizedImageURL == null) {
+ if (other.resizedImageURL != null)
+ return false;
+ } else if (!resizedImageURL.equals(other.resizedImageURL))
+ return false;
+ if (isVisibleForPublic() != other.isVisibleForPublic()) {
+ return false;
+ }
+ if (username == null) {
+ if (other.username != null)
+ return false;
+ } else if (!username.equals(other.username)) {
+ return false;
+ }
+ if (raceTimepoint == null) {
+ if (other.raceTimepoint != null)
+ return false;
+ } else if (!raceTimepoint.equals(other.raceTimepoint))
+ return false;
+ return true;
+ }
+
+ /**
+ * Compares {@link TagDTO} with given attributes.
+ *
+ * @return true if all parameters match the key attributes of {@link TagDTO}, otherwise
+ * false
+ */
+ public boolean equals(String tag, String comment, String imageURL, String resizedImageURL, boolean visibleForPublic,
+ String username, TimePoint raceTimepoint) {
+ if (this.comment == null) {
+ if (comment != null)
+ return false;
+ } else if (!this.comment.equals(comment))
+ return false;
+ if (this.imageURL == null) {
+ if (imageURL != null)
+ return false;
+ } else if (!this.imageURL.equals(imageURL))
+ return false;
+ if (this.resizedImageURL == null) {
+ if (resizedImageURL != null)
+ return false;
+ } else if (!this.resizedImageURL.equals(resizedImageURL))
+ return false;
+ if (this.raceTimepoint == null) {
+ if (raceTimepoint != null)
+ return false;
+ } else if (!this.raceTimepoint.equals(raceTimepoint))
+ return false;
+ if (this.tag == null) {
+ if (tag != null)
+ return false;
+ } else if (!this.tag.equals(tag))
+ return false;
+ if (this.username == null) {
+ if (username != null)
+ return false;
+ } else if (!this.username.equals(username)) {
+ return false;
+ }
+ if (isVisibleForPublic() != visibleForPublic) {
+ return false;
+ }
+ if (this.raceTimepoint == null) {
+ if (raceTimepoint != null)
+ return false;
+ } else if (!this.raceTimepoint.equals(raceTimepoint)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "TagDTO [tag=" + tag + ", comment=" + comment + ", imageURL=" + imageURL + ", resizedImageURL="
+ + resizedImageURL + ", visibleForPublic=" + visibleForPublic + ", username=" + username
+ + ", raceTimepoint=" + raceTimepoint + ", createdAt=" + createdAt + ", revokedAt=" + revokedAt + "]";
+ }
+}
\ No newline at end of file
diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tagging/RaceLogNotFoundException.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tagging/RaceLogNotFoundException.java
new file mode 100644
index 00000000000..392b8a5f695
--- /dev/null
+++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tagging/RaceLogNotFoundException.java
@@ -0,0 +1,14 @@
+package com.sap.sailing.domain.common.tagging;
+
+public class RaceLogNotFoundException extends Exception {
+
+ private static final long serialVersionUID = -1935079125197798550L;
+
+ public RaceLogNotFoundException() {
+
+ }
+
+ public RaceLogNotFoundException(String message) {
+ super(message);
+ }
+}
diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tagging/ServiceNotFoundException.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tagging/ServiceNotFoundException.java
new file mode 100644
index 00000000000..3ca538d89d8
--- /dev/null
+++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tagging/ServiceNotFoundException.java
@@ -0,0 +1,14 @@
+package com.sap.sailing.domain.common.tagging;
+
+public class ServiceNotFoundException extends Exception {
+
+ private static final long serialVersionUID = 4354757061181985766L;
+
+ public ServiceNotFoundException() {
+
+ }
+
+ public ServiceNotFoundException(String message) {
+ super(message);
+ }
+}
\ No newline at end of file
diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tagging/TagAlreadyExistsException.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tagging/TagAlreadyExistsException.java
new file mode 100644
index 00000000000..f51e7ceaba3
--- /dev/null
+++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tagging/TagAlreadyExistsException.java
@@ -0,0 +1,14 @@
+package com.sap.sailing.domain.common.tagging;
+
+public class TagAlreadyExistsException extends Exception {
+
+ private static final long serialVersionUID = -3908869597780348971L;
+
+ public TagAlreadyExistsException() {
+
+ }
+
+ public TagAlreadyExistsException(String message) {
+ super(message);
+ }
+}
diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java
index 1c89c0f6538..5c61d99ea84 100644
--- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java
+++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java
@@ -62,6 +62,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogStartOfTrackingEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogStartProcedureChangedEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogSuppressedMarkPassingsEvent;
+import com.sap.sailing.domain.abstractlog.race.RaceLogTagEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogWindFixEvent;
import com.sap.sailing.domain.abstractlog.race.SimpleRaceLogIdentifier;
import com.sap.sailing.domain.abstractlog.race.impl.CompetitorResultImpl;
@@ -84,6 +85,7 @@ import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartOfTrackingEventI
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartProcedureChangedEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartTimeEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogSuppressedMarkPassingsEventImpl;
+import com.sap.sailing.domain.abstractlog.race.impl.RaceLogTagEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogWindFixEventImpl;
import com.sap.sailing.domain.abstractlog.race.impl.SimpleRaceLogIdentifierImpl;
import com.sap.sailing.domain.abstractlog.race.scoring.AdditionalScoringInformationType;
@@ -1591,7 +1593,9 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
} else if (eventClass.equals(RaceLogUseCompetitorsFromRaceLogEvent.class.getSimpleName())) {
resultEvent = loadRaceLogUseCompetitorsFromRaceLogEvent(createdAt, author, logicalTimePoint, id, passId,
competitors, dbObject);
- } else {
+ } else if (eventClass.equals(RaceLogTagEvent.class.getSimpleName())) {
+ resultEvent = loadRaceLogTagEvent(createdAt, author, logicalTimePoint, id, passId, dbObject);
+ }else {
throw new IllegalStateException(String.format("Unknown RaceLogEvent type %s", eventClass));
}
return new Pair<>(resultEvent, dbObjectForUpdate);
@@ -1766,6 +1770,16 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
.valueOf(dbObject.get(FieldNames.RACE_LOG_START_PROCEDURE_TYPE.name()).toString());
return new RaceLogStartProcedureChangedEventImpl(createdAt, logicalTimePoint, author, id, passId, type);
}
+
+ private RaceLogEvent loadRaceLogTagEvent(TimePoint createdAt, AbstractLogEventAuthor author,
+ TimePoint logicalTimePoint, Serializable id, Integer passId,
+ DBObject dbObject) {
+ String tag = (String) dbObject.get(FieldNames.RACE_LOG_TAG.name());
+ String comment = (String) dbObject.get(FieldNames.RACE_LOG_COMMENT.name());
+ String imageUrl = (String) dbObject.get(FieldNames.RACE_LOG_IMAGE_URL.name());
+ String resizedImageURL = (String) dbObject.get(FieldNames.RACE_LOG_RESIZED_IMAGE_URL.name());
+ return new RaceLogTagEventImpl(tag, comment, imageUrl, resizedImageURL, createdAt, logicalTimePoint, author, id, passId);
+ }
private RaceLogEvent loadRaceLogGateLineOpeningTimeEvent(TimePoint createdAt, AbstractLogEventAuthor author,
TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors,
diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java
index b557b018195..929e9bde534 100644
--- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java
+++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java
@@ -105,7 +105,7 @@ public enum FieldNames {
RACE_LOG_REVOKED_EVENT_SHORT_INFO, RACE_LOG_REVOKED_REASON, RACE_LOG_DEVICE_MAPPING_EVENT_ID,
RACE_LOG_CLOSING_TIMEPOINT, TIMEPOINT_OF_FIXED_MARKPASSING, INDEX_OF_PASSED_WAYPOINT,INDEX_OF_FIRST_SUPPRESSED_WAYPOINT, MARK, RACE_LOG_COMPETITOR_ID, RACE_LOG_BOAT_ID,
RACE_LOG_DEPDENDENT_ON_FLEET, RACE_LOG_DEPDENDENT_ON_RACECOLUMN, RACE_LOG_DEPDENDENT_ON_REGATTALIKE,
- RACE_LOG_START_TIME_DIFFERENCE_IN_MS, RACE_LOG_FINISHING_TIME_AS_MILLIS,
+ RACE_LOG_START_TIME_DIFFERENCE_IN_MS, RACE_LOG_FINISHING_TIME_AS_MILLIS, RACE_LOG_TAG, RACE_LOG_COMMENT, RACE_LOG_IMAGE_URL, RACE_LOG_RESIZED_IMAGE_URL,
/** @deprecated Replaced by {@link #REGATTA_LOG_FROM}; auto-migration from race log device mappings to regatta log is implemented */ @Deprecated RACE_LOG_FROM,
/** @deprecated Replaced by {@link #REGATTA_LOG_TO}; auto-migration from race log device mappings to regatta log is implemented */ @Deprecated RACE_LOG_TO,
/** @deprecated Replaced by a regatta log entry with type {@link #REGATTA_LOG_MARK} */ @Deprecated RACE_LOG_MARK,
diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java
index 408e806b9fc..04534b5f7c5 100644
--- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java
+++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java
@@ -46,6 +46,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogStartOfTrackingEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogStartProcedureChangedEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogSuppressedMarkPassingsEvent;
+import com.sap.sailing.domain.abstractlog.race.RaceLogTagEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogWindFixEvent;
import com.sap.sailing.domain.abstractlog.race.scoring.RaceLogAdditionalScoringInformationEvent;
import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDenoteForTrackingEvent;
@@ -1087,6 +1088,24 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogUseCompetitorsFromRaceLogEvent(event));
return result;
}
+
+ public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogTagEvent event) {
+ BasicDBObject result = new BasicDBObject();
+ storeRaceLogIdentifier(raceLogIdentifier, result);
+ result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogTagEvent(event));
+ return result;
+ }
+
+ public DBObject storeRaceLogTagEvent(RaceLogTagEvent event) {
+ DBObject result = new BasicDBObject();
+ storeRaceLogEventProperties(event, result);
+ result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogTagEvent.class.getSimpleName());
+ result.put(FieldNames.RACE_LOG_TAG.name(), event.getTag());
+ result.put(FieldNames.RACE_LOG_COMMENT.name(), event.getComment());
+ result.put(FieldNames.RACE_LOG_IMAGE_URL.name(), event.getImageURL());
+ result.put(FieldNames.RACE_LOG_RESIZED_IMAGE_URL.name(), event.getResizedImageURL());
+ return result;
+ }
public DBObject storeRaceLogUseCompetitorsFromRaceLogEvent(RaceLogUseCompetitorsFromRaceLogEvent event) {
DBObject result = new BasicDBObject();
@@ -1776,5 +1795,4 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
e.printStackTrace();
}
}
-
}
diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogStoreVisitor.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogStoreVisitor.java
index 4ba962744a7..11cb40b051c 100644
--- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogStoreVisitor.java
+++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogStoreVisitor.java
@@ -21,6 +21,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogStartOfTrackingEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogStartProcedureChangedEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogSuppressedMarkPassingsEvent;
+import com.sap.sailing.domain.abstractlog.race.RaceLogTagEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogWindFixEvent;
import com.sap.sailing.domain.abstractlog.race.scoring.RaceLogAdditionalScoringInformationEvent;
import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDenoteForTrackingEvent;
@@ -180,4 +181,10 @@ public class MongoRaceLogStoreVisitor implements RaceLogEventVisitor {
DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event);
mongoObjectFactory.storeRaceLogEventEvent(object);
}
+
+ @Override
+ public void visit(RaceLogTagEvent event) {
+ DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event);
+ mongoObjectFactory.storeRaceLogEventEvent(object);
+ }
}
diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogEventVisitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogEventVisitor.java
index f51bda94195..a26c060ac4c 100644
--- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogEventVisitor.java
+++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogEventVisitor.java
@@ -53,4 +53,6 @@ public interface RaceLogEventVisitor {
public void visit(RaceLogEndOfTrackingEvent event);
public void visit(RaceLogUseCompetitorsFromRaceLogEvent event);
+
+ public void visit(RaceLogTagEvent event);
}
diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogTagEvent.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogTagEvent.java
new file mode 100644
index 00000000000..d77d91aa4c6
--- /dev/null
+++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogTagEvent.java
@@ -0,0 +1,63 @@
+package com.sap.sailing.domain.abstractlog.race;
+
+import com.sap.sailing.domain.abstractlog.Revokable;
+import com.sap.sse.common.TimePoint;
+
+/**
+ * Tags can be added by users to mark an important situation at a race. Tags consist of a title, optional comment and
+ * optional image URL.
+ */
+public interface RaceLogTagEvent extends RaceLogEvent, Revokable {
+
+ /**
+ * Returns title of tag. The title should be short and summarize the tag.
+ *
+ * @return title of tag
+ */
+ String getTag();
+
+ /**
+ * Returns name of user who created the tag. Equals name of {@link #getAuthor() author} of {@link RaceLogEvent}.
+ *
+ * @return username of author
+ */
+ String getUsername();
+
+ /**
+ * Returns optional comment. Comments should add additional information to the title.
+ *
+ * @return comment of tag, may be null as comments are optional
+ */
+ String getComment();
+
+ /**
+ * Returns optional image URL.
+ *
+ * @return image URL of tag, may be null as images are optional
+ */
+ String getImageURL();
+
+ /**
+ * Returns optional resized image URL.
+ *
+ * @return resized image URL of tag, may be null as images are optional and images might not be resized
+ */
+ String getResizedImageURL();
+
+ /**
+ * Marks this {@link RaceLogTagEvent} as revoked. Used to differentiate between revoked and non-revoked
+ * {@link RaceLogTagEvent tag events}.
+ *
+ * @param revokedAt
+ * {@link RaceLogRevokeEvent#getCreatedAt() timepoint of creation} of {@link RaceLogRevokeEvent}
+ */
+ void markAsRevoked(TimePoint revokedAt);
+
+ /**
+ * Returns timepoint of creation of {@link RaceLogRevokeEvent}.
+ *
+ * @return {@link RaceLogRevokeEvent#getCreatedAt() timepoint of creation} of {@link RaceLogRevokeEvent} if
+ * {@link RaceLogTagEvent tag} is {@link #markAsRevoked() revoked}, otherwise null
+ */
+ TimePoint getRevokedAt();
+}
diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/TagFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/TagFinder.java
new file mode 100644
index 00000000000..83c11b04def
--- /dev/null
+++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/TagFinder.java
@@ -0,0 +1,75 @@
+package com.sap.sailing.domain.abstractlog.race.analyzing.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.sap.sailing.domain.abstractlog.race.RaceLog;
+import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
+import com.sap.sailing.domain.abstractlog.race.RaceLogRevokeEvent;
+import com.sap.sailing.domain.abstractlog.race.RaceLogTagEvent;
+
+/**
+ * Scans {@link RaceLog} for {@link RaceLogTagEvent RaceLogTagEvents} and {@link RaceLogRevokeEvent
+ * RaceLogRevokeEvents}.
+ */
+public class TagFinder extends RaceLogAnalyzer> {
+
+ private final RaceLog raceLog;
+ private final boolean onlyUnrevokedEvents;
+
+ /**
+ * Creates instance of {@link TagFinder} which searches the given raceLog for {@link RaceLogTagEvent
+ * RaceLogTagEvents} and their {@link RaceLogRevokeEvent revocations}.
+ *
+ * @param raceLog
+ * racelog to scan for {@link RaceLogTagEvent RaceLogTagEvents}
+ */
+ public TagFinder(RaceLog raceLog) {
+ this(raceLog, false);
+ }
+
+ /**
+ * Creates instance of {@link TagFinder} which searches the given raceLog for {@link RaceLogTagEvent
+ * RaceLogTagEvents} and for their {@link RaceLogRevokeEvent revocations} if onlyUnrevokedEvents is set
+ * to false.
+ *
+ * @param raceLog
+ * racelog to scan for {@link RaceLogTagEvent RaceLogTagEvents}
+ * @param onlyUnrevokedEvents
+ * true to search only for unrevoked event, otherwise false
+ */
+ public TagFinder(RaceLog raceLog, boolean onlyUnrevokedEvents) {
+ super(raceLog);
+ this.raceLog = raceLog;
+ this.onlyUnrevokedEvents = onlyUnrevokedEvents;
+ }
+
+ /**
+ * Scans {@link RaceLog} for {@link RaceLogTagEvent RaceLogTagEvents} and {@link RaceLogRevokeEvent
+ * RaceLogRevokeEvents}. If a {@link RaceLogTagEvent} is revoked, the event will be
+ * {@link RaceLogTagEvent#markAsRevoked() marked as revoked}.
+ */
+ @Override
+ protected List performAnalysis() {
+ Iterable raceLogEvents = onlyUnrevokedEvents ? raceLog.getUnrevokedEvents() : getAllEvents();
+ List result = new ArrayList<>();
+ for (RaceLogEvent raceLogEvent : raceLogEvents) {
+ if (raceLogEvent instanceof RaceLogTagEvent) {
+ // tag event is not revoked (at least until this point of time at scanning race log)
+ result.add((RaceLogTagEvent) raceLogEvent);
+ } else if (raceLogEvent instanceof RaceLogRevokeEvent) {
+ // tag event got revoked => update it in result list or add it to result as revoked tag event
+ RaceLogRevokeEvent revokeEvent = (RaceLogRevokeEvent) raceLogEvent;
+ RaceLogEvent revokedEvent = raceLog.getEventById(revokeEvent.getRevokedEventId());
+ if (revokedEvent != null && revokedEvent instanceof RaceLogTagEvent) {
+ RaceLogTagEvent revokedTagEvent = (RaceLogTagEvent) revokedEvent;
+ int index = result.indexOf(revokedTagEvent);
+ if (index >= 0) {
+ result.get(index).markAsRevoked(revokeEvent.getCreatedAt());
+ }
+ }
+ }
+ }
+ return result;
+ }
+}
diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/AbstractRaceLogChangedVisitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/AbstractRaceLogChangedVisitor.java
index 9662e231f16..89e0c66cb50 100755
--- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/AbstractRaceLogChangedVisitor.java
+++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/AbstractRaceLogChangedVisitor.java
@@ -19,6 +19,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogStartOfTrackingEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogStartProcedureChangedEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogSuppressedMarkPassingsEvent;
+import com.sap.sailing.domain.abstractlog.race.RaceLogTagEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogWindFixEvent;
import com.sap.sailing.domain.abstractlog.race.scoring.RaceLogAdditionalScoringInformationEvent;
import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDenoteForTrackingEvent;
@@ -138,4 +139,9 @@ public abstract class AbstractRaceLogChangedVisitor implements RaceLogEventVisit
public void visit(RaceLogUseCompetitorsFromRaceLogEvent event) {
notifyListenerAboutEventAdded(event);
}
+
+ @Override
+ public void visit(RaceLogTagEvent event) {
+ notifyListenerAboutEventAdded(event);
+ }
}
diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/BaseRaceLogEventVisitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/BaseRaceLogEventVisitor.java
index 8f35eb16aba..dd36294e008 100644
--- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/BaseRaceLogEventVisitor.java
+++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/BaseRaceLogEventVisitor.java
@@ -18,6 +18,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogStartOfTrackingEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogStartProcedureChangedEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogSuppressedMarkPassingsEvent;
+import com.sap.sailing.domain.abstractlog.race.RaceLogTagEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogWindFixEvent;
import com.sap.sailing.domain.abstractlog.race.scoring.RaceLogAdditionalScoringInformationEvent;
import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDenoteForTrackingEvent;
@@ -145,4 +146,8 @@ public abstract class BaseRaceLogEventVisitor implements RaceLogEventVisitor {
}
-}
+ @Override
+ public void visit(RaceLogTagEvent event) {
+ //TODO maybe add overridding visit methods for subclasses of this class D067890
+ }
+}
\ No newline at end of file
diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/RaceLogTagEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/RaceLogTagEventImpl.java
new file mode 100644
index 00000000000..442d3c0f799
--- /dev/null
+++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/RaceLogTagEventImpl.java
@@ -0,0 +1,152 @@
+package com.sap.sailing.domain.abstractlog.race.impl;
+
+import java.io.Serializable;
+
+import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
+import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor;
+import com.sap.sailing.domain.abstractlog.race.RaceLogTagEvent;
+import com.sap.sse.common.TimePoint;
+
+/**
+ * Default implementation of {@link RaceLogTagEvent}.
+ */
+public class RaceLogTagEventImpl extends RaceLogEventImpl implements RaceLogTagEvent {
+
+ private static final long serialVersionUID = 7213518902555323432L;
+
+ private final String tag, comment, username, imageURL, resizedImageURL;
+ private TimePoint revokedAt;
+
+ /**
+ * Creates {@link RaceLogTagEvent} with all required information.
+ */
+ public RaceLogTagEventImpl(String tag, String comment, String imageURL, String resizedImageURL, TimePoint createdAt,
+ TimePoint logicalTimePoint, AbstractLogEventAuthor author, Serializable id, int passId) {
+ super(createdAt, logicalTimePoint, author, id, passId);
+ this.tag = tag;
+ this.comment = comment;
+ this.imageURL = imageURL;
+ this.resizedImageURL = resizedImageURL;
+ username = author.getName();
+ revokedAt = null;
+ }
+
+ /**
+ * Creates {@link RaceLogTagEvent} without required serializable id by generating it.
+ */
+ public RaceLogTagEventImpl(String tag, String comment, String imageURL, String resizedImageURL, TimePoint createdAt,
+ TimePoint logicalTimePoint, AbstractLogEventAuthor author, int passId) {
+ this(tag, comment, imageURL, resizedImageURL, createdAt, logicalTimePoint, author, randId(), passId);
+ }
+
+ /**
+ * Creates {@link RaceLogTagEvent} with minimal required information.
+ */
+ public RaceLogTagEventImpl(String tag, String comment, String imageURL, String resizedImageURL, TimePoint logicalTimePoint,
+ AbstractLogEventAuthor author, int passId) {
+ this(tag, comment, imageURL, resizedImageURL, now(), logicalTimePoint, author, randId(), passId);
+ }
+
+ @Override
+ public String getTag() {
+ return tag;
+ }
+
+ @Override
+ public String getComment() {
+ return comment;
+ }
+
+ @Override
+ public String getUsername() {
+ return username;
+ }
+
+ /**
+ * Only sets revokedAt if tag is not already revoked and given {@link TimePoint} is non-null.
+ */
+ @Override
+ public void markAsRevoked(TimePoint revokedAt) {
+ if (this.revokedAt == null && revokedAt != null) {
+ this.revokedAt = revokedAt;
+ }
+ }
+
+ @Override
+ public TimePoint getRevokedAt() {
+ return revokedAt;
+ }
+
+ @Override
+ public void accept(RaceLogEventVisitor visitor) {
+ visitor.visit(this);
+ }
+
+ @Override
+ public String getShortInfo() {
+ return "tag=" + tag + ", comment=" + comment;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ RaceLogTagEventImpl other = (RaceLogTagEventImpl) obj;
+ if (comment == null) {
+ if (other.comment != null)
+ return false;
+ } else if (!comment.equals(other.comment))
+ return false;
+ if (imageURL == null) {
+ if (other.imageURL != null)
+ return false;
+ } else if (!imageURL.equals(other.imageURL))
+ return false;
+ if (resizedImageURL == null) {
+ if (other.resizedImageURL != null)
+ return false;
+ } else if (!resizedImageURL.equals(other.resizedImageURL))
+ return false;
+ if (tag == null) {
+ if (other.tag != null)
+ return false;
+ } else if (!tag.equals(other.tag))
+ return false;
+ if (username == null) {
+ if (other.username != null)
+ return false;
+ } else if (!username.equals(other.username))
+ return false;
+ if (getLogicalTimePoint() == null) {
+ if (other.getLogicalTimePoint() != null)
+ return false;
+ } else if (!getLogicalTimePoint().equals(other.getLogicalTimePoint()))
+ return false;
+ if (getCreatedAt() == null) {
+ if (other.getCreatedAt() != null)
+ return false;
+ } else if (!getCreatedAt().equals(other.getCreatedAt()))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "RaceLogTagEvent [tag=" + tag + ", comment=" + comment + ", imageURL=" + imageURL + ", resizedImageURL=" + resizedImageURL + ", username="
+ + username + ", revokedAt=" + revokedAt + "]";
+ }
+
+ @Override
+ public String getImageURL() {
+ return imageURL;
+ }
+
+ @Override
+ public String getResizedImageURL() {
+ return resizedImageURL;
+ }
+}
\ No newline at end of file
diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/RaceState.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/RaceState.java
index 6df0c3f9ca0..710cafe3700 100644
--- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/RaceState.java
+++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/RaceState.java
@@ -194,7 +194,7 @@ public interface RaceState extends ReadonlyRaceState {
* an already existing one.
*/
void setAdditionalScoringInformationEnabled(TimePoint creationTimePoint, boolean enable, AdditionalScoringInformationType informationType);
-
+
boolean isAdditionalScoringInformationEnabled(AdditionalScoringInformationType informationType);
/**
diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/RaceStateChangedListener.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/RaceStateChangedListener.java
index 81cd4038a1f..d2b3b276790 100644
--- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/RaceStateChangedListener.java
+++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/RaceStateChangedListener.java
@@ -17,5 +17,6 @@ public interface RaceStateChangedListener {
void onFinishingPositionsConfirmed(ReadonlyRaceState state);
void onCourseDesignChanged(ReadonlyRaceState state);
void onWindFixChanged(ReadonlyRaceState state);
+ void onTagEventsChanged(ReadonlyRaceState state);
}
diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/ReadonlyRaceState.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/ReadonlyRaceState.java
index 3b1649bfd03..b2dc8b0e980 100644
--- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/ReadonlyRaceState.java
+++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/ReadonlyRaceState.java
@@ -4,6 +4,7 @@ import com.sap.sailing.domain.abstractlog.race.CompetitorResults;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent;
+import com.sap.sailing.domain.abstractlog.race.RaceLogTagEvent;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogAnalyzer;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.StartTimeFinderResult;
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogEventComparator;
@@ -147,4 +148,8 @@ public interface ReadonlyRaceState extends RaceStateEventProcessor {
*/
Wind getWindFix();
+ /**
+ * Returns an iterable of tag events which may be empty but never null.
+ */
+ Iterable getTagEvents();
}
diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/BaseRaceStateChangedListener.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/BaseRaceStateChangedListener.java
index cac2cac05f8..5d7db3578c0 100644
--- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/BaseRaceStateChangedListener.java
+++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/BaseRaceStateChangedListener.java
@@ -63,4 +63,9 @@ public abstract class BaseRaceStateChangedListener implements RaceStateChangedLi
}
+ @Override
+ public void onTagEventsChanged(ReadonlyRaceState state) {
+
+ }
+
}
diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateChangedListeners.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateChangedListeners.java
index 5b51541827c..c03f3845e01 100644
--- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateChangedListeners.java
+++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateChangedListeners.java
@@ -111,4 +111,11 @@ public class RaceStateChangedListeners extends HashSet
listener.onWindFixChanged(state);
}
}
+
+ @Override
+ public void onTagEventsChanged(ReadonlyRaceState state) {
+ for (RaceStateChangedListener listener : getWorkingCopyOfListeners()) {
+ listener.onTagEventsChanged(state);
+ }
+ }
}
diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateImpl.java
index 7d09c0c9ad5..5fee951c675 100644
--- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateImpl.java
+++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateImpl.java
@@ -242,5 +242,4 @@ public class RaceStateImpl extends ReadonlyRaceStateImpl implements RaceState {
public void forceUpdate() {
super.update();
}
-
-}
+}
\ No newline at end of file
diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/ReadonlyRaceStateImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/ReadonlyRaceStateImpl.java
index accf15e0227..f73ac581202 100644
--- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/ReadonlyRaceStateImpl.java
+++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/ReadonlyRaceStateImpl.java
@@ -13,6 +13,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogDependentStartTimeEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogPassChangeEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent;
+import com.sap.sailing.domain.abstractlog.race.RaceLogTagEvent;
import com.sap.sailing.domain.abstractlog.race.SimpleRaceLogIdentifier;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.ConfirmedFinishPositioningListFinder;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.FinishPositioningListFinder;
@@ -27,6 +28,7 @@ import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceStatusAnalyzer
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RacingProcedureTypeAnalyzer;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.StartTimeFinder;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.StartTimeFinderResult;
+import com.sap.sailing.domain.abstractlog.race.analyzing.impl.TagFinder;
import com.sap.sailing.domain.abstractlog.race.impl.WeakRaceLogChangedVisitor;
import com.sap.sailing.domain.abstractlog.race.state.RaceStateChangedListener;
import com.sap.sailing.domain.abstractlog.race.state.RaceStateEvent;
@@ -167,6 +169,8 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
private final LastPublishedCourseDesignFinder courseDesignerAnalyzer;
private final LastWindFixFinder lastWindFixAnalyzer;
+
+ private final TagFinder tagAnalyzer;
/**
* The cached racing procedure type. If no racing procedure type specification is found in the underlying race log,
@@ -197,6 +201,8 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
*/
private Iterable timePointsWhenRaceStatusMayChange;
+ private Iterable cachedTagEvents;
+
private int cachedPassId;
private StartTimeFinderResult cachedStartTimeFinderResult;
private TimePoint cachedFinishingTime;
@@ -246,6 +252,7 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
this.confirmedFinishPositioningListAnalyzer = new ConfirmedFinishPositioningListFinder(raceLog);
this.courseDesignerAnalyzer = new LastPublishedCourseDesignFinder(raceLog, /* onlyCoursesWithValidWaypointList */ false);
this.lastWindFixAnalyzer = new LastWindFixFinder(raceLog);
+ this.tagAnalyzer = new TagFinder(raceLog);
this.raceStateToObserveListener = new BaseRaceStateChangedListener() {
@Override
@@ -420,6 +427,11 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
return cachedWindFix;
}
+ @Override
+ public Iterable getTagEvents() {
+ return cachedTagEvents;
+ }
+
@Override
public RegattaConfiguration getConfiguration() {
return procedureFactory.getConfiguration();
@@ -562,6 +574,11 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
cachedWindFix = windFix;
changedListeners.onWindFixChanged(this);
}
+ Iterable tagEvents = tagAnalyzer.analyze();
+ if (!Util.equalsWithNull(cachedTagEvents, tagEvents)) {
+ cachedTagEvents = tagEvents;
+ changedListeners.onTagEventsChanged(this);
+ }
}
/**
diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnRaceLogReplicator.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnRaceLogReplicator.java
index 26c2a045b4c..82427674879 100644
--- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnRaceLogReplicator.java
+++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnRaceLogReplicator.java
@@ -21,6 +21,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogStartOfTrackingEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogStartProcedureChangedEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogSuppressedMarkPassingsEvent;
+import com.sap.sailing.domain.abstractlog.race.RaceLogTagEvent;
import com.sap.sailing.domain.abstractlog.race.RaceLogWindFixEvent;
import com.sap.sailing.domain.abstractlog.race.scoring.RaceLogAdditionalScoringInformationEvent;
import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDenoteForTrackingEvent;
@@ -162,4 +163,9 @@ public class RaceColumnRaceLogReplicator implements RaceLogEventVisitor, Seriali
public void visit(RaceLogUseCompetitorsFromRaceLogEvent event) {
notifyOnAdd(event);
}
+
+ @Override
+ public void visit(RaceLogTagEvent event) {
+ notifyOnAdd(event);
+ }
}
diff --git a/java/com.sap.sailing.gwt.ui/.settings/org.eclipse.core.resources.prefs b/java/com.sap.sailing.gwt.ui/.settings/org.eclipse.core.resources.prefs
index 7c2cbcf679b..1a1b620212e 100755
--- a/java/com.sap.sailing.gwt.ui/.settings/org.eclipse.core.resources.prefs
+++ b/java/com.sap.sailing.gwt.ui/.settings/org.eclipse.core.resources.prefs
@@ -18,5 +18,6 @@ encoding//src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properti
encoding//src/main/java/com/sap/sailing/gwt/ui/client/media/MediaPlayerManagerComponent.java=utf-8
encoding//src/main/java/com/sap/sailing/gwt/ui/leaderboard/CompetitorFilterPanel.java=utf-8
encoding//src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java=utf-8
+encoding//src/main/java/com/sap/sailing/gwt/ui/leaderboard/TagFilterPanel.java=utf-8
encoding//src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java=utf-8
encoding//src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java=utf-8
diff --git a/java/com.sap.sailing.gwt.ui/RaceBoard.css b/java/com.sap.sailing.gwt.ui/RaceBoard.css
index 38eecd7a2b7..7ee5fde83db 100755
--- a/java/com.sap.sailing.gwt.ui/RaceBoard.css
+++ b/java/com.sap.sailing.gwt.ui/RaceBoard.css
@@ -594,7 +594,9 @@ img.openColumn {
background-position: 5% 50%, 95% 50%
}
-.gwt-SplitLayoutPanel-EastToggleButton-Open-leaderboard {
+.gwt-SplitLayoutPanel-EastToggleButton-Open-leaderboard,
+.gwt-SplitLayoutPanel-WestToggleButton-Open-leaderboard
+ {
background-color: white;
background-image:
url(images/viewicons/@1x/img/SAP_RV_Leaderboard_GHOSTED.png),
@@ -604,7 +606,8 @@ img.openColumn {
background-position: 5px center, calc(100% - 6px) center;
}
-.gwt-SplitLayoutPanel-EastToggleButton-Closed-leaderboard {
+.gwt-SplitLayoutPanel-EastToggleButton-Closed-leaderboard,
+.gwt-SplitLayoutPanel-WestToggleButton-Closed-leaderboard {
background-color: white;
background-image:
url(images/viewicons/@1x/img/SAP_RV_Leaderboard_INACTIVE.png),
@@ -614,6 +617,26 @@ img.openColumn {
background-position: 5px center, calc(100% - 6px) center;
}
+.gwt-SplitLayoutPanel-WestToggleButton-Open-tags {
+ background-color: white;
+ background-image:
+ url(images/viewicons/@1x/img/SAP_RV_Map_GHOSTED.png),
+ url(images/arrows_bottom_black_small.png);
+ background-repeat: no-repeat, no-repeat;
+ background-position: 4% 50%, 96% 50%;
+ background-position: 5px center, calc(100% - 6px) center;
+}
+
+.gwt-SplitLayoutPanel-WestToggleButton-Closed-tags {
+ background-color: white;
+ background-image:
+ url(images/viewicons/@1x/img/SAP_RV_Map_INACTIVE.png),
+ url(images/arrows_top_black_small.png);
+ background-repeat: no-repeat, no-repeat;
+ background-position: 4% 50%, 96% 50%;
+ background-position: 5px center, calc(100% - 6px) center;
+}
+
.gwt-SplitLayoutPanel-NorthSouthToggleButton-Closed-media,
.gwt-SplitLayoutPanel-NorthSouthToggleButton-Open-media {
background-color: white;
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html
index be534fbc260..646e110cfef 100755
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html
@@ -10,6 +10,15 @@
across a distributed server and event landscape.
+
September 2018
+
+
In RaceBoard.html is a new feature available which allows users to create tags consisting of a title
+ and optional comment and/or image per race. These tags can be saved visible only for the creating user or for everbody
+ (requiring special permissions) and can be used to mark interesting situations during a race. Tag buttons allow users to
+ save frequently used tags as a preset which can be used quckily and do not require repeating input of the same tag.
+ Those tags and their comments are also used in order to find the containing leaderboard via the global search.
+
+
August 2018
The windstreamlets are now colored dynamically according to the wind fields maximum and minimum wind speed.
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/raceboard/RaceBoardPerspectiveOwnSettings.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/raceboard/RaceBoardPerspectiveOwnSettings.java
index e9480181516..c63f89957ae 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/raceboard/RaceBoardPerspectiveOwnSettings.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/raceboard/RaceBoardPerspectiveOwnSettings.java
@@ -1,5 +1,6 @@
package com.sap.sailing.gwt.settings.client.raceboard;
+import com.sap.sailing.domain.common.dto.TagDTO;
import com.sap.sailing.gwt.settings.client.settingtypes.DurationSetting;
import com.sap.sse.common.Duration;
import com.sap.sse.common.impl.MillisecondsDurationImpl;
@@ -23,6 +24,7 @@ public class RaceBoardPerspectiveOwnSettings extends AbstractGenericSerializable
private transient StringSetting activeCompetitorsFilterSetName;
private transient BooleanSetting canReplayDuringLiveRaces;
private transient DurationSetting initialDurationAfterRaceStartInReplay;
+ private transient StringSetting jumpToTag;
public static final String PARAM_VIEW_MODE = "viewMode";
public static final String PARAM_VIEW_SHOW_LEADERBOARD = "viewShowLeaderboard";
@@ -33,6 +35,7 @@ public class RaceBoardPerspectiveOwnSettings extends AbstractGenericSerializable
public static final String PARAM_VIEW_CHART_SUPPORT_ENABLED = "viewChartSupportEnabled";
public static final String PARAM_CAN_REPLAY_DURING_LIVE_RACES = "canReplayDuringLiveRaces";
public static final String PARAM_TIME_AFTER_RACE_START_AS_HOURS_COLON_MILLIS_COLON_SECONDS = "t";
+ public static final String PARAM_JUMP_TO_TAG = TagDTO.TAG_URL_PARAMETER;
public RaceBoardPerspectiveOwnSettings() {
}
@@ -52,11 +55,12 @@ public class RaceBoardPerspectiveOwnSettings extends AbstractGenericSerializable
this.activeCompetitorsFilterSetName = new StringSetting("activeCompetitorsFilterSetName", this, null);
this.canReplayDuringLiveRaces = new BooleanSetting("canReplayDuringLiveRaces", this, false);
this.initialDurationAfterRaceStartInReplay = new DurationSetting(PARAM_TIME_AFTER_RACE_START_AS_HOURS_COLON_MILLIS_COLON_SECONDS, this, null);
+ this.jumpToTag = new StringSetting(PARAM_JUMP_TO_TAG, this, null);
}
public RaceBoardPerspectiveOwnSettings(String activeCompetitorsFilterSetName, Boolean showLeaderboard,
Boolean showWindChart, Boolean showCompetitorsChart, Boolean canReplayDuringLiveRaces,
- Duration initialDurationAfterRaceStartInReplay) {
+ Duration initialDurationAfterRaceStartInReplay, String jumpToTag) {
this.activeCompetitorsFilterSetName.setValue(activeCompetitorsFilterSetName);
this.showLeaderboard.setValue(showLeaderboard);
this.showWindChart.setValue(showWindChart);
@@ -84,10 +88,14 @@ public class RaceBoardPerspectiveOwnSettings extends AbstractGenericSerializable
public boolean isCanReplayDuringLiveRaces() {
return canReplayDuringLiveRaces.getValue();
}
+
+ public String getJumpToTag() {
+ return jumpToTag.getValue();
+ }
public static RaceBoardPerspectiveOwnSettings readSettingsFromURL(boolean defaultForViewShowLeaderboard,
boolean defaultForViewShowWindchart, boolean defaultForViewShowCompetitorsChart,
- String defaultForViewCompetitorFilter, boolean defaultForCanReplayDuringLiveRaces) {
+ String defaultForViewCompetitorFilter, boolean defaultForCanReplayDuringLiveRaces, String defaultForJumpToTag) {
final boolean showLeaderboard = GwtHttpRequestUtils.getBooleanParameter(PARAM_VIEW_SHOW_LEADERBOARD, defaultForViewShowLeaderboard /* default */);
final boolean showWindChart = GwtHttpRequestUtils.getBooleanParameter(PARAM_VIEW_SHOW_WINDCHART, defaultForViewShowWindchart /* default */);
final boolean showCompetitorsChart = GwtHttpRequestUtils.getBooleanParameter(PARAM_VIEW_SHOW_COMPETITORSCHART, defaultForViewShowCompetitorsChart /* default */);
@@ -95,8 +103,9 @@ public class RaceBoardPerspectiveOwnSettings extends AbstractGenericSerializable
final boolean canReplayWhileLiveIsPossible = GwtHttpRequestUtils.getBooleanParameter(PARAM_CAN_REPLAY_DURING_LIVE_RACES, defaultForCanReplayDuringLiveRaces /* default */);
final Duration initialDurationAfterRaceStartInReplay = parseDuration(GwtHttpRequestUtils.getStringParameter(
PARAM_TIME_AFTER_RACE_START_AS_HOURS_COLON_MILLIS_COLON_SECONDS, null /* default */));
+ final String jumpToTag = GwtHttpRequestUtils.getStringParameter(PARAM_JUMP_TO_TAG, defaultForJumpToTag /* default */);
return new RaceBoardPerspectiveOwnSettings(activeCompetitorsFilterSetName, showLeaderboard, showWindChart,
- showCompetitorsChart, canReplayWhileLiveIsPossible, initialDurationAfterRaceStartInReplay);
+ showCompetitorsChart, canReplayWhileLiveIsPossible, initialDurationAfterRaceStartInReplay, jumpToTag);
}
public Duration getInitialDurationAfterRaceStartInReplay() {
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetRaceTimesInfoAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetRaceTimesInfoAction.java
index bb23803f5f8..f7053f250e0 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetRaceTimesInfoAction.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetRaceTimesInfoAction.java
@@ -2,24 +2,36 @@ package com.sap.sailing.gwt.ui.actions;
import java.util.Collection;
import java.util.List;
+import java.util.Map;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
+import com.sap.sse.common.TimePoint;
import com.sap.sse.gwt.client.async.AsyncAction;
public class GetRaceTimesInfoAction implements AsyncAction> {
private final SailingServiceAsync sailingService;
private final Collection raceIdentifiers;
+ private final Map latestReceivedTagTimes;
public GetRaceTimesInfoAction(SailingServiceAsync sailingService, Collection raceIdentifiers) {
+ this(sailingService, raceIdentifiers, null);
+ }
+
+ public GetRaceTimesInfoAction(SailingServiceAsync sailingService, Collection raceIdentifiers, Map latestReceivedTagTimes) {
this.sailingService = sailingService;
this.raceIdentifiers = raceIdentifiers;
+ this.latestReceivedTagTimes = latestReceivedTagTimes;
}
@Override
public void execute(AsyncCallback> callback) {
- sailingService.getRaceTimesInfos(raceIdentifiers, callback);
+ if (latestReceivedTagTimes == null) {
+ sailingService.getRaceTimesInfos(raceIdentifiers, callback);
+ } else {
+ sailingService.getRaceTimesInfosIncludingTags(raceIdentifiers, latestReceivedTagTimes, callback);
+ }
}
}
\ No newline at end of file
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimesInfoProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimesInfoProvider.java
index 1bbc5c7302f..392594e2662 100644
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimesInfoProvider.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimesInfoProvider.java
@@ -19,6 +19,7 @@ import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.gwt.ui.actions.GetRaceTimesInfoAction;
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
+import com.sap.sse.common.TimePoint;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
@@ -34,6 +35,29 @@ public class RaceTimesInfoProvider {
private final HashMap raceTimesInfos;
private final Set listeners;
private boolean terminated = false;
+
+ /**
+ * If set to true, {@link com.sap.sailing.domain.common.dto.TagDTO tags} will also be requested from server by this
+ * {@link RaceTimesInfoProvider}. This is only a temporarily workaround to save a new repeating request which is
+ * only used for tags. This attribute will be set by {@link com.sap.sailing.gwt.ui.raceboard.tagging.TaggingPanel
+ * TaggingPanel} by calling methods {@link #enableTagRequests()} and {@link #disableTagRequests()} and should not
+ * affect any data of the other attributes of {@link RaceTimesInfoDTO}.
+ */
+ // TODO: remove this workaround as soon as bug 4736 is resolved
+ private boolean requestTags = false;
+ /**
+ * Used to save timepoints of the client for differential transmission of
+ * {@link com.sap.sailing.domain.common.dto.TagDTO tags} from the server. The {@link RaceTimesInfoProvider} will
+ * only request {@link com.sap.sailing.domain.common.dto.TagDTO tags} since the saved timepoint of the requested
+ * race. The saved timepoint does equal the latest {@link com.sap.sailing.domain.common.dto.TagDTO#createdAt
+ * createdAt} or {@link com.sap.sailing.domain.common.dto.TagDTO#revokedAt revokedAt} timepoint of all already
+ * received tags of the current client. This allows the server to filter all tags only for updates for this specific
+ * client and therefor only updates will be transmitted over network. The saved timepoint can be null
+ * or 0 to request all tags from server.
+ * Timepoints can be updated via method call {@link #setLatestReceivedTagTime(RegattaAndRaceIdentifier, TimePoint)}
+ * and should be updated everytime the client receives a change of the tags to use minimal network bandwidth.
+ */
+ private Map latestReceivedTagTimes;
/**
* The raceIdentifiers has to be non-null, but can be empty.
@@ -47,6 +71,7 @@ public class RaceTimesInfoProvider {
this.requestIntervalInMillis = requestIntervalInMillis;
raceTimesInfos = new HashMap();
listeners = new HashSet();
+ latestReceivedTagTimes = new HashMap();
RepeatingCommand command = new RepeatingCommand() {
@Override
public boolean execute() {
@@ -82,7 +107,7 @@ public class RaceTimesInfoProvider {
raceIdentifiers.add(raceIdentifier);
if (forceTimesInfoRequest) {
final long clientTimeWhenRequestWasSent = System.currentTimeMillis();
- sailingService.getRaceTimesInfo(raceIdentifier, new AsyncCallback() {
+ AsyncCallback callback = new AsyncCallback() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Error trying to obtain the time infos for race "
@@ -97,14 +122,25 @@ public class RaceTimesInfoProvider {
notifyListeners(clientTimeWhenRequestWasSent, raceTimesInfo.currentServerTime, clientTimeWhenResponseWasReceived);
}
}
- });
+ };
+ if (requestTags) {
+ sailingService.getRaceTimesInfoIncludingTags(raceIdentifier, latestReceivedTagTimes.get(raceIdentifier), callback);
+ } else {
+ sailingService.getRaceTimesInfo(raceIdentifier, callback);
+ }
}
}
private void readTimesInfos() {
if (!raceIdentifiers.isEmpty()) {
final long clientTimeWhenRequestWasSent = System.currentTimeMillis();
- GetRaceTimesInfoAction getRaceTimesInfoAction = new GetRaceTimesInfoAction(sailingService, raceIdentifiers);
+ GetRaceTimesInfoAction getRaceTimesInfoAction;
+ if (requestTags) {
+ getRaceTimesInfoAction = new GetRaceTimesInfoAction(sailingService, raceIdentifiers, latestReceivedTagTimes);
+ } else {
+ getRaceTimesInfoAction = new GetRaceTimesInfoAction(sailingService, raceIdentifiers);
+ }
+
asyncActionsExecutor.execute(getRaceTimesInfoAction, new AsyncCallback>() {
@Override
public void onFailure(Throwable caught) {
@@ -226,4 +262,48 @@ public class RaceTimesInfoProvider {
raceTimesInfos.clear();
}
+ /**
+ * Returns the saved timepoint for the given raceIdentifier which is used as known point of time when
+ * data is requested from server and {@link #requestTags} is set to true.
+ *
+ * @param raceIdentifier
+ * identifies race
+ * @return timepoint of latest received tag, may be null when no timepoint is saved
+ */
+ public TimePoint getLatestReceivedTagTime(RegattaAndRaceIdentifier raceIdentifier) {
+ return latestReceivedTagTimes.get(raceIdentifier);
+ }
+
+ /**
+ * Sets the timepoint to request {@link com.sap.sailing.domain.common.dto.TagDTO tags} from when request is send to
+ * {@link SailingService}. latestReceivedTagTime may be null or 0 to request
+ * all tags.
+ *
+ * @param raceIdentifier
+ * identifies race
+ * @param latestReceivedTagTime
+ * timepoint to request {@link com.sap.sailing.domain.common.dto.TagDTO tags} from. Should be
+ * {@link com.sap.sailing.domain.common.dto.TagDTO#createdAt createdAt} or
+ * {@link com.sap.sailing.domain.common.dto.TagDTO#revokedAt revokedAt} timepoint of tags of the latests
+ * known tag to optimize network bandwidth.
+ */
+ public void setLatestReceivedTagTime(RegattaAndRaceIdentifier raceIdentifier, TimePoint latestReceivedTagTime) {
+ latestReceivedTagTimes.put(raceIdentifier, latestReceivedTagTime);
+ }
+
+ /**
+ * Enables the repeating request to receive also {@link com.sap.sailing.domain.common.dto.TagDTO tags} from
+ * {@link SailingService}.
+ */
+ public void enableTagRequests() {
+ requestTags = true;
+ }
+
+ /**
+ * Disables the repeating request to receive {@link com.sap.sailing.domain.common.dto.TagDTO tags} from
+ * {@link SailingService}.
+ */
+ public void disableTagRequests() {
+ requestTags = false;
+ }
}
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java
index 64ff5065f37..c09d02b9c9c 100755
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java
@@ -10,6 +10,9 @@ import java.util.UUID;
import com.google.gwt.user.client.rpc.RemoteService;
import com.sap.sailing.domain.abstractlog.Revokable;
+import com.sap.sailing.domain.abstractlog.race.RaceLog;
+import com.sap.sailing.domain.abstractlog.race.RaceLogTagEvent;
+import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState;
import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDenoteForTrackingEvent;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.common.CompetitorDescriptor;
@@ -46,6 +49,7 @@ import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.domain.common.dto.RaceColumnInSeriesDTO;
import com.sap.sailing.domain.common.dto.RaceDTO;
import com.sap.sailing.domain.common.dto.RegattaCreationParametersDTO;
+import com.sap.sailing.domain.common.dto.TagDTO;
import com.sap.sailing.domain.common.racelog.RacingProcedureType;
import com.sap.sailing.domain.common.racelog.tracking.CompetitorRegistrationOnRaceLogDisabledException;
import com.sap.sailing.domain.common.racelog.tracking.DoesNotHaveRegattaLogException;
@@ -126,6 +130,8 @@ import com.sap.sse.gwt.client.media.ImageDTO;
import com.sap.sse.gwt.client.media.ImageResizingTaskDTO;
import com.sap.sse.gwt.client.media.VideoDTO;
import com.sap.sse.gwt.client.replication.RemoteReplicationService;
+import com.sap.sse.security.UserStore;
+import com.sap.sse.security.ui.shared.SuccessInfo;
/**
* The client side stub for the RPC service. Usually, when a null date is passed to
@@ -193,9 +199,25 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS
boolean extrapolate) throws NoWindException;
RaceTimesInfoDTO getRaceTimesInfo(RegattaAndRaceIdentifier raceIdentifier);
-
+
+ /**
+ * Returns {@link RaceTimesInfoDTO race times info} for specified race (raceIdentifier) including
+ * {@link RaceLogTagEvent tag events} since received timestamp (searchSince). Loads tags from
+ * {@link ReadonlyRaceState cache} instead of scanning the whole {@link RaceLog} every request.
+ */
+ RaceTimesInfoDTO getRaceTimesInfoIncludingTags(RegattaAndRaceIdentifier raceIdentifier, TimePoint searchSince);
+
List getRaceTimesInfos(Collection raceIdentifiers);
-
+
+ /**
+ * Collects besides {@link RaceTimesInfoDTO race times infos} public {@link RaceLogTagEvent tag events} from
+ * {@link ReadonlyRaceState cache} and compares the createdAt timepoint to the received
+ * searchSince timepoint. Returns {@link RaceTimesInfoDTO race times infos} including
+ * {@link RaceLogTagEvent public tag events} since the latest client-side received tag.
+ */
+ List getRaceTimesInfosIncludingTags(Collection raceIdentifiers,
+ Map searchSinceMap);
+
CoursePositionsDTO getCoursePositions(RegattaAndRaceIdentifier raceIdentifier, Date date);
RaceCourseDTO getRaceCourse(RegattaAndRaceIdentifier raceIdentifier, Date date);
@@ -552,7 +574,117 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS
* @throws NotFoundException
*/
void addCourseDefinitionToRaceLog(String leaderboardName, String raceColumnName, String fleetName, List> course) throws NotFoundException;
-
+
+ /**
+ * Adds public tag as {@link RaceLogTagEvent} to {@link RaceLog} and private tag to
+ * {@link com.sap.sse.security.UserStore UserStore}.
+ *
+ * @param leaderboardName
+ * required to identify {@link RaceLog}, must NOT be null
+ * @param raceColumnName
+ * required to identify {@link RaceLog}, must NOT be null
+ * @param fleetName
+ * required to identify {@link RaceLog}, must NOT be null
+ * @param tag
+ * title of tag, must NOT be null
+ * @param comment
+ * optional comment of tag
+ * @param imageURLs
+ * optional image URLs of tag
+ * @param visibleForPublic
+ * when set to true tag will be saved as public tag (visible for every user), when set to
+ * false tag will be saved as private tag (visible only for creator)
+ * @param raceTimepoint
+ * timepoint in race where user created tag, must NOT be null
+ * @return successful {@link SuccessInfo} if tag was added successfully, otherwise
+ * non-successful {@link SuccessInfo}
+ */
+ SuccessInfo addTag(String leaderboardName, String raceColumnName, String fleetName, String tag, String comment,
+ String imageURL, String resizedImageURL, boolean visibleForPublic, TimePoint raceTimepoint);
+
+ /**
+ * Removes public {@link TagDTO tag} from {@link RaceLog} and private {@link TagDTO tag} from {@link UserStore}.
+ *
+ * @param leaderboardName
+ * required to identify {@link RaceLog}, must NOT be null
+ * @param raceColumnName
+ * required to identify {@link RaceLog}, must NOT be null
+ * @param fleetName
+ * required to identify {@link RaceLog}, must NOT be null
+ * @param tag
+ * tag to remove
+ * @return successful {@link SuccessInfo} if tag was removed successfully, otherwise
+ * non-successful {@link SuccessInfo}
+ */
+ SuccessInfo removeTag(String leaderboardName, String raceColumnName, String fleetName, TagDTO tag);
+
+ /**
+ * Updates given tagToUpdate with the given attributes tag, comment,
+ * imageURL and visibleForPublic. Tags are not really updated, instead public tags are
+ * revoked/private tags removed first and then the new tags gets saved depending on the new value
+ * visibleForPublic.
+ *
+ * @param leaderboardName
+ * required to identify {@link RaceLog}, must NOT be null
+ * @param raceColumnName
+ * required to identify {@link RaceLog}, must NOT be null
+ * @param fleetName
+ * required to identify {@link RaceLog}, must NOT be null
+ * @param tagToUpdate
+ * tag to be updated
+ * @param tag
+ * new tag title
+ * @param comment
+ * new comment
+ * @param imageURL
+ * new image url
+ * @param visibleForPublic
+ * new visibility status
+ * @return successful {@link SuccessInfo} if tag was updated successfully, otherwise
+ * non-successful {@link SuccessInfo}
+ */
+ SuccessInfo updateTag(String leaderboardName, String raceColumnName, String fleetName, TagDTO tagToUpdate,
+ String tag, String comment, String imageURL, String resizedImageURL, boolean visibleForPublic);
+
+ /**
+ * Returns all public and private tags of specified race and current user.
+ *
+ * @param leaderboardName
+ * required to identify {@link RaceLog}, must NOT be null
+ * @param raceColumnName
+ * required to identify {@link RaceLog}, must NOT be null
+ * @param fleetName
+ * required to identify {@link RaceLog}, must NOT be null
+ * @return list of {@link TagDTO tags}
+ */
+ List getAllTags(String leaderboardName, String raceColumnName, String fleetName);
+
+ /**
+ * Returns all public {@link TagDTO tags} of specified race.
+ *
+ * @param leaderboardName
+ * required to identify {@link RaceLog}, must NOT be null
+ * @param raceColumnName
+ * required to identify {@link RaceLog}, must NOT be null
+ * @param fleetName
+ * required to identify {@link RaceLog}, must NOT be null
+ * @return list of public {@link TagDTO tags}
+ */
+ List getPublicTags(String leaderboardName, String raceColumnName, String fleetName);
+
+ /**
+ * Returns all private {@link TagDTO tags} of specified race and current user.
+ *
+ * @param leaderboardName
+ * required to identify {@link RaceLog}, must NOT be null
+ * @param raceColumnName
+ * required to identify {@link RaceLog}, must NOT be null
+ * @param fleetName
+ * required to identify {@link RaceLog}, must NOT be null
+ * @return list of private {@link TagDTO tags}
+ */
+ List getPrivateTags(String leaderboardName, String raceColumnName, String fleetName);
+
RaceCourseDTO getLastCourseDefinitionInRaceLog(String leaderboardName, String raceColumnName, String fleetName) throws NotFoundException;
/**
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java
index a82d82fa795..305a7177748 100755
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java
@@ -42,6 +42,7 @@ import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.domain.common.dto.RaceColumnInSeriesDTO;
import com.sap.sailing.domain.common.dto.RaceDTO;
import com.sap.sailing.domain.common.dto.RegattaCreationParametersDTO;
+import com.sap.sailing.domain.common.dto.TagDTO;
import com.sap.sailing.domain.common.racelog.RacingProcedureType;
import com.sap.sailing.domain.common.tracking.impl.PreciseCompactGPSFixMovingImpl.PreciseCompactPosition;
import com.sap.sailing.domain.common.windfinder.SpotDTO;
@@ -113,6 +114,7 @@ import com.sap.sse.gwt.client.media.VideoDTO;
import com.sap.sse.gwt.client.replication.RemoteReplicationServiceAsync;
import com.sap.sse.pairinglist.PairingList;
import com.sap.sse.pairinglist.PairingListTemplate;
+import com.sap.sse.security.ui.shared.SuccessInfo;
/**
* The async counterpart of {@link SailingService}
@@ -238,9 +240,15 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan
void getRaceTimesInfo(RegattaAndRaceIdentifier raceIdentifier, AsyncCallback callback);
+ void getRaceTimesInfoIncludingTags(RegattaAndRaceIdentifier raceIdentifier, TimePoint searchSince,
+ AsyncCallback callback);
+
void getRaceTimesInfos(Collection raceIdentifiers,
AsyncCallback> callback);
+ void getRaceTimesInfosIncludingTags(Collection raceIdentifiers,
+ Map searchSinceMap, AsyncCallback> callback);
+
void getCoursePositions(RegattaAndRaceIdentifier raceIdentifier, Date date,
AsyncCallback asyncCallback);
@@ -736,6 +744,25 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan
void addCourseDefinitionToRaceLog(String leaderboardName, String raceColumnName, String fleetName,
List> course, AsyncCallback callback);
+ void addTag(String leaderboardName, String raceColumnName, String fleetName, String tag, String comment,
+ String imageURL, String resizedImageURL, boolean visibleForPublic, TimePoint raceTimepoint,
+ AsyncCallback asyncCallback);
+
+ void updateTag(String leaderboardName, String raceColumnName, String fleetName, TagDTO tagToUpdate, String tag,
+ String comment, String imageURL, String resizedImageURL, boolean visibleForPublic, AsyncCallback asyncCallback);
+
+ void removeTag(String leaderboardName, String raceColumnName, String fleetName, TagDTO tag,
+ AsyncCallback asyncCallback);
+
+ void getAllTags(String leaderboardName, String raceColumnName, String fleetName,
+ AsyncCallback> asyncCallback);
+
+ void getPublicTags(String leaderboardName, String raceColumnName, String fleetName,
+ AsyncCallback> asyncCallback);
+
+ void getPrivateTags(String leaderboardName, String raceColumnName, String fleetName,
+ AsyncCallback> asyncCallback);
+
void getLastCourseDefinitionInRaceLog(String leaderboardName, String raceColumnName, String fleetName,
AsyncCallback callback);
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java
index 2fa0bfd16af..cf35cf20fe0 100755
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java
@@ -2110,4 +2110,66 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
String errorUpdatingOwnership(String nameOfOwnedObject);
String soundOff();
String turnOffAllSoundChannels();
+ String tag();
+ String tagButtons();
+ String tagPanel();
+ String tagCreated(String author, String time);
+ String tagNoTagsFound();
+ String tagAddTag();
+ String tagEditTag();
+ String tagDeleteTag();
+ String tagShareTag();
+ String tagAddTags();
+ String tagSavedSuccessfully();
+ String tagNotSaved();
+ String tagNotSavedReason(String reason);
+ String tagAlreadyExists();
+ String tagNotLoaded();
+ String tagNotLoggedIn();
+ String tagNotRemoved();
+ String tagRemovedSuccessfully();
+ String tagFilterExplanation();
+ String tagSelectedTags();
+ String tagsFilter();
+ String tagSearchTags();
+ String tagLabelTag();
+ String tagLabelComment();
+ String tagLabelImage();
+ String tagLabelAction();
+ String tagLabelVisibility();
+ String tagCreateTag();
+ String tagEditCustomTagButtons();
+ String tagAddCustomTagButton();
+ String tagNotSpecified();
+ String tagEditFilter();
+ String tagEditCustomTagButton();
+ String tagSaveCustomTagButton();
+ String tagPreview();
+ String tagVisibleForPublicCheckBox();
+ String tagVisibility();
+ String tagConfirmDeletionHeading();
+ String tagConfirmDeletion(String tag);
+ String tagButtonConfirmDeletionHeading();
+ String tagButtonConfirmDeletion(String tagButton);
+ String tagDiscardChangesHeading();
+ String tagDiscardChanges();
+ String tagFilterNotSavable();
+ String tagCurrentFilter();
+ String tagCreateFilterNotLoggedIn();
+ String tagCreateFilterHint();
+ String tagButtonNotSavable();
+ String tagNotEditable();
+ String tagPublicModificationPermissionMissing();
+ String tagNotFound();
+ String tagInvalidURL();
+ String tagSharedDialog();
+ String tagSharedURL();
+ String tagCopyToClipBoard();
+ String tagBrowseAndUploadImage();
+ String tagCopiedLinkSuccessfully();
+ String tagCopiedLinkNotSuccessfully();
+ String confirm();
+ String cancel();
+ String close();
+ String username();
}
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties
index 48d00f71549..c6bcaed56b2 100755
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties
@@ -2106,4 +2106,66 @@ automaticResizeNeedsStorageService=Automatic Resizing needs a working FileStorag
changeOwnership=Change ownership
errorUpdatingOwnership=Error updating ownership of object {0}
soundOff=Sound off
-turnOffAllSoundChannels=Turn off all sound channels
\ No newline at end of file
+turnOffAllSoundChannels=Turn off all sound channels
+tag=Tag
+tagButtons=Tag-Buttons
+tagPanel=Tags
+tagCreated=({1}, created by {0})
+tagNoTagsFound=No tags found!
+tagAddTag=Add Tag
+tagEditTag=Edit Tag
+tagDeleteTag=Delete Tag
+tagShareTag=Share Tag
+tagAddTags=Add Tags
+tagSavedSuccessfully=Saved tag successfully
+tagNotSaved=Could not save tag!
+tagNotSavedReason=Could not save tag! Reason: {0}
+tagAlreadyExists=Tag already exists
+tagNotLoaded=Could not load tags!
+tagNotLoggedIn=Please log in to add new tags!
+tagNotRemoved=Could not remove tag!
+tagRemovedSuccessfully=Removed tag successfully
+tagFilterExplanation=The filter will show all tags matching the following criterias. Filters are case sensitive!
+tagSelectedTags=Selected Tags
+tagsFilter=Tag Filter
+tagSearchTags=Search
+tagLabelTag=Tag
+tagLabelComment=Comment
+tagLabelImage=Image
+tagLabelAction=Action
+tagLabelVisibility=Visibility
+tagCreateTag=Create Tag
+tagEditCustomTagButtons=Edit Tag-Buttons
+tagAddCustomTagButton=Add Tag-Button
+tagNotSpecified=Tag must not be empty!
+tagEditFilter=Edit Filter
+tagEditCustomTagButton=Edit Tag-Button
+tagSaveCustomTagButton=Save
+tagPreview=Tag Preview:
+tagVisibleForPublicCheckBox=Visible for public
+tagVisibility=Visibility
+tagConfirmDeletionHeading=Delete Tag
+tagConfirmDeletion=This will delete the tag "{0}". Do you really want to continue?
+tagButtonConfirmDeletionHeading=Delete Tag-Button
+tagButtonConfirmDeletion=This will delete the tag-button "{0}". Do you really want to continue?
+tagDiscardChangesHeading=Discard changes
+tagDiscardChanges=Are you sure you want to discard your changes?
+tagFilterNotSavable=Could not save the filter settings on the server!
+tagCurrentFilter=Current Filter:
+tagCreateFilterNotLoggedIn=You are not logged in so filters will not be saved!
+tagCreateFilterHint=Create a filter set to filter the list of tags.
+tagButtonNotSavable=Could not save tag-button on the server!
+tagNotEditable=You may not edit this tag!
+tagPublicModificationPermissionMissing=You are not allowed to create public tags but you are allowed to create private tags instead.
+tagNotFound=Tag could not be found!
+tagInvalidURL=Invalid URL parameter
+tagSharedDialog=Shared tag
+tagSharedURL=You can share this tag by passing the following URL:
+tagCopyToClipBoard=Copy to clipboard
+tagBrowseAndUploadImage=Browse and upload
+tagCopiedLinkSuccessfully=Successfully copied link to clipboard!
+tagCopiedLinkNotSuccessfully=Your browser does not support copying to clipboard, please copy link manually!
+username=User name
+confirm=Confirm
+cancel=Cancel
+close=Close
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties
index d409021226b..6705263c988 100755
--- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties
@@ -2101,4 +2101,66 @@ automaticResizeNeedsStorageService=Die automatische Skalierung benötigt einen f
changeOwnership=Besitzer wechseln
errorUpdatingOwnership=Fehler beim Setzen des Eigners für Objekt {0}
soundOff=Ton aus
-turnOffAllSoundChannels=Alle Tonkanäle abschalten
\ No newline at end of file
+turnOffAllSoundChannels=Alle Tonkanäle abschalten
+tag=Tag
+tagButtons=Tag-Buttons
+tagPanel=Tags
+tagCreated=({1}, erstellt von {0})
+tagNoTagsFound=Keine Tags gefunden!
+tagAddTag=Tag hinzufügen
+tagEditTag=Tag bearbeiten
+tagDeleteTag=Tag löschen
+tagShareTag=Tag teilen
+tagAddTags=Tags hinzufügen
+tagSavedSuccessfully=Tag erfolgreich gespeichert
+tagNotSaved=Tag konnte nicht gespeichert werden!
+tagNotSavedReason=Tag konnte nicht gespeichert werden! Grund: {0}
+tagAlreadyExists=Tag existiert bereits
+tagNotLoaded=Tags konnten nicht geladen werden!
+tagNotLoggedIn=Bitte logge dich ein, um einem Rennen Tags hinzufügen zu können!
+tagNotRemoved=Tag konnte nicht entfernt werden!
+tagRemovedSuccessfully=Tag erfolgreich entfernt
+tagFilterExplanation=Der Filter zeigt alle Tags an, welche die nachfolgenden Kriterien erfüllen. Groß- und Kleinschreibung werden beachtet!
+tagSelectedTags=Ausgewählte Tags
+tagsFilter=Tag Filter
+tagSearchTags=Suchen
+tagLabelTag=Tag
+tagLabelComment=Kommentar
+tagLabelImage=Bild
+tagLabelAction=Aktion
+tagLabelVisibility=Sichtbarkeit
+tagCreateTag=Tag hinzufügen
+tagEditCustomTagButtons=Tag-Buttons bearbeiten
+tagAddCustomTagButton=Tag-Button hinzufügen
+tagNotSpecified=Tag darf nicht leer sein!
+tagEditFilter=Bearbeite Filter
+tagEditCustomTagButton=Bearbeite Tag-Button
+tagSaveCustomTagButton=Speichern
+tagPreview=Tag Vorschau:
+tagVisibleForPublicCheckBox=Für alle sichtbar
+tagVisibility=Sichtbarkeit
+tagConfirmDeletionHeading=Tag löschen
+tagConfirmDeletion=Dies wird den Tag "{0}" endgültig löschen. Willst du wirklich fortfahren?
+tagButtonConfirmDeletionHeading=Tag-Button löschen
+tagButtonConfirmDeletion=Dies wird den Tag-Button "{0}" endgütltig löschen. Willst du wirklich fortfahren?
+tagDiscardChangesHeading=Änderungen verwerfen
+tagDiscardChanges=Willst du wirklich alle Änderungen verwerfen?
+tagFilterNotSavable=Die Tag-Filter konnten nicht auf dem Server gespeichert werden!
+tagCurrentFilter=Aktueller Filter:
+tagCreateFilterNotLoggedIn=Bitte einloggen um Filter speichern zu können!
+tagCreateFilterHint=Lege einen Filter an, um die sichtbaren Tags zu reduzieren.
+tagButtonNotSavable=Tag-Button konnten nicht auf dem Server gespeichert werden!
+tagNotEditable=Du darfst diesen Tag nicht bearbeiten!
+tagPublicModificationPermissionMissing=Du darfst nur private, jedoch keine öffentlichen Tags erstellen.
+tagNotFound=Tag wurde nicht gefunden!
+tagInvalidURL=URL Parameter nicht gültig
+tagSharedDialog=Geteilter Tag
+tagSharedURL=Du kannst diesen Tag durch die Weitergabe der folgenden URL teilen:
+tagCopyToClipBoard=Kopiere in die Zwischenablage
+tagBrowseAndUploadImage=Bild auswählen und senden
+tagCopiedLinkSuccessfully=Link erfolgreich in die Zwischenablage kopiert!
+tagCopiedLinkNotSuccessfully=Dein Browser unterstüzt leider kein Kopieren in die Zwischenablage, bitte kopiere den Link manuell!
+username=Benutzername
+confirm=Fortfahren
+cancel=Abbrechen
+close=Schließen
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/AbstractTagTextFilterUIFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/AbstractTagTextFilterUIFactory.java
new file mode 100644
index 00000000000..856f09b1262
--- /dev/null
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/AbstractTagTextFilterUIFactory.java
@@ -0,0 +1,38 @@
+package com.sap.sailing.gwt.ui.client.shared.filter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.google.gwt.user.client.ui.ListBox;
+import com.sap.sailing.domain.common.dto.TagDTO;
+import com.sap.sse.common.filter.TextFilter;
+import com.sap.sse.common.filter.TextOperator;
+import com.sap.sse.gwt.client.dialog.DataEntryDialog;
+
+public abstract class AbstractTagTextFilterUIFactory implements FilterUIFactory {
+
+ protected List supportedOperators;
+ protected TextOperator.Operators defaultOperator;
+ protected TextFilter tagTextFilter;
+
+ public AbstractTagTextFilterUIFactory(TextFilter tagTextFilter, TextOperator.Operators defaultOperator) {
+ this.tagTextFilter = tagTextFilter;
+ this.defaultOperator = defaultOperator;
+ supportedOperators = new ArrayList();
+ }
+
+ protected ListBox createOperatorSelectionListBox(DataEntryDialog> dataEntryDialog) {
+ ListBox operatorsListBox = dataEntryDialog.createListBox(false);
+ int i = 0;
+ for (TextOperator.Operators op : supportedOperators) {
+ operatorsListBox.addItem(FilterOperatorsFormatter.format(op), op.name());
+ if (tagTextFilter.getOperator() != null && tagTextFilter.getOperator().getName().equals(op.name())) {
+ operatorsListBox.setSelectedIndex(i);
+ } else if (defaultOperator != null && defaultOperator.equals(op)) {
+ operatorsListBox.setSelectedIndex(i);
+ }
+ i++;
+ }
+ return operatorsListBox;
+ }
+}
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/AbstractTagsFilterSetDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/AbstractTagsFilterSetDialog.java
new file mode 100644
index 00000000000..feaad61c069
--- /dev/null
+++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/AbstractTagsFilterSetDialog.java
@@ -0,0 +1,277 @@
+package com.sap.sailing.gwt.ui.client.shared.filter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.FocusWidget;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.HasVerticalAlignment;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.sap.sailing.gwt.ui.client.StringMessages;
+import com.sap.sailing.domain.common.dto.TagDTO;
+import com.sap.sse.common.filter.Filter;
+import com.sap.sse.common.filter.FilterSet;
+import com.sap.sse.gwt.client.dialog.DataEntryDialog;
+
+/**
+ * Used to create or edit a {@link FilterSet} for {@link TagDTO}s
+ */
+public abstract class AbstractTagsFilterSetDialog extends DataEntryDialog>> {
+ private final FilterSet> tagsFilterSet;
+ private final StringMessages stringMessages;
+ private final List availableTagFilterNames;
+
+ private ListBox filterListBox;
+ private final Button addFilterButton;
+
+ protected TextBox filterSetNameTextBox;
+ private Grid tagsFiltersGrid;
+ private Label tagsFiltersGridHeadline;
+ private Label tagsFiltersGridFooter;
+ private VerticalPanel mainPanel;
+
+ private final List filterEditWidgets;
+ private final List> filterUIFactories;
+ private final List