Merge branch 'bug4104' of ssh://trac@sapsailing.com/home/trac/git into bug4104

This commit is contained in:
D067890 committed 2018-09-24 09:55:29 +02:00
commit b45a8a7165
23 files changed
+609 -495

No files matched your search

@@ -17,12 +17,13 @@ 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,
@@ -5,12 +5,17 @@ import java.io.Serializable;
import com.sap.sse.common.TimePoint;
/**
* Used to send tags over network. Allows to create tags with all possible combinations of states (private/public and
* valid/revoked).
* 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.<br/>
* <br/>
* 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}. <code>NOT</code> part of
* the key are {@link #createdAt} and {@link #revokedAt}.
*/
// TODO: Remove max length
// TODO: add Javadoc, which parameter has which meaning? what can be guessed from parameters (revokedAt = null)? what is
// the key to identify a tag?
public class TagDTO implements Serializable {
private static final long serialVersionUID = 3907411584518452300L;
@@ -19,15 +24,23 @@ public class TagDTO implements Serializable {
private String tag, comment, imageURL, username;
private boolean visibleForPublic;
/**
* By default every tag should have set the attributes {@link #raceTimepoint} and {@link #createdAt}.<br/>
* <code>raceTimepoint</code>: absolute timepoint of race when the tag got created<br/>
* <code>createdAt</code>: actual absolute creation timepoint of tag<br/>
* <br/>
* 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 <code>null</code>. So every tag
* where {@link #revokedAt} equals <code>null</code> can be seen as valid and <code>NOT</code> revoked.
*/
private TimePoint raceTimepoint, createdAt, revokedAt;
public static final int MAX_TAG_LENGTH = 100;
public static final int MAX_COMMENT_LENGTH = 400;
/**
* Required by GWT, do <b>NOT</b> use this constructor! Use the other constructors instead to allow information
* transfer.
*/
@Deprecated
TagDTO() {
}
@@ -40,18 +53,18 @@ public class TagDTO implements Serializable {
* comment of tag, may be <code>null</code> as comment is optional
* @param imageURL
* image URL of tag, may be <code>null</code> as image is optional
* @param username
* name of user who created the tag
* @param visibleForPublic
* should be <code>true</code> if everbidy should see this tag, otherwise <code>false</code>
* @param username
* name of user who created the tag
* @param raceTimepoint
* timepoint of race where tag ot created
* @param createdAt
* timepoint where <code>username</code> created the tag
*/
public TagDTO(String tag, String comment, String imageURL, String username, boolean visibleForPublic,
public TagDTO(String tag, String comment, String imageURL, boolean visibleForPublic, String username,
TimePoint raceTimepoint, TimePoint createdAt) {
this(tag, comment, imageURL, username, visibleForPublic, raceTimepoint, createdAt, null);
this(tag, comment, imageURL, visibleForPublic, username, raceTimepoint, createdAt, null);
}
/**
@@ -63,10 +76,10 @@ public class TagDTO implements Serializable {
* comment of tag, may be <code>null</code> as comment is optional
* @param imageURL
* image URL of tag, may be <code>null</code> as image is optional
* @param username
* name of user who created the tag
* @param visibleForPublic
* should be <code>true</code> if everbidy should see this tag, otherwise <code>false</code>
* @param username
* name of user who created the tag
* @param raceTimepoint
* timepoint of race where tag ot created
* @param createdAt
@@ -74,15 +87,15 @@ public class TagDTO implements Serializable {
* @param revokedAt
* timepoint where tag got revoked, may be <code>null</code> if tag is not revoked
*/
public TagDTO(String tag, String comment, String imageURL, String username, boolean visibleForPublic,
public TagDTO(String tag, String comment, String imageURL, boolean visibleForPublic, String username,
TimePoint raceTimepoint, TimePoint createdAt, TimePoint revokedAt) {
this.tag = tag;
this.comment = comment;
this.imageURL = imageURL;
this.visibleForPublic = visibleForPublic;
this.username = username;
this.raceTimepoint = raceTimepoint;
this.createdAt = createdAt;
this.visibleForPublic = visibleForPublic;
this.revokedAt = revokedAt;
}
@@ -113,15 +126,6 @@ public class TagDTO implements Serializable {
return imageURL;
}
/**
* Returns name of user who created this tag.
*
* @return username
*/
public String getUsername() {
return username;
}
/**
* Returns visibilty of tag.
*
@@ -131,6 +135,15 @@ public class TagDTO implements Serializable {
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.
*
@@ -172,6 +185,11 @@ public class TagDTO implements Serializable {
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;
@@ -182,34 +200,30 @@ public class TagDTO implements Serializable {
return false;
} else if (!imageURL.equals(other.imageURL))
return false;
if (raceTimepoint == null) {
if (other.raceTimepoint != null)
return false;
} else if (!raceTimepoint.equals(other.raceTimepoint))
return false;
if (tag == null) {
if (other.tag != null)
return false;
} else if (!tag.equals(other.tag))
if (isVisibleForPublic() != other.isVisibleForPublic()) {
return false;
}
if (username == null) {
if (other.username != null)
return false;
} else if (!username.equals(other.username)) {
return false;
}
if (isVisibleForPublic() != other.isVisibleForPublic()) {
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 <code>true</code> if all parameters match the attributes of {@link TagDTO}, otherwise <code>false</code>
* @return <code>true</code> if all parameters match the key attributes of {@link TagDTO}, otherwise
* <code>false</code>
*/
public boolean equals(String tag, String comment, String imageURL, String username, boolean visibleForPublic,
public boolean equals(String tag, String comment, String imageURL, boolean visibleForPublic, String username,
TimePoint raceTimepoint) {
if (this.comment == null) {
if (comment != null)
@@ -251,8 +265,8 @@ public class TagDTO implements Serializable {
@Override
public String toString() {
return "TagDTO [tag=" + tag + ", comment=" + comment + ", imageURL=" + imageURL + ", username=" + username
+ ", raceTimepoint=" + raceTimepoint + ", createdAt=" + createdAt + ", isPublic=" + visibleForPublic
+ ", revokedAt=" + revokedAt + "]";
return "TagDTO [tag=" + tag + ", comment=" + comment + ", imageURL=" + imageURL + ", visibleForPublic="
+ visibleForPublic + ", username=" + username + ", raceTimepoint=" + raceTimepoint + ", createdAt="
+ createdAt + ", revokedAt=" + revokedAt + "]";
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -23,7 +23,6 @@ import com.sap.sse.common.TimePoint;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
// TODO: add Javadoc
public class RaceTimesInfoProvider {
private final SailingServiceAsync sailingService;
@@ -37,10 +36,27 @@ public class RaceTimesInfoProvider {
private final Set<RaceTimesInfoProviderListener> listeners;
private boolean terminated = false;
// Should server send tag updates for this RaceTimesInfoProvider instance? Default is set to false,
// TaggingPanel will set value to true for corresponding instance.
// => decrease server load by not requesting tags every roundtrip.
/**
* 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 <code>null</code>
* or <code>0</code> to request all tags from server.<br/>
* 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<RegattaAndRaceIdentifier, TimePoint> latestReceivedTagTimes;
/**
@@ -246,18 +262,47 @@ public class RaceTimesInfoProvider {
raceTimesInfos.clear();
}
/**
* Returns the saved timepoint for the given <code>raceIdentifier</code> which is used as known point of time when
* data is requested from server and {@link #requestTags} is set to <code>true</code>.
*
* @param raceIdentifier
* identifies race
* @return timepoint of latest received tag, may be <code>null</code> 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}. <code>latestReceivedTagTime</code> may be <code>null</code> or <code>0</code> 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;
}
@@ -12,6 +12,7 @@ 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;
@@ -121,6 +122,8 @@ import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.Util.Triple;
import com.sap.sse.common.mail.MailException;
import com.sap.sse.common.media.MediaTagConstants;
import com.sap.sse.filestorage.FileStorageService;
import com.sap.sse.gwt.client.ServerInfoDTO;
import com.sap.sse.gwt.client.filestorage.FileStorageManagementGwtService;
import com.sap.sse.gwt.client.media.ImageDTO;
@@ -197,13 +200,23 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS
RaceTimesInfoDTO getRaceTimesInfo(RegattaAndRaceIdentifier raceIdentifier);
RaceTimesInfoDTO getRaceTimesInfoIncludingTags(RegattaAndRaceIdentifier raceIdentifier,
TimePoint latestReceivedTagTime);
/**
* Returns {@link RaceTimesInfoDTO race times info} for specified race (<code>raceIdentifier</code>) including
* {@link RaceLogTagEvent tag events} since received timestamp (<code>searchSince</code>). Loads tags from
* {@link ReadonlyRaceState cache} instead of scanning the whole {@link RaceLog} every request.
*/
RaceTimesInfoDTO getRaceTimesInfoIncludingTags(RegattaAndRaceIdentifier raceIdentifier, TimePoint searchSince);
List<RaceTimesInfoDTO> getRaceTimesInfos(Collection<RegattaAndRaceIdentifier> raceIdentifiers);
/**
* Collects besides {@link RaceTimesInfoDTO race times infos} public {@link RaceLogTagEvent tag events} from
* {@link ReadonlyRaceState cache} and compares the <code>createdAt</code> timepoint to the received
* <code>searchSince</code> timepoint. Returns {@link RaceTimesInfoDTO race times infos} including
* {@link RaceLogTagEvent public tag events} since the latest client-side received tag.
*/
List<RaceTimesInfoDTO> getRaceTimesInfosIncludingTags(Collection<RegattaAndRaceIdentifier> raceIdentifiers,
Map<RegattaAndRaceIdentifier, TimePoint> latestReceivedTagTimes);
Map<RegattaAndRaceIdentifier, TimePoint> searchSinceMap);
CoursePositionsDTO getCoursePositions(RegattaAndRaceIdentifier raceIdentifier, Date date);
@@ -240,15 +240,14 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan
void getRaceTimesInfo(RegattaAndRaceIdentifier raceIdentifier, AsyncCallback<RaceTimesInfoDTO> callback);
void getRaceTimesInfoIncludingTags(RegattaAndRaceIdentifier raceIdentifier, TimePoint latestReceivedTagTime,
void getRaceTimesInfoIncludingTags(RegattaAndRaceIdentifier raceIdentifier, TimePoint searchSince,
AsyncCallback<RaceTimesInfoDTO> callback);
void getRaceTimesInfos(Collection<RegattaAndRaceIdentifier> raceIdentifiers,
AsyncCallback<List<RaceTimesInfoDTO>> callback);
void getRaceTimesInfosIncludingTags(Collection<RegattaAndRaceIdentifier> raceIdentifiers,
Map<RegattaAndRaceIdentifier, TimePoint> latestReceivedTagTimes,
AsyncCallback<List<RaceTimesInfoDTO>> callback);
Map<RegattaAndRaceIdentifier, TimePoint> searchSinceMap, AsyncCallback<List<RaceTimesInfoDTO>> callback);
void getCoursePositions(RegattaAndRaceIdentifier raceIdentifier, Date date,
AsyncCallback<CoursePositionsDTO> asyncCallback);
@@ -7,50 +7,40 @@ import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONBoolean;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONString;
import com.sap.sailing.gwt.ui.client.GwtJsonDeSerializer;
/**
* Serializes and deserializes {@link TagButton tag-buttons} to save them in the {@link com.sap.sse.security.UserStore
* UserStore}.
*/
// TODO: Don't use key for tag-buttons, save array directly instead.
public class TagButtonJsonDeSerializer implements GwtJsonDeSerializer<List<TagButton>> {
public class TagButtonJsonDeSerializer {
private static final String FIELD_TAG_BUTTONS = "tagButtons";
private static final String FIELD_BUTTON_NAME = "buttonName";
private static final String FIELD_TAG = "tag";
private static final String FIELD_COMMENT = "comment";
private static final String FIELD_IMAGE_URL = "imageURL";
private static final String FIELD_VISIBLE_FOR_PUBLIC = "public";
@Override
public JSONObject serialize(List<TagButton> tagButtons) {
JSONObject result = new JSONObject();
JSONArray tagButtonsArray = new JSONArray();
result.put(FIELD_TAG_BUTTONS, tagButtonsArray);
public JSONArray serialize(List<TagButton> tagButtons) {
JSONArray result = new JSONArray();
int i = 0;
for (TagButton button : tagButtons) {
JSONObject tagButtonObject = new JSONObject();
tagButtonsArray.set(i++, tagButtonObject);
tagButtonObject.put(FIELD_BUTTON_NAME, new JSONString(button.getText()));
tagButtonObject.put(FIELD_TAG, new JSONString(button.getTag()));
tagButtonObject.put(FIELD_COMMENT, new JSONString(button.getComment()));
tagButtonObject.put(FIELD_IMAGE_URL, new JSONString(button.getImageURL()));
tagButtonObject.put(FIELD_VISIBLE_FOR_PUBLIC, JSONBoolean.getInstance(button.isVisibleForPublic()));
result.set(i++, tagButtonObject);
}
return result;
}
@Override
public List<TagButton> deserialize(JSONObject rootObject) {
List<TagButton> result = null;
public List<TagButton> deserialize(JSONArray rootObject) {
List<TagButton> result = new ArrayList<TagButton>();
if (rootObject != null) {
result = new ArrayList<TagButton>();
JSONArray tagButtonsArray = (JSONArray) rootObject.get(FIELD_TAG_BUTTONS);
for (int i = 0; i < tagButtonsArray.size(); i++) {
JSONObject tagButtonValue = (JSONObject) tagButtonsArray.get(i);
for (int i = 0; i < rootObject.size(); i++) {
JSONObject tagButtonValue = (JSONObject) rootObject.get(i);
JSONString tagButtonName = (JSONString) tagButtonValue.get(FIELD_BUTTON_NAME);
JSONString tagButtonTag = (JSONString) tagButtonValue.get(FIELD_TAG);
JSONString tagButtonComment = (JSONString) tagButtonValue.get(FIELD_COMMENT);
@@ -63,5 +53,4 @@ public class TagButtonJsonDeSerializer implements GwtJsonDeSerializer<List<TagBu
}
return result;
}
}
@@ -1,6 +1,6 @@
package com.sap.sailing.gwt.ui.raceboard.tagging;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONParser;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.user.client.rpc.AsyncCallback;
@@ -18,7 +18,7 @@ import com.sap.sse.security.ui.client.UserService;
*/
public class TagButtonPanel extends FlowPanel {
private static final String USER_STORAGE_TAG_BUTTONS_KEY = "sailingAnalytics.raceBoard.tagButtons";
private static final String USER_STORAGE_TAG_BUTTONS_KEY = "sailing.tags.buttons";
private final TagPanelStyle style = TagPanelResources.INSTANCE.style();
@@ -71,8 +71,8 @@ public class TagButtonPanel extends FlowPanel {
if (result != null && !result.isEmpty()) {
final TagButtonJsonDeSerializer deserializer = new TagButtonJsonDeSerializer();
final JSONValue value = JSONParser.parseStrict(result);
if (value.isObject() != null) {
for (TagButton tagButton : deserializer.deserialize((JSONObject) value)) {
if (value.isArray() != null) {
for (TagButton tagButton : deserializer.deserialize((JSONArray) value)) {
taggingPanel.addTagButton(tagButton);
}
}
@@ -92,8 +92,8 @@ public class TagButtonPanel extends FlowPanel {
*/
protected void storeAllTagButtons() {
TagButtonJsonDeSerializer serializer = new TagButtonJsonDeSerializer();
JSONObject jsonObject = serializer.serialize(taggingPanel.getTagButtons());
userService.setPreference(USER_STORAGE_TAG_BUTTONS_KEY, jsonObject.toString(), new AsyncCallback<Void>() {
JSONArray jsonArray = serializer.serialize(taggingPanel.getTagButtons());
userService.setPreference(USER_STORAGE_TAG_BUTTONS_KEY, jsonArray.toString(), new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
Notification.notify(stringMessages.tagButtonNotSavable(), NotificationType.WARNING);
@@ -36,7 +36,7 @@ import com.sap.sse.security.ui.client.UserService;
*/
public class TagFilterPanel extends FlowPanel implements KeyUpHandler, Filter<TagDTO> {
private final static String LOCAL_STORAGE_TAGS_FILTER_SETS_KEY = "sailingAnalytics.raceBoard.tagsFilterSets";
private final static String LOCAL_STORAGE_TAGS_FILTER_SETS_KEY = "sailing.tags.filtersets";
private final TagPanelStyle style = TagPanelResources.INSTANCE.style();
@@ -44,7 +44,6 @@ public class TagInputPanel extends FlowPanel {
tagTextBox = new TextBox();
tagTextBox.setStyleName(style.tagInputPanelTag());
tagTextBox.setTitle(stringMessages.tagLabelTag());
tagTextBox.getElement().setAttribute("maxlength", Integer.toString(TagDTO.MAX_TAG_LENGTH));
tagTextBox.getElement().setPropertyString("placeholder", stringMessages.tagLabelTag());
add(tagTextBox);
@@ -57,7 +56,6 @@ public class TagInputPanel extends FlowPanel {
commentTextArea = new TextArea();
commentTextArea.setStyleName(style.tagInputPanelComment());
commentTextArea.setVisibleLines(4);
commentTextArea.getElement().setAttribute("maxlength", Integer.toString(TagDTO.MAX_COMMENT_LENGTH));
commentTextArea.setTitle(stringMessages.tagLabelComment());
commentTextArea.getElement().setPropertyString("placeholder", stringMessages.tagLabelComment());
add(commentTextArea);
@@ -70,7 +70,7 @@ public class TagPreviewPanel extends FlowPanel {
protected void renderPreview(TagInputPanel inputPanel) {
listContainingPreviewTag.removeAll(listContainingPreviewTag);
listContainingPreviewTag.add(new TagDTO(inputPanel.getTag(), inputPanel.getComment(), inputPanel.getImageURL(),
userService.getCurrentUser().getName(), inputPanel.isVisibleForPublic(),
inputPanel.isVisibleForPublic(), userService.getCurrentUser().getName(),
new MillisecondsTimePoint(taggingPanel.getTimerTime()), MillisecondsTimePoint.now()));
tagPreviewCellList.setRowData(listContainingPreviewTag);
@@ -56,13 +56,12 @@ import com.sap.sse.security.ui.shared.UserDTO;
* so on. Best practice: The constructor of subcomponents of the TaggingPanel contains only the TaggingPanel as a
* parameter. Every other required shared resource (string messages, service references, ...) can be requested from the
* TaggingPanel itself.
*
* @author Julian Rendl, Henri Kohlberg
*/
// TODO: Add refresh button which resets lastReceivedTagTime
// TODO: resize plus of "add tags" button
// TODO: get URL params as constructor parameter and not from Window object
// TODO: Goal: Show tags in time slider (add bugzilla bug)
// TODO: Unit Tests (incl. concatenation)
// TODO: use HTML storage as event provider to update other tabs when user modifies private tags (new bugzilla bug)
// TODO: cache user settings and use observer pattern for cache
public class TaggingPanel extends ComponentWithoutSettings
implements RaceTimesInfoProviderListener, UserStatusEventHandler, TimeListener {
@@ -243,7 +242,7 @@ public class TaggingPanel extends ComponentWithoutSettings
boolean tagIsNewTag = true;
// check if tag already exists
for (TagDTO tagDTO : tagListProvider.getAllTags()) {
if (tagDTO.equals(tag, comment, imageURL, userService.getCurrentUser().getName(), visibleForPublic,
if (tagDTO.equals(tag, comment, imageURL, visibleForPublic, userService.getCurrentUser().getName(),
new MillisecondsTimePoint(getTimerTime()))) {
tagIsNewTag = false;
break;
@@ -52,7 +52,7 @@
background-color: #efaa00;
background-position: center;
background-repeat: no-repeat;
background-size: 60%;
background-size: 40%;
}
.taggingPanel button.toggleEditState:hover {
@@ -63,6 +63,7 @@ import javax.servlet.http.HttpServletRequest;
import org.apache.http.client.ClientProtocolException;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.AuthorizationException;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
@@ -289,6 +290,9 @@ import com.sap.sailing.domain.common.racelog.tracking.TransformationException;
import com.sap.sailing.domain.common.security.Permission;
import com.sap.sailing.domain.common.security.Permission.Mode;
import com.sap.sailing.domain.common.sharding.ShardingType;
import com.sap.sailing.domain.common.tagging.RaceLogNotFoundException;
import com.sap.sailing.domain.common.tagging.ServiceNotFoundException;
import com.sap.sailing.domain.common.tagging.TagAlreadyExistsException;
import com.sap.sailing.domain.common.tracking.BravoFix;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
@@ -2234,17 +2238,11 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet
return raceTimesInfo;
}
/**
* Returns {@link RaceTimesInfoDTO race times info} for specified race (<code>raceIdentifier</code>) including
* {@link RaceLogTagEvent tag events} since received timestamp (<code>latestReceivedTagTime</code>). Loads tags from
* {@link ReadonlyRaceState cache} instead of scanning the whole {@link RaceLog} every request.
*/
// TODO: rename latestReceivedTagTime to match role
@Override
public RaceTimesInfoDTO getRaceTimesInfoIncludingTags(RegattaAndRaceIdentifier raceIdentifier,
TimePoint latestReceivedTagTime) {
TimePoint searchSince) {
RaceTimesInfoDTO raceTimesInfo = getRaceTimesInfo(raceIdentifier);
raceTimesInfo.setTags(getService().getTaggingService().getPublicTags(raceIdentifier, latestReceivedTagTime));
raceTimesInfo.setTags(getService().getTaggingService().getPublicTags(raceIdentifier, searchSince));
return raceTimesInfo;
}
@@ -2260,19 +2258,13 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet
return raceTimesInfos;
}
/**
* Collects besides {@link RaceTimesInfoDTO race times infos} public {@link RaceLogTagEvent tag events} from
* {@link ReadonlyRaceState cache} and compares the <code>createdAt</code> timepoint to the received latest tag
* creation timepoint. Returns {@link RaceTimesInfoDTO race times infos} including {@link RaceLogTagEvents public
* tag events} since the latest client-side received tag.
*/
@Override
public List<RaceTimesInfoDTO> getRaceTimesInfosIncludingTags(Collection<RegattaAndRaceIdentifier> raceIdentifiers,
Map<RegattaAndRaceIdentifier, TimePoint> latestReceivedTagTimes) {
Map<RegattaAndRaceIdentifier, TimePoint> searchSinceMap) {
List<RaceTimesInfoDTO> raceTimesInfos = new ArrayList<RaceTimesInfoDTO>();
for (RegattaAndRaceIdentifier raceIdentifier : raceIdentifiers) {
RaceTimesInfoDTO raceTimesInfo = getRaceTimesInfoIncludingTags(raceIdentifier,
latestReceivedTagTimes.get(raceIdentifier));
searchSinceMap.get(raceIdentifier));
if (raceTimesInfo != null) {
raceTimesInfos.add(raceTimesInfo);
}
@@ -6476,12 +6468,27 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet
public SuccessInfo addTag(String leaderboardName, String raceColumnName, String fleetName, String tag,
String comment, String imageURL, boolean visibleForPublic, TimePoint raceTimepoint) {
SuccessInfo successInfo = new SuccessInfo(true, null, null, null);
boolean successful = getService().getTaggingService().addTag(leaderboardName, raceColumnName, fleetName, tag,
comment, imageURL, visibleForPublic, raceTimepoint);
if (!successful) {
String message = serverStringMessages.get(getClientLocale(),
getService().getTaggingService().getLastErrorCode().getCode());
successInfo = new SuccessInfo(false, message, null, null);
try {
getService().getTaggingService().addTag(leaderboardName, raceColumnName, fleetName, tag, comment, imageURL,
visibleForPublic, raceTimepoint);
} catch (AuthorizationException e) {
successInfo = new SuccessInfo(false, serverStringMessages.get(getClientLocale(), "missingAuthorization"),
null, null);
} catch (IllegalArgumentException e) {
successInfo = new SuccessInfo(false, serverStringMessages.get(getClientLocale(), "invalidParameters"), null,
null);
} catch (RaceLogNotFoundException e) {
successInfo = new SuccessInfo(false, serverStringMessages.get(getClientLocale(), "raceLogNotFound"), null,
null);
} catch (ServiceNotFoundException e) {
successInfo = new SuccessInfo(false, serverStringMessages.get(getClientLocale(), "securityServiceNotFound"),
null, null);
} catch (TagAlreadyExistsException e) {
successInfo = new SuccessInfo(false, serverStringMessages.get(getClientLocale(), "tagAlreadyExists"), null,
null);
} catch (Exception e) {
successInfo = new SuccessInfo(false, serverStringMessages.get(getClientLocale(), "unknownError"), null,
null);
}
return successInfo;
}
@@ -6489,12 +6496,26 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet
@Override
public SuccessInfo removeTag(String leaderboardName, String raceColumnName, String fleetName, TagDTO tag) {
SuccessInfo successInfo = new SuccessInfo(true, null, null, null);
boolean successful = getService().getTaggingService().removeTag(leaderboardName, raceColumnName, fleetName,
tag);
if (!successful) {
String message = serverStringMessages.get(getClientLocale(),
getService().getTaggingService().getLastErrorCode().getCode());
successInfo = new SuccessInfo(false, message, null, null);
try {
getService().getTaggingService().removeTag(leaderboardName, raceColumnName, fleetName, tag);
} catch (AuthorizationException e) {
successInfo = new SuccessInfo(false, serverStringMessages.get(getClientLocale(), "missingAuthorization"),
null, null);
} catch (IllegalArgumentException e) {
successInfo = new SuccessInfo(false, serverStringMessages.get(getClientLocale(), "invalidParameters"), null,
null);
} catch (NotRevokableException e) {
successInfo = new SuccessInfo(false, serverStringMessages.get(getClientLocale(), "tagNotRevokable"), null,
null);
} catch (RaceLogNotFoundException e) {
successInfo = new SuccessInfo(false, serverStringMessages.get(getClientLocale(), "raceLogNotFound"), null,
null);
} catch (ServiceNotFoundException e) {
successInfo = new SuccessInfo(false, serverStringMessages.get(getClientLocale(), "securityServiceNotFound"),
null, null);
} catch (Exception e) {
successInfo = new SuccessInfo(false, serverStringMessages.get(getClientLocale(), "unknownError"), null,
null);
}
return successInfo;
}
@@ -6503,12 +6524,30 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet
public SuccessInfo updateTag(String leaderboardName, String raceColumnName, String fleetName, TagDTO tagToUpdate,
String tag, String comment, String imageURL, boolean visibleForPublic) {
SuccessInfo successInfo = new SuccessInfo(true, null, null, null);
boolean successful = getService().getTaggingService().updateTag(leaderboardName, raceColumnName, fleetName,
tagToUpdate, tag, comment, imageURL, visibleForPublic);
if (!successful) {
String message = serverStringMessages.get(getClientLocale(),
getService().getTaggingService().getLastErrorCode().getCode());
successInfo = new SuccessInfo(false, message, null, null);
try {
getService().getTaggingService().updateTag(leaderboardName, raceColumnName, fleetName, tagToUpdate, tag,
comment, imageURL, visibleForPublic);
} catch (AuthorizationException e) {
successInfo = new SuccessInfo(false, serverStringMessages.get(getClientLocale(), "missingAuthorization"),
null, null);
} catch (IllegalArgumentException e) {
successInfo = new SuccessInfo(false, serverStringMessages.get(getClientLocale(), "invalidParameters"), null,
null);
} catch (NotRevokableException e) {
successInfo = new SuccessInfo(false, serverStringMessages.get(getClientLocale(), "tagNotRevokable"), null,
null);
} catch (RaceLogNotFoundException e) {
successInfo = new SuccessInfo(false, serverStringMessages.get(getClientLocale(), "raceLogNotFound"), null,
null);
} catch (ServiceNotFoundException e) {
successInfo = new SuccessInfo(false, serverStringMessages.get(getClientLocale(), "securityServiceNotFound"),
null, null);
} catch (TagAlreadyExistsException e) {
successInfo = new SuccessInfo(false, serverStringMessages.get(getClientLocale(), "tagAlreadyExists"), null,
null);
} catch (Exception e) {
successInfo = new SuccessInfo(false, serverStringMessages.get(getClientLocale(), "unknownError"), null,
null);
}
return successInfo;
}
@@ -6516,19 +6555,39 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet
@Override
public List<TagDTO> getAllTags(String leaderboardName, String raceColumnName, String fleetName) {
List<TagDTO> result = new ArrayList<TagDTO>();
result.addAll(getService().getTaggingService().getPrivateTags(leaderboardName, raceColumnName, fleetName));
result.addAll(getService().getTaggingService().getPublicTags(leaderboardName, raceColumnName, fleetName));
try {
result.addAll(getService().getTaggingService().getPublicTags(leaderboardName, raceColumnName, fleetName));
} catch (RaceLogNotFoundException e) {
// do nothing and try to return as much tags as possible (private tags)
}
try {
result.addAll(getService().getTaggingService().getPrivateTags(leaderboardName, raceColumnName, fleetName));
} catch (ServiceNotFoundException e) {
// do nothing and try to return as much tags as possible (public tags)
}
return result;
}
@Override
public List<TagDTO> getPublicTags(String leaderboardName, String raceColumnName, String fleetName) {
return getService().getTaggingService().getPublicTags(leaderboardName, raceColumnName, fleetName);
List<TagDTO> result = new ArrayList<TagDTO>();
try {
result.addAll(getService().getTaggingService().getPublicTags(leaderboardName, raceColumnName, fleetName));
} catch (RaceLogNotFoundException e) {
// do nothing as method will always return at least an empty list
}
return result;
}
@Override
public List<TagDTO> getPrivateTags(String leaderboardName, String raceColumnName, String fleetName) {
return getService().getTaggingService().getPrivateTags(leaderboardName, raceColumnName, fleetName);
List<TagDTO> result = new ArrayList<TagDTO>();
try {
result.addAll(getService().getTaggingService().getPrivateTags(leaderboardName, raceColumnName, fleetName));
} catch (ServiceNotFoundException e) {
// do nothing as method will always return at least an empty list
}
return result;
}
/**
@@ -4,14 +4,9 @@ slicingTimeRangeOutOfBounds=The TimeRange to slice is not part of the race
slicingCouldNotObtainRace=Could not obtain sliced race
slicingError=Error slicing race
unknownError=Unknown error
notLoggedIn=You are not logged in
missingPermissions=Missing permissions
securityServiceNotFound=Security service not found
racelogNotFound=Racelog not found
tagNotRevokable=This tag cannot be revoked
tagAlreadyExists=Tag does already exist, duplicated tags are not allowed
tagAlreadyRemoved=Tag cannot be removed twice!
tagNotEmpty=Tag may not be empty
timepointNotEmpty=Timepoint may not be empty
tagTooLong=Tag is too long
commentToLong=Comment is too long
missingAuthorization=You are not logged in or missing the required permission!
securityServiceNotFound=Security service not found!
raceLogNotFound=Racelog not found!
tagNotRevokable=This tag cannot be revoked!
tagAlreadyExists=Tag does already exist, duplicated tags are not allowed!
invalidParameters=One of the given parameters does not have a valid value!
@@ -3,15 +3,10 @@ slicingRaceColumnAlreadyUsedThe=Der Rennspaltenname ist in der Regatta bereits v
slicingTimeRangeOutOfBounds=Die angegebene Zeitspanne ist ausserhalb des Rennens
slicingCouldNotObtainRace=Das geschnittene Rennen konnte nicht gefunden werden
slicingError=Fehler beim Schneiden des Rennens
unknownError=Unbekannter Fehler
notLoggedIn=Du bist nicht eingeloggt
missingPermissions=Fehlende Berechtigung
securityServiceNotFound=Sicherheits-Service konnte nicht gefunden werden
racelogNotFound=Racelog konnte nicht gefunden werden
tagNotRevokable=Dieser Tag ist nicht umkehrbar
tagAlreadyExists=Dieser Tag existiert bereits
tagAlreadyRemoved=Dieser Tag wurde bereits geloescht
tagNotEmpty=Der Tag darf nicht leer sein
timepointNotEmpty=Der Zeitpunkt darf nicht leer sein
tagTooLong=Der Tag ist zu lang
commentToLong=Der Kommentar ist zu lang
unknownError=Unbekannter Fehler!
missingAuthorization=Du bist nicht eingeloggt oder dir fehlen die benoetigten Informationen!
securityServiceNotFound=Sicherheits-Service konnte nicht gefunden werden!
raceLogNotFound=Racelog konnte nicht gefunden werden!
tagNotRevokable=Dieser Tag ist nicht umkehrbar!
tagAlreadyExists=Dieser Tag existiert bereits!
invalidParameters=Einer der Parameter hat einen ungueltigen Wert!
@@ -21,10 +21,14 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
import org.apache.shiro.authz.AuthorizationException;
import org.json.simple.JSONArray;
import com.sap.sailing.domain.common.abstractlog.NotRevokableException;
import com.sap.sailing.domain.common.dto.TagDTO;
import com.sap.sailing.domain.common.racelog.RaceLogServletConstants;
import com.sap.sailing.domain.common.tagging.RaceLogNotFoundException;
import com.sap.sailing.domain.common.tagging.TagAlreadyExistsException;
import com.sap.sailing.server.gateway.jaxrs.AbstractSailingServerResource;
import com.sap.sailing.server.tagging.TagDTODeSerializer;
import com.sap.sailing.server.tagging.TaggingService;
@@ -68,32 +72,31 @@ public class TagsResource extends AbstractSailingServerResource {
@PathParam(RaceLogServletConstants.PARAMS_RACE_COLUMN_NAME) String raceColumnName,
@PathParam(RaceLogServletConstants.PARAMS_RACE_FLEET_NAME) String fleetName,
@DefaultValue("both") @QueryParam("visibility") String visibility) {
boolean lookForPublic = visibility.equalsIgnoreCase("both") || visibility.equalsIgnoreCase("public");
boolean lookForPrivate = visibility.equalsIgnoreCase("both") || visibility.equalsIgnoreCase("private");
Response response;
TaggingService taggingService = getService().getTaggingService();
if (taggingService != null) {
List<TagDTO> tags = new ArrayList<TagDTO>();
final TaggingService taggingService = getService().getTaggingService();
final List<TagDTO> tags = new ArrayList<TagDTO>();
final boolean lookForPublic = visibility.equalsIgnoreCase("both") || visibility.equalsIgnoreCase("public");
final boolean lookForPrivate = visibility.equalsIgnoreCase("both") || visibility.equalsIgnoreCase("private");
try {
if (lookForPublic) {
Util.addAll(taggingService.getPublicTags(leaderboardName, raceColumnName, fleetName), tags);
}
if (lookForPrivate) {
Util.addAll(taggingService.getPrivateTags(leaderboardName, raceColumnName, fleetName), tags);
try {
Util.addAll(taggingService.getPrivateTags(leaderboardName, raceColumnName, fleetName), tags);
} catch (AuthorizationException e) {
// do nothing when user is not logged in
}
}
// remove revoked tags from result
tags.removeIf(tag -> tag.getRevokedAt() != null && tag.getRevokedAt().asMillis() != 0);
JSONArray jsonTags = serializer.serialize(tags);
response = Response.ok(jsonTags.toJSONString()).type(APPLICATION_JSON_UTF8).build();
} else {
response = Response.status(Status.INTERNAL_SERVER_ERROR).type(TEXT_PLAIN_UTF8)
.entity("Tagging Service not found!").build();
logger.warning("Tagging Service not found!");
} catch (RaceLogNotFoundException e) {
response = Response.status(Status.BAD_REQUEST).type(TEXT_PLAIN_UTF8).build();
} catch (Exception e) {
logger.warning("Could not load tags! " + e.getMessage());
response = Response.status(Status.INTERNAL_SERVER_ERROR).type(TEXT_PLAIN_UTF8).build();
}
return response;
}
@@ -106,11 +109,12 @@ public class TagsResource extends AbstractSailingServerResource {
* @param visible
* default is <code>false</code>
*
* @return status 201 (created) if creation was successful, otherwise 400 (bad request)
* @return status 201 (created) if creation was successful, otherwise 400 (bad request) 401 (unauthorized) or 500
* (internal server error)
* @see TaggingService#addTag(String, String, String, String, String, String, boolean, TimePoint)
*/
@POST
@Produces({ APPLICATION_JSON_UTF8, TEXT_PLAIN_UTF8 })
@Produces({ TEXT_PLAIN_UTF8 })
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response createTag(@Context UriInfo uriInfo,
@PathParam(RaceLogServletConstants.PARAMS_LEADERBOARD_NAME) String leaderboardName,
@@ -119,15 +123,18 @@ public class TagsResource extends AbstractSailingServerResource {
@FormParam("comment") String comment, @FormParam("image") String imageURL,
@FormParam("public") boolean visibleForPublic, @FormParam("raceTimepoint") long raceTimepoint) {
Response response;
TaggingService taggingService = getService().getTaggingService();
boolean successful = taggingService.addTag(leaderboardName, raceColumnName, fleetName, tag, comment, imageURL,
visibleForPublic, new MillisecondsTimePoint(raceTimepoint));
if (successful) {
final TaggingService taggingService = getService().getTaggingService();
try {
taggingService.addTag(leaderboardName, raceColumnName, fleetName, tag, comment, imageURL, visibleForPublic,
new MillisecondsTimePoint(raceTimepoint));
response = Response.created(uriInfo.getRequestUri()).build();
} else {
String errorMessage = taggingService.getLastErrorCode().getMessage();
response = Response.status(Status.BAD_REQUEST).type(TEXT_PLAIN_UTF8).entity(errorMessage).build();
logger.warning("Could not save tag! " + errorMessage);
} catch (IllegalArgumentException | RaceLogNotFoundException | TagAlreadyExistsException e) {
response = Response.status(Status.BAD_REQUEST).type(TEXT_PLAIN_UTF8).build();
} catch (AuthorizationException e) {
response = Response.status(Status.UNAUTHORIZED).type(TEXT_PLAIN_UTF8).build();
} catch (Exception e) {
logger.warning("Could not save tag! " + e.getMessage());
response = Response.status(Status.INTERNAL_SERVER_ERROR).type(TEXT_PLAIN_UTF8).build();
}
return response;
}
@@ -135,12 +142,13 @@ public class TagsResource extends AbstractSailingServerResource {
/**
* Removes tag given as json string.
*
* @return status 204 (no content) if deletion was successful, otherwise 400 (bad request)
* @return status 204 (no content) if deletion was successful, otherwise 400 (bad request), 401 (unauthorized) or
* 500 (internal server error)
* @see TagDTO
* @see TaggingService#removeTag(String, String, String, TagDTO)
*/
@DELETE
@Produces({ APPLICATION_JSON_UTF8, TEXT_PLAIN_UTF8 })
@Produces({ TEXT_PLAIN_UTF8 })
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response deleteTag(@PathParam(RaceLogServletConstants.PARAMS_LEADERBOARD_NAME) String leaderboardName,
@PathParam(RaceLogServletConstants.PARAMS_RACE_COLUMN_NAME) String raceColumnName,
@@ -149,13 +157,16 @@ public class TagsResource extends AbstractSailingServerResource {
Response response;
TagDTO tagToRemove = serializer.deserializeTag(tagJson);
TaggingService taggingService = getService().getTaggingService();
boolean successful = taggingService.removeTag(leaderboardName, raceColumnName, fleetName, tagToRemove);
if (successful) {
try {
taggingService.removeTag(leaderboardName, raceColumnName, fleetName, tagToRemove);
response = Response.noContent().build();
} else {
String errorMessage = taggingService.getLastErrorCode().getMessage();
response = Response.status(Status.BAD_REQUEST).type(TEXT_PLAIN_UTF8).entity(errorMessage).build();
logger.warning("Could not remove tag! " + errorMessage);
} catch (IllegalArgumentException | NotRevokableException | RaceLogNotFoundException e) {
response = Response.status(Status.BAD_REQUEST).type(TEXT_PLAIN_UTF8).build();
} catch (AuthorizationException e) {
response = Response.status(Status.UNAUTHORIZED).type(TEXT_PLAIN_UTF8).build();
} catch (Exception e) {
logger.warning("Could not remove tag! " + e.getMessage());
response = Response.status(Status.INTERNAL_SERVER_ERROR).type(TEXT_PLAIN_UTF8).build();
}
return response;
}
@@ -168,12 +179,13 @@ public class TagsResource extends AbstractSailingServerResource {
* @param tag
* may not be empty
*
* @return status 204 (no content) if update was successful, otherwise 400 (bad request)
* @return status 204 (no content) if update was successful, otherwise 400 (bad request), 401 (unauthorized) or 500
* (internal server error)
* @see TagDTO
* @see TaggingService#updateTag(String, String, String, TagDTO, String, String, String, boolean)
*/
@PUT
@Produces({ APPLICATION_JSON_UTF8, TEXT_PLAIN_UTF8 })
@Produces({ TEXT_PLAIN_UTF8 })
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response updateTag(@PathParam(RaceLogServletConstants.PARAMS_LEADERBOARD_NAME) String leaderboardName,
@PathParam(RaceLogServletConstants.PARAMS_RACE_COLUMN_NAME) String raceColumnName,
@@ -182,28 +194,32 @@ public class TagsResource extends AbstractSailingServerResource {
@FormParam("comment") String commentParam, @FormParam("image") String imageURLParam,
@FormParam("public") String visibleForPublicParam) {
Response response;
boolean successful = true;
TagDTO tagToUpdate = serializer.deserializeTag(tagJson);
TaggingService taggingService = getService().getTaggingService();
final TagDTO tagToUpdate = serializer.deserializeTag(tagJson);
final TaggingService taggingService = getService().getTaggingService();
// only call update method when any of the parameters needs to be changed
if (tagParam != null || commentParam != null || imageURLParam != null || visibleForPublicParam != null) {
// keep old values when no new values are provided
String tag = tagParam == null ? tagToUpdate.getTag() : tagParam;
String comment = commentParam == null ? tagToUpdate.getComment() : commentParam;
String imageURL = imageURLParam == null ? tagToUpdate.getImageURL() : imageURLParam;
boolean visibleForPublic = visibleForPublicParam == null ? tagToUpdate.isVisibleForPublic()
: visibleForPublicParam.equalsIgnoreCase("true") ? true : false;
successful = taggingService.updateTag(leaderboardName, raceColumnName, fleetName, tagToUpdate, tag, comment,
imageURL, visibleForPublic);
}
if (successful) {
response = Response.noContent().build();
String tag = (tagParam == null ? tagToUpdate.getTag() : tagParam);
String comment = (commentParam == null ? tagToUpdate.getComment() : commentParam);
String imageURL = (imageURLParam == null ? tagToUpdate.getImageURL() : imageURLParam);
boolean visibleForPublic = (visibleForPublicParam == null ? tagToUpdate.isVisibleForPublic()
: visibleForPublicParam.equalsIgnoreCase("true") ? true : false);
try {
taggingService.updateTag(leaderboardName, raceColumnName, fleetName, tagToUpdate, tag, comment,
imageURL, visibleForPublic);
response = Response.noContent().build();
} catch (IllegalArgumentException | NotRevokableException | RaceLogNotFoundException
| TagAlreadyExistsException e) {
response = Response.status(Status.BAD_REQUEST).type(TEXT_PLAIN_UTF8).build();
} catch (AuthorizationException e) {
response = Response.status(Status.UNAUTHORIZED).type(TEXT_PLAIN_UTF8).build();
} catch (Exception e) {
logger.warning("Could not update tag! " + e.getMessage());
response = Response.status(Status.INTERNAL_SERVER_ERROR).type(TEXT_PLAIN_UTF8).build();
}
} else {
String errorMessage = taggingService.getLastErrorCode().getMessage();
response = Response.status(Status.BAD_REQUEST).type(TEXT_PLAIN_UTF8).entity(errorMessage).build();
logger.warning("Could not update tag! " + errorMessage);
response = Response.noContent().build();
}
return response;
}
@@ -606,9 +606,16 @@ public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetch
PolarDataService getPolarDataService();
SimulationService getSimulationService();
/**
* {@link TaggingService} can be used to perform all CRUD operations on private and pulic
* {@link com.sap.sailing.domain.common.dto.TagDTO tags}. This service is used by the REST API and GWT client and
* needs to perform independant of the requesting resource.
*
* @return instance of TaggingService
*/
TaggingService getTaggingService();
RaceTracker getRaceTrackerById(Object id);
/**
@@ -25,7 +25,7 @@ public class TagDTODeSerializer {
public static final String FIELD_CREATED_AT = "createdAt";
public static final String FIELD_REVOKED_AT = "revokedAt";
private static final String UNIQUE_KEY_PREFIX = "Tags:";
private static final String UNIQUE_KEY_PREFIX = "sailing.tags.";
private final JSONParser parser = new JSONParser();
@@ -82,7 +82,7 @@ public class TagDTODeSerializer {
TimePoint raceTimePoint = deserilizeTimePoint((Long) (jsonObject.get(FIELD_RACE_TIMEPOINT)));
TimePoint createdAt = deserilizeTimePoint((Long) (jsonObject.get(FIELD_CREATED_AT)));
TimePoint revokedAt = deserilizeTimePoint((Long) (jsonObject.get(FIELD_REVOKED_AT)));
return new TagDTO(tag, comment, imageURL, username, visibleForPublic, raceTimePoint, createdAt, revokedAt);
return new TagDTO(tag, comment, imageURL, visibleForPublic, username, raceTimePoint, createdAt, revokedAt);
} catch (Exception e) {
return null;
}
@@ -2,56 +2,28 @@ package com.sap.sailing.server.tagging;
import java.util.List;
import org.apache.shiro.authz.AuthorizationException;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.abstractlog.NotRevokableException;
import com.sap.sailing.domain.common.dto.TagDTO;
import com.sap.sailing.domain.common.tagging.RaceLogNotFoundException;
import com.sap.sailing.domain.common.tagging.ServiceNotFoundException;
import com.sap.sailing.domain.common.tagging.TagAlreadyExistsException;
import com.sap.sse.common.TimePoint;
// TODO: Add Javadoc, what does TaggingService do?
// TODO: Replace error handling by throwing exceptions, see "topleveltranslations.json"
// TODO: Remove public at interfaces
// TODO: CommentTooLong
// TODO: Remove max length
// TODO: rename latestReceivedTagTime to match role
// TODO: remove entry in settings if there are no private tags for this race anymore
// TODO: rename keys to naming pattern (ssailing.tags....)
// TODO: see "translation_v2.json" for new translation files
// TODO: use document settings id for tags/tag-buttons/... as race identifier
/**
* This service is used to perform all CRUD operations on {@link TagDTO tags} and is used by the
* {@link com.sap.sailing.server.gateway.jaxrs.api.TagsResource REST API} for mobile apps as well as the GWT client via
* the {@link com.sap.sailing.gwt.ui.server.SailingServiceImpl SailingService}.
*
* @author Henri Kohlberg
*/
public interface TaggingService {
/**
* Enum used to identify issues.
*/
public enum ErrorCode {
UNKNOWN_ERROR("unknownError", "Unknown error"),
NOT_LOGGED_IN("notLoggedIn", "You are not logged in"),
MISSING_PERMISSIONS("missingPermissions", "Missing permissions"),
SECURITY_SERIVCE_NOT_FOUND("securityServiceNotFound", "Security service not found"),
RACELOG_NOT_FOUND("racelogNotFound", "Racelog not found"),
TAG_NOT_REVOKABLE("tagNotRevokable", "This tag cannot be revoked"),
TAG_ALREADY_EXISTS("tagAlreadyExists", "Tag does already exist, duplicated tags are not allowed"),
TAG_ALREADY_REMOVED("tagAlreadyRemoved", "Tag cannot be removed twice!"),
TAG_NOT_EMPTY("tagNotEmpty", "Tag may not be empty"),
TIMEPOINT_NOT_EMPTY("timepointNotEmpty", "Timepoint may not be empty"),
TAG_TOO_LONG("tagTooLong", "Tag is too long"),
COMMENT_TOO_LONG("commentToLong", "Comment is too long");
private final String code;
private final String message;
ErrorCode(String code, String message) {
this.code = code;
this.message = message;
}
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
}
/**
* Saves given properties as tag for given race. Checks if all parameters are valid and all required parameters are
* set.
@@ -74,10 +46,21 @@ public interface TaggingService {
* tag will be saved in {@link com.sap.sse.security.UserStore UserStore} (only visible for the creator)
* @param raceTimepoint
* timepoint in race when user created tag, must <b>NOT</b> be <code>null</code>
* @return <code>true</code> if tag was saved successfully, otherwise <code>false</code>
* @throws AuthorizationException
* thrown if user is not logged in or is missing permissions
* @throws IllegalArgumentException
* thrown if one of the required parameters has an invalid value
* @throws RaceLogNotFoundException
* thrown if racelog cannot be found (e.g. when <code>leaderboardName</code>,
* <code>raceColumnName</code> or <code>fleetName</code> are missing)
* @throws ServiceNotFoundException
* thrown if security service cannot be found
* @throws TagAlreadyExistsException
* thrown if tag already exists
*/
public boolean addTag(String leaderboardName, String raceColumnName, String fleetName, String tag, String comment,
String imageURL, boolean visibleForPublic, TimePoint raceTimepoint);
void addTag(String leaderboardName, String raceColumnName, String fleetName, String tag, String comment,
String imageURL, boolean visibleForPublic, TimePoint raceTimepoint) throws AuthorizationException,
IllegalArgumentException, RaceLogNotFoundException, ServiceNotFoundException, TagAlreadyExistsException;
/**
* Removes public {@link TagDTO tag} from {@link com.sap.sailing.domain.abstractlog.race.RaceLog RaceLog} and
@@ -91,9 +74,21 @@ public interface TaggingService {
* required to identify {@link RaceLog}, must <b>NOT</b> be <code>null</code>
* @param tag
* tag to remove
* @return <code>true</code> if tag was removed successfully, otherwise <code>false</code>
* @throws AuthorizationException
* thrown if user is not logged in or is missing permissions
* @throws IllegalArgumentException
* thrown if one of the required parameters has an invalid value
* @throws NotRevokableException
* thrown if tag is public and not revokable from racelog
* @throws RaceLogNotFoundException
* thrown if racelog cannot be found (e.g. when <code>leaderboardName</code>,
* <code>raceColumnName</code> or <code>fleetName</code> are missing)
* @throws ServiceNotFoundException
* thrown if security service cannot be found
*/
public boolean removeTag(String leaderboardName, String raceColumnName, String fleetName, TagDTO tag);
void removeTag(String leaderboardName, String raceColumnName, String fleetName, TagDTO tag)
throws AuthorizationException, IllegalArgumentException, NotRevokableException, RaceLogNotFoundException,
ServiceNotFoundException;
/**
* Updates given <code>tagToUpdate</code> with the given parameters <code>tag</code>, <code>comment</code>,
@@ -115,10 +110,24 @@ public interface TaggingService {
* new iamge URL
* @param visibleForPublic
* new privacy status
* @return <code>true</code> if tag was updated successfully, otherwise <code>false</code>
* @throws AuthorizationException
* thrown if user is not logged in or is missing permissions
* @throws IllegalArgumentException
* thrown if one of the required parameters has an invalid value
* @throws NotRevokableException
* thrown if tag is public and not revokable from racelog
* @throws TagAlreadyExistsException
* thrown if tag already exists
* @throws RaceLogNotFoundException
* thrown if racelog cannot be found (e.g. when <code>leaderboardName</code>,
* <code>raceColumnName</code> or <code>fleetName</code> are missing)
* @throws ServiceNotFoundException
* thrown if security service cannot be found
*/
public boolean updateTag(String leaderboardName, String raceColumnName, String fleetName, TagDTO tagToUpdate,
String tag, String comment, String imageURL, boolean visibleForPublic);
void updateTag(String leaderboardName, String raceColumnName, String fleetName, TagDTO tagToUpdate, String tag,
String comment, String imageURL, boolean visibleForPublic)
throws AuthorizationException, IllegalArgumentException, NotRevokableException, RaceLogNotFoundException,
ServiceNotFoundException, TagAlreadyExistsException;
/**
* Returns all public tags for the specified race.
@@ -131,18 +140,24 @@ public interface TaggingService {
* required to identify {@link RaceLog}, must <b>NOT</b> be <code>null</code>
* @return list of {@link TagDTO tags}, empty list in case an error occurs or there are no tags available but
* <b>never null</b>!
* @throws RaceLogNotFoundException
* thrown if racelog cannot be found (e.g. when <code>leaderboardName</code>,
* <code>raceColumnName</code> or <code>fleetName</code> are missing)
*/
public List<TagDTO> getPublicTags(String leaderboardName, String raceColumnName, String fleetName);
List<TagDTO> getPublicTags(String leaderboardName, String raceColumnName, String fleetName)
throws RaceLogNotFoundException;
/**
* Returns all public tags since the given <code>latestReceivedTagTime</code> for the specified race.
* Returns all public tags since the given <code>searchSinceTimePoint</code> for the specified race.
*
* @param raceIdentifier
* required to identify {@link RaceLog}, must <b>NOT</b> be <code>null</code>
* @param searchSince
* tags will only be returned if they got created after this time point
* @return list of {@link TagDTO tags}, empty list in case an error occurs or there are no tags available but
* <b>never null</b>!
*/
public List<TagDTO> getPublicTags(RegattaAndRaceIdentifier raceIdentifier, TimePoint latestReceivedTagTime);
List<TagDTO> getPublicTags(RegattaAndRaceIdentifier raceIdentifier, TimePoint searchSince);
/**
* Returns all private tags of current user for the specified race.
@@ -155,15 +170,9 @@ public interface TaggingService {
* required to identify {@link RaceLog}, must <b>NOT</b> be <code>null</code>
* @return list of {@link TagDTO tags}, empty list in case an error occurs or there are no tags available but
* <b>never null</b>!
* @throws ServiceNotFoundException
* thrown if security service cannot be found
*/
public List<TagDTO> getPrivateTags(String leaderboardName, String raceColumnName, String fleetName);
/**
* Returns the last error code of the current user. Needs to be converted into error message to display this message
* to the user.
*
* @return last {@link ErrorCode error code} which occured if error is known, otherwise
* {@link ErrorCode#UNKNOWN_ERROR unknown error}
*/
public ErrorCode getLastErrorCode();
List<TagDTO> getPrivateTags(String leaderboardName, String raceColumnName, String fleetName)
throws ServiceNotFoundException;
}
@@ -1,9 +1,7 @@
package com.sap.sailing.server.tagging;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.AuthorizationException;
@@ -19,6 +17,9 @@ import com.sap.sailing.domain.common.abstractlog.NotRevokableException;
import com.sap.sailing.domain.common.dto.TagDTO;
import com.sap.sailing.domain.common.security.Permission;
import com.sap.sailing.domain.common.security.Permission.Mode;
import com.sap.sailing.domain.common.tagging.RaceLogNotFoundException;
import com.sap.sailing.domain.common.tagging.ServiceNotFoundException;
import com.sap.sailing.domain.common.tagging.TagAlreadyExistsException;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.server.RacingEventService;
import com.sap.sse.common.TimePoint;
@@ -30,263 +31,209 @@ public class TaggingServiceImpl implements TaggingService {
private final RacingEventService racingService;
private final TagDTODeSerializer serializer;
private final Map<Subject, ErrorCode> lastErrorCodes;
public TaggingServiceImpl(RacingEventService racingService) {
this.racingService = racingService;
serializer = new TagDTODeSerializer();
lastErrorCodes = new HashMap<Subject, ErrorCode>();
}
private void setLastErrorCode(ErrorCode errorCode) {
lastErrorCodes.put(SecurityUtils.getSubject(), errorCode);
}
private String getCurrentUsername() {
String result = null;
/**
* Returns current username.
*
* @throws AuthorizationException
* @return username of current user
*/
private String getCurrentUsername() throws AuthorizationException {
Object principal = SecurityUtils.getSubject().getPrincipal();
if (principal != null) {
result = principal.toString();
} else {
setLastErrorCode(ErrorCode.NOT_LOGGED_IN);
if (principal == null) {
throw new AuthorizationException();
}
return result;
return principal.toString();
}
private SecurityService getSecurityService() {
/**
* Returns instance of {@link SecurityService} to access the user store.
*
* @return instance of {@link SecurityService}
* @throws ServiceNotFoundException
*/
private SecurityService getSecurityService() throws ServiceNotFoundException {
SecurityService securityService = Activator.getSecurityService();
if (securityService == null) {
setLastErrorCode(ErrorCode.SECURITY_SERIVCE_NOT_FOUND);
throw new ServiceNotFoundException("Security service not found!");
}
return securityService;
}
private boolean addPublicTag(String leaderboardName, String raceColumnName, String fleetName, String tag,
String comment, String imageURL, TimePoint raceTimepoint) {
boolean successful = true;
try {
// TODO: As soon as permission-vertical branch got merged into master, apply
// new permission system at this permission check (see bug 4104, comment 9)
// functionality: Check if user has the permission to add RaceLogEvents to RaceLog.
SecurityUtils.getSubject().checkPermission(
Permission.LEADERBOARD.getStringPermissionForObjects(Mode.UPDATE, leaderboardName));
RaceLog raceLog = racingService.getRaceLog(leaderboardName, raceColumnName, fleetName);
if (raceLog == null) {
setLastErrorCode(ErrorCode.RACELOG_NOT_FOUND);
successful = false;
} else {
// check if tag already exists
boolean alreadyExists = false;
List<TagDTO> publicTags = getPublicTags(leaderboardName, raceColumnName, fleetName);
for (TagDTO publicTag : publicTags) {
// ignore revoked tags as TagDTO.equals() does ignore revokedAt timepoint
if (publicTag.getRevokedAt() == null && publicTag.equals(tag, comment, imageURL,
racingService.getServerAuthor().getName(), true, raceTimepoint)) {
alreadyExists = true;
break;
}
}
if (alreadyExists) {
setLastErrorCode(ErrorCode.TAG_ALREADY_EXISTS);
successful = false;
} else {
raceLog.add(new RaceLogTagEventImpl(tag, comment, imageURL, raceTimepoint,
racingService.getServerAuthor(), raceLog.getCurrentPassId()));
}
}
} catch (AuthorizationException e) {
setLastErrorCode(ErrorCode.MISSING_PERMISSIONS);
successful = false;
}
return successful;
}
private boolean addPrivateTag(String leaderboardName, String raceColumnName, String fleetName, String tag,
String comment, String imageURL, TimePoint raceTimepoint) {
boolean successful = true;
SecurityService securityService = Activator.getSecurityService();
if (securityService == null) {
setLastErrorCode(ErrorCode.SECURITY_SERIVCE_NOT_FOUND);
successful = false;
} else {
Subject subject = SecurityUtils.getSubject();
if (subject.getPrincipal() != null) {
String username = subject.getPrincipal().toString();
TagDTODeSerializer serializer = new TagDTODeSerializer();
String key = serializer.generateUniqueKey(leaderboardName, raceColumnName, fleetName);
String privateTagsJson = securityService.getPreference(username, key);
List<TagDTO> privateTags = serializer.deserializeTags(privateTagsJson);
TagDTO tagToAdd = new TagDTO(tag, comment, imageURL, username, false, raceTimepoint,
MillisecondsTimePoint.now());
if (privateTags.contains(tagToAdd)) {
setLastErrorCode(ErrorCode.TAG_ALREADY_EXISTS);
successful = false;
} else {
privateTags.add(tagToAdd);
securityService.setPreference(username, key, serializer.serializeTags(privateTags));
}
} else {
setLastErrorCode(ErrorCode.NOT_LOGGED_IN);
successful = false;
}
}
return successful;
}
private boolean removePublicTag(String leaderboardName, String raceColumnName, String fleetName, TagDTO tag) {
boolean successful = true;
private void addPublicTag(String leaderboardName, String raceColumnName, String fleetName, String tag,
String comment, String imageURL, TimePoint raceTimepoint)
throws RaceLogNotFoundException, TagAlreadyExistsException {
// TODO: As soon as permission-vertical branch got merged into master, apply
// new permission system at this permission check (see bug 4104, comment 9)
// functionality: Check if user has the permission to add RaceLogEvents to RaceLog.
SecurityUtils.getSubject()
.checkPermission(Permission.LEADERBOARD.getStringPermissionForObjects(Mode.UPDATE, leaderboardName));
RaceLog raceLog = racingService.getRaceLog(leaderboardName, raceColumnName, fleetName);
if (raceLog == null) {
setLastErrorCode(ErrorCode.RACELOG_NOT_FOUND);
successful = false;
} else {
ReadonlyRaceState raceState = ReadonlyRaceStateImpl.getOrCreate(racingService, raceLog);
Iterable<RaceLogTagEvent> foundTagEvents = raceState.getTagEvents();
for (RaceLogTagEvent tagEvent : foundTagEvents) {
if (tagEvent.getRevokedAt() != null) {
continue;
} else if (tagEvent.getTag().equals(tag.getTag()) && tagEvent.getComment().equals(tag.getComment())
&& tagEvent.getImageURL().equals(tag.getImageURL())
&& tagEvent.getUsername().equals(tag.getUsername())
&& tagEvent.getLogicalTimePoint().equals(tag.getRaceTimepoint())) {
try {
// TODO: As soon as permission-vertical branch got merged into master, apply
// new permission system at this permission check (see bug 4104, comment 9)
// functionality: Check if user has the permission to delete tag from RaceLog (same user or
// admin).
Subject subject = SecurityUtils.getSubject();
subject.checkPermission(
Permission.LEADERBOARD.getStringPermissionForObjects(Mode.UPDATE, leaderboardName));
if ((subject.getPrincipal() != null && subject.getPrincipal().equals(tag.getUsername()))
|| subject.hasRole("admin")) {
raceLog.revokeEvent(tagEvent.getAuthor(), tagEvent, "Revoked");
} else {
setLastErrorCode(ErrorCode.MISSING_PERMISSIONS);
successful = false;
}
} catch (AuthorizationException e) {
setLastErrorCode(ErrorCode.MISSING_PERMISSIONS);
successful = false;
} catch (NotRevokableException e) {
setLastErrorCode(ErrorCode.TAG_NOT_REVOKABLE);
successful = false;
}
break;
}
throw new RaceLogNotFoundException();
}
// check if tag already exists
List<TagDTO> publicTags = getPublicTags(leaderboardName, raceColumnName, fleetName);
for (TagDTO publicTag : publicTags) {
// ignore revoked tags as TagDTO.equals() does ignore revokedAt timepoint
if (publicTag.getRevokedAt() == null && publicTag.equals(tag, comment, imageURL, true,
racingService.getServerAuthor().getName(), raceTimepoint)) {
throw new TagAlreadyExistsException();
}
}
return successful;
raceLog.add(new RaceLogTagEventImpl(tag, comment, imageURL, raceTimepoint, racingService.getServerAuthor(),
raceLog.getCurrentPassId()));
}
private boolean removePrivateTag(String leaderboardName, String raceColumnName, String fleetName, TagDTO tag) {
boolean successful = true;
private void addPrivateTag(String leaderboardName, String raceColumnName, String fleetName, String tag,
String comment, String imageURL, TimePoint raceTimepoint)
throws AuthorizationException, ServiceNotFoundException, TagAlreadyExistsException {
SecurityService securityService = Activator.getSecurityService();
if (securityService == null) {
throw new ServiceNotFoundException("Security service not found!");
}
Subject subject = SecurityUtils.getSubject();
if (subject.getPrincipal() == null) {
throw new AuthorizationException();
}
String username = subject.getPrincipal().toString();
TagDTODeSerializer serializer = new TagDTODeSerializer();
String key = serializer.generateUniqueKey(leaderboardName, raceColumnName, fleetName);
String privateTagsJson = securityService.getPreference(username, key);
List<TagDTO> privateTags = serializer.deserializeTags(privateTagsJson);
TagDTO tagToAdd = new TagDTO(tag, comment, imageURL, false, username, raceTimepoint,
MillisecondsTimePoint.now());
if (privateTags.contains(tagToAdd)) {
throw new TagAlreadyExistsException();
}
privateTags.add(tagToAdd);
securityService.setPreference(username, key, serializer.serializeTags(privateTags));
}
private void removePublicTag(String leaderboardName, String raceColumnName, String fleetName, TagDTO tag)
throws AuthorizationException, NotRevokableException, RaceLogNotFoundException {
RaceLog raceLog = racingService.getRaceLog(leaderboardName, raceColumnName, fleetName);
if (raceLog == null) {
throw new RaceLogNotFoundException();
}
ReadonlyRaceState raceState = ReadonlyRaceStateImpl.getOrCreate(racingService, raceLog);
Iterable<RaceLogTagEvent> foundTagEvents = raceState.getTagEvents();
for (RaceLogTagEvent tagEvent : foundTagEvents) {
if (tagEvent.getRevokedAt() != null) {
continue;
} else if (tagEvent.getTag().equals(tag.getTag()) && tagEvent.getComment().equals(tag.getComment())
&& tagEvent.getImageURL().equals(tag.getImageURL())
&& tagEvent.getUsername().equals(tag.getUsername())
&& tagEvent.getLogicalTimePoint().equals(tag.getRaceTimepoint())) {
// TODO: As soon as permission-vertical branch got merged into master, apply
// new permission system at this permission check (see bug 4104, comment 9)
// functionality: Check if user has the permission to delete tag from RaceLog (same user or
// admin).
Subject subject = SecurityUtils.getSubject();
subject.checkPermission(
Permission.LEADERBOARD.getStringPermissionForObjects(Mode.UPDATE, leaderboardName));
if (!(subject.getPrincipal() != null && subject.getPrincipal().equals(tag.getUsername()))
|| subject.hasRole("admin")) {
throw new AuthorizationException();
}
raceLog.revokeEvent(tagEvent.getAuthor(), tagEvent, "Revoked");
break;
}
}
}
private void removePrivateTag(String leaderboardName, String raceColumnName, String fleetName, TagDTO tag)
throws AuthorizationException, ServiceNotFoundException {
String username = getCurrentUsername();
if (username == null) {
setLastErrorCode(ErrorCode.NOT_LOGGED_IN);
successful = false;
} else {
List<TagDTO> privateTags = getPrivateTags(leaderboardName, raceColumnName, fleetName);
privateTags.remove(tag);
SecurityService securityService = getSecurityService();
String key = serializer.generateUniqueKey(leaderboardName, raceColumnName, fleetName);
// error code will be set during collection of required data
if (username != null && securityService != null && key != null) {
List<TagDTO> privateTags = getPrivateTags(leaderboardName, raceColumnName, fleetName);
privateTags.remove(tag);
SecurityService securityService = getSecurityService();
String key = serializer.generateUniqueKey(leaderboardName, raceColumnName, fleetName);
// error code will be set during collection of required data
if (username != null && securityService != null && key != null) {
if (privateTags.isEmpty()) {
securityService.unsetPreference(username, key);
} else {
securityService.setPreference(username, key, serializer.serializeTags(privateTags));
}
}
return successful;
}
@Override
public boolean addTag(String leaderboardName, String raceColumnName, String fleetName, String tag, String comment,
String imageURL, boolean visibleForPublic, TimePoint raceTimepoint) {
boolean successful;
public void addTag(String leaderboardName, String raceColumnName, String fleetName, String tag, String comment,
String imageURL, boolean visibleForPublic, TimePoint raceTimepoint) throws AuthorizationException,
IllegalArgumentException, RaceLogNotFoundException, ServiceNotFoundException, TagAlreadyExistsException {
// prefill optional parameters
comment = comment == null ? "" : comment;
imageURL = imageURL == null ? "" : imageURL;
// check all parameters for validity
if (tag == null || tag.isEmpty()) {
setLastErrorCode(ErrorCode.TAG_NOT_EMPTY);
successful = false;
} else if (tag.length() > TagDTO.MAX_TAG_LENGTH) {
setLastErrorCode(ErrorCode.TAG_TOO_LONG);
successful = false;
} else if (comment.length() > TagDTO.MAX_COMMENT_LENGTH) {
setLastErrorCode(ErrorCode.COMMENT_TOO_LONG);
successful = false;
} else if (raceTimepoint == null || raceTimepoint.asMillis() == 0) {
throw new IllegalArgumentException("Tag may not be empty!");
}
if (raceTimepoint == null || raceTimepoint.asMillis() == 0) {
// TODO: Check if timepoint is near start/end of race (+/- x%)
setLastErrorCode(ErrorCode.TIMEPOINT_NOT_EMPTY);
successful = false;
throw new IllegalArgumentException("Timepoint may not be empty!");
}
if (visibleForPublic) {
addPublicTag(leaderboardName, raceColumnName, fleetName, tag, comment, imageURL, raceTimepoint);
} else {
// all parameters are valid => save tag
if (visibleForPublic) {
successful = addPublicTag(leaderboardName, raceColumnName, fleetName, tag, comment, imageURL,
raceTimepoint);
} else {
successful = addPrivateTag(leaderboardName, raceColumnName, fleetName, tag, comment, imageURL,
raceTimepoint);
}
addPrivateTag(leaderboardName, raceColumnName, fleetName, tag, comment, imageURL, raceTimepoint);
}
return successful;
}
@Override
public boolean removeTag(String leaderboardName, String raceColumnName, String fleetName, TagDTO tag) {
boolean successful = true;
// check all parameters for validity
public void removeTag(String leaderboardName, String raceColumnName, String fleetName, TagDTO tag)
throws AuthorizationException, IllegalArgumentException, NotRevokableException, RaceLogNotFoundException,
ServiceNotFoundException {
if (tag == null) {
setLastErrorCode(ErrorCode.TAG_NOT_EMPTY);
successful = false;
throw new IllegalArgumentException("Tag may not be empty!");
}
if (tag.isVisibleForPublic()) {
removePublicTag(leaderboardName, raceColumnName, fleetName, tag);
} else {
// all parameters are valid => remove tag
if (tag.isVisibleForPublic()) {
successful = removePublicTag(leaderboardName, raceColumnName, fleetName, tag);
} else {
successful = removePrivateTag(leaderboardName, raceColumnName, fleetName, tag);
}
removePrivateTag(leaderboardName, raceColumnName, fleetName, tag);
}
return successful;
}
@Override
public boolean updateTag(String leaderboardName, String raceColumnName, String fleetName, TagDTO tagToUpdate,
String tag, String comment, String imageURL, boolean visibleForPublic) {
boolean successful = true;
public void updateTag(String leaderboardName, String raceColumnName, String fleetName, TagDTO tagToUpdate,
String tag, String comment, String imageURL, boolean visibleForPublic)
throws AuthorizationException, IllegalArgumentException, NotRevokableException, RaceLogNotFoundException,
ServiceNotFoundException, TagAlreadyExistsException {
String username = getCurrentUsername();
if (username != null && tagToUpdate.getUsername().equals(username)) {
if (removeTag(leaderboardName, raceColumnName, fleetName, tagToUpdate)) {
successful = addTag(leaderboardName, raceColumnName, fleetName, tag, comment, imageURL,
visibleForPublic, tagToUpdate.getRaceTimepoint());
} else {
successful = false;
}
removeTag(leaderboardName, raceColumnName, fleetName, tagToUpdate);
addTag(leaderboardName, raceColumnName, fleetName, tag, comment, imageURL, visibleForPublic,
tagToUpdate.getRaceTimepoint());
}
return successful;
}
@Override
public List<TagDTO> getPublicTags(String leaderboardName, String raceColumnName, String fleetName) {
public List<TagDTO> getPublicTags(String leaderboardName, String raceColumnName, String fleetName)
throws RaceLogNotFoundException {
final List<TagDTO> result = new ArrayList<TagDTO>();
RaceLog raceLog = racingService.getRaceLog(leaderboardName, raceColumnName, fleetName);
if (raceLog == null) {
setLastErrorCode(ErrorCode.RACELOG_NOT_FOUND);
} else {
ReadonlyRaceState raceState = ReadonlyRaceStateImpl.getOrCreate(racingService, raceLog);
Iterable<RaceLogTagEvent> foundTagEvents = raceState.getTagEvents();
for (RaceLogTagEvent tagEvent : foundTagEvents) {
result.add(new TagDTO(tagEvent.getTag(), tagEvent.getComment(), tagEvent.getImageURL(),
tagEvent.getUsername(), true, tagEvent.getLogicalTimePoint(), tagEvent.getCreatedAt(),
tagEvent.getRevokedAt()));
}
throw new RaceLogNotFoundException();
}
ReadonlyRaceState raceState = ReadonlyRaceStateImpl.getOrCreate(racingService, raceLog);
Iterable<RaceLogTagEvent> foundTagEvents = raceState.getTagEvents();
for (RaceLogTagEvent tagEvent : foundTagEvents) {
result.add(new TagDTO(tagEvent.getTag(), tagEvent.getComment(), tagEvent.getImageURL(), true,
tagEvent.getUsername(), tagEvent.getLogicalTimePoint(), tagEvent.getCreatedAt(),
tagEvent.getRevokedAt()));
}
return result;
}
@Override
public List<TagDTO> getPublicTags(RegattaAndRaceIdentifier raceIdentifier, TimePoint latestReceivedTagTime) {
public List<TagDTO> getPublicTags(RegattaAndRaceIdentifier raceIdentifier, TimePoint searchSince) {
final List<TagDTO> result = new ArrayList<TagDTO>();
TrackedRace trackedRace = racingService.getExistingTrackedRace(raceIdentifier);
Iterable<RaceLog> raceLogs = trackedRace.getAttachedRaceLogs();
@@ -294,13 +241,13 @@ public class TaggingServiceImpl implements TaggingService {
ReadonlyRaceState raceState = ReadonlyRaceStateImpl.getOrCreate(racingService, raceLog);
Iterable<RaceLogTagEvent> foundTagEvents = raceState.getTagEvents();
for (RaceLogTagEvent tagEvent : foundTagEvents) {
if ((latestReceivedTagTime == null && tagEvent.getRevokedAt() == null)
|| (latestReceivedTagTime != null && tagEvent.getRevokedAt() == null
&& tagEvent.getCreatedAt().after(latestReceivedTagTime))
|| (latestReceivedTagTime != null && tagEvent.getRevokedAt() != null
&& tagEvent.getRevokedAt().after(latestReceivedTagTime))) {
result.add(new TagDTO(tagEvent.getTag(), tagEvent.getComment(), tagEvent.getImageURL(),
tagEvent.getUsername(), true, tagEvent.getLogicalTimePoint(), tagEvent.getCreatedAt(),
if ((searchSince == null && tagEvent.getRevokedAt() == null)
|| (searchSince != null && tagEvent.getRevokedAt() == null
&& tagEvent.getCreatedAt().after(searchSince))
|| (searchSince != null && tagEvent.getRevokedAt() != null
&& tagEvent.getRevokedAt().after(searchSince))) {
result.add(new TagDTO(tagEvent.getTag(), tagEvent.getComment(), tagEvent.getImageURL(), true,
tagEvent.getUsername(), tagEvent.getLogicalTimePoint(), tagEvent.getCreatedAt(),
tagEvent.getRevokedAt()));
}
}
@@ -309,27 +256,13 @@ public class TaggingServiceImpl implements TaggingService {
}
@Override
public List<TagDTO> getPrivateTags(String leaderboardName, String raceColumnName, String fleetName) {
public List<TagDTO> getPrivateTags(String leaderboardName, String raceColumnName, String fleetName)
throws AuthorizationException, ServiceNotFoundException {
final List<TagDTO> result = new ArrayList<TagDTO>();
SecurityService securityService = getSecurityService();
if (securityService != null) {
String username = getCurrentUsername();
if (username != null) {
String key = serializer.generateUniqueKey(leaderboardName, raceColumnName, fleetName);
String privateTagsJson = securityService.getPreference(username, key);
List<TagDTO> privateTags = serializer.deserializeTags(privateTagsJson);
result.addAll(privateTags);
}
}
String key = serializer.generateUniqueKey(leaderboardName, raceColumnName, fleetName);
String privateTagsJson = getSecurityService().getPreference(getCurrentUsername(), key);
List<TagDTO> privateTags = serializer.deserializeTags(privateTagsJson);
result.addAll(privateTags);
return result;
}
@Override
public ErrorCode getLastErrorCode() {
ErrorCode lastErrorCode = lastErrorCodes.get(SecurityUtils.getSubject());
if (lastErrorCode == null) {
lastErrorCode = ErrorCode.UNKNOWN_ERROR;
}
return lastErrorCode;
}
}