diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AbstractSailingServerResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AbstractSailingServerResource.java index e2c844448ee..936faae6193 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AbstractSailingServerResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/AbstractSailingServerResource.java @@ -96,14 +96,4 @@ public abstract class AbstractSailingServerResource { bigDecimal = bigDecimal.setScale(places, RoundingMode.HALF_UP); return bigDecimal.doubleValue(); } - - /** - * Restores original URL parameter which uses the escape "__" for "/" - * - * @param toRestore url parameter as string containing "__" instead of "/" - * @return restored url parameter containing "/" instead of "__" - */ - protected String restoreEscapedParameter(String toRestore) { - return toRestore != null && toRestore.contains(SLASH_ENCODING) ? toRestore.replace(SLASH_ENCODING, "/") : toRestore; - } } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/TagsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/TagsResource.java index 13083e932f8..91bdb6bda5c 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/TagsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/TagsResource.java @@ -4,106 +4,177 @@ import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.FormParam; import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; import javax.ws.rs.Path; -import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.UriInfo; -import org.apache.shiro.SecurityUtils; -import org.apache.shiro.subject.Subject; import org.json.simple.JSONArray; -import org.json.simple.parser.JSONParser; -import org.json.simple.parser.ParseException; -import com.sap.sailing.domain.abstractlog.race.RaceLog; -import com.sap.sailing.domain.abstractlog.race.RaceLogTagEvent; -import com.sap.sailing.domain.abstractlog.race.analyzing.impl.TagFinder; import com.sap.sailing.domain.common.dto.TagDTO; +import com.sap.sailing.domain.common.racelog.RaceLogServletConstants; import com.sap.sailing.server.gateway.jaxrs.AbstractSailingServerResource; import com.sap.sailing.server.tagging.TagDTODeSerializer; -import com.sap.sse.security.SecurityService; -import com.sap.sse.security.UserStore; +import com.sap.sailing.server.tagging.TaggingService; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; +import com.sap.sse.common.impl.MillisecondsTimePoint; @Path("/v1/tags") public class TagsResource extends AbstractSailingServerResource { private static final Logger logger = Logger.getLogger(TagsResource.class.getName()); + private static final String APPLICATION_JSON_UTF8 = MediaType.APPLICATION_JSON + ";charset=UTF-8"; + private static final String TEXT_PLAIN_UTF8 = MediaType.TEXT_PLAIN + ";charset=UTF-8"; private final TagDTODeSerializer serializer; - // private final boolean enforceSecurityChecks; public TagsResource() { - this(true); - } - - public TagsResource(boolean enforceSecurityChecks) { - // this.enforceSecurityChecks = enforceSecurityChecks; serializer = new TagDTODeSerializer(); } /** - * Loads public tags from {@link RaceLog} and private tags from {@link UserStore} in case current user is logged in. - * Only loads private tags when parameters leaderboardName, raceColumnName and - * fleetName can identify {@link RaceLog}. + * Loads public tags from {@link com.sap.sailing.domain.abstractlog.race.RaceLog RaceLog} and private tags from + * {@link com.sap.sse.security.UserStore UserStore} in case current user is logged in. Only loads tags when + * parameters leaderboardName, raceColumnName and fleetName can identify + * {@link com.sap.sailing.domain.abstractlog.race.RaceLog RaceLog}. + * + * @return status 200 (including tags) if request was successful, otherwise 400 (bad request) or 500 (internal + * server error) */ @GET - @Produces("application/json;charset=UTF-8") - @Path("/{leaderboardName}/{raceColumnName}/{fleetName}") - public Response getTags(@PathParam("leaderboardName") String leaderboardNameParameter, - @PathParam("raceColumnName") String raceColumnNameParameter, - @PathParam("fleetName") String fleetNameParameter) { - // restore escaped parameters - String leaderboardName = restoreEscapedParameter(leaderboardNameParameter); - String raceColumnName = restoreEscapedParameter(raceColumnNameParameter); - String fleetName = restoreEscapedParameter(fleetNameParameter); + @Produces({ APPLICATION_JSON_UTF8, TEXT_PLAIN_UTF8 }) + public Response getTags(@QueryParam(RaceLogServletConstants.PARAMS_LEADERBOARD_NAME) String leaderboardName, + @QueryParam(RaceLogServletConstants.PARAMS_RACE_COLUMN_NAME) String raceColumnName, + @QueryParam(RaceLogServletConstants.PARAMS_RACE_FLEET_NAME) String fleetName) { + // TODO: Allow to get only public, only private tags or both of them + // TODO: Allow to get only unrevoked tags + Response response; + TaggingService taggingService = getService().getTaggingService(); + if (taggingService != null) { + List tags = new ArrayList(); - // default response - Response response = Response.noContent().build(); - List tags = new ArrayList(); + List publicTags = taggingService.getPublicTags(leaderboardName, raceColumnName, fleetName); + Util.addAll(publicTags, tags); - // check if RaceLog is available and all parameters are set - RaceLog raceLog = getService().getRaceLog(leaderboardName, raceColumnName, fleetName); - if (raceLog == null) { - response = Response.status(Status.PRECONDITION_FAILED) - .entity("RaceLog not found! Check parameters leaderboardName, raceColumnName and fleetName") - .build(); - } else { - // load public tags - TagFinder finder = new TagFinder(raceLog, true); - List publicTags = finder.analyze(); - for (RaceLogTagEvent tagEvent : publicTags) { - tags.add(new TagDTO(tagEvent.getTag(), tagEvent.getComment(), tagEvent.getImageURL(), - tagEvent.getUsername(), true, tagEvent.getLogicalTimePoint(), tagEvent.getCreatedAt())); - } + List privateTags = taggingService.getPrivateTags(leaderboardName, raceColumnName, fleetName); + Util.addAll(privateTags, tags); - // load private tags - Subject subject = SecurityUtils.getSubject(); - if (subject.getPrincipal() != null) { - String username = subject.getPrincipal().toString(); - SecurityService securityService = getService(SecurityService.class); - - // TODO: check preferences key in home and set private tag - String preference = securityService.getPreference(username, - serializer.generateUniqueKey(leaderboardName, raceColumnName, fleetName)); - if (preference != null) { - try { - JSONParser jsonParser = new JSONParser(); - JSONArray jsonArray = (JSONArray) jsonParser.parse(preference); - List privateTags = serializer.deserialize(jsonArray); - tags.addAll(privateTags); - } catch (ParseException e) { - logger.warning("Could not parse private tags received from JSON in UserStore!"); - } - } - } - - // convert tags to JSON JSONArray jsonTags = serializer.serialize(tags); - response = Response.ok(jsonTags.toJSONString()).build(); + 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!"); } - return response; } + + /** + * Saves new tag. + * + * @param tag + * may not be empty + * @param visible + * default is false + * + * @return status 201 (created) if creation was successful, otherwise 400 (bad request) + * @see TaggingService#addTag(String, String, String, String, String, String, boolean, TimePoint) + */ + @POST + @Produces({ APPLICATION_JSON_UTF8, TEXT_PLAIN_UTF8 }) + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + public Response createTag(@Context UriInfo uriInfo, + @QueryParam(RaceLogServletConstants.PARAMS_LEADERBOARD_NAME) String leaderboardName, + @QueryParam(RaceLogServletConstants.PARAMS_RACE_COLUMN_NAME) String raceColumnName, + @QueryParam(RaceLogServletConstants.PARAMS_RACE_FLEET_NAME) String fleetName, @FormParam("tag") String tag, + @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) { + 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); + } + return response; + } + + /** + * Removes tag given as json string. + * + * @return status 204 (no content) if deletion was successful, otherwise 400 (bad request) + * @see TagDTO + * @see TaggingService#removeTag(String, String, String, TagDTO) + */ + @DELETE + @Produces({ APPLICATION_JSON_UTF8, TEXT_PLAIN_UTF8 }) + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + public Response deleteTag(@QueryParam(RaceLogServletConstants.PARAMS_LEADERBOARD_NAME) String leaderboardName, + @QueryParam(RaceLogServletConstants.PARAMS_RACE_COLUMN_NAME) String raceColumnName, + @QueryParam(RaceLogServletConstants.PARAMS_RACE_FLEET_NAME) String fleetName, + @FormParam("tag_json") String tagJson) { + Response response; + TagDTO tagToRemove = serializer.deserializeTag(tagJson); + TaggingService taggingService = getService().getTaggingService(); + boolean successful = taggingService.removeTag(leaderboardName, raceColumnName, fleetName, tagToRemove); + if (successful) { + 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); + } + return response; + } + + /** + * Updates tag. + * + * @param tag + * may not be empty + * @param visible + * default is false + * + * @return status 204 (no content) if update was successful, otherwise 400 (bad request) + * @see TagDTO + * @see TaggingService#updateTag(String, String, String, TagDTO, String, String, String, boolean) + */ + @PUT + @Produces({ APPLICATION_JSON_UTF8, TEXT_PLAIN_UTF8 }) + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + public Response updateTag(@QueryParam(RaceLogServletConstants.PARAMS_LEADERBOARD_NAME) String leaderboardName, + @QueryParam(RaceLogServletConstants.PARAMS_RACE_COLUMN_NAME) String raceColumnName, + @QueryParam(RaceLogServletConstants.PARAMS_RACE_FLEET_NAME) String fleetName, + @FormParam("tag_json") String tagJson, @FormParam("tag") String tag, @FormParam("comment") String comment, + @FormParam("image") String imageURL, @FormParam("public") boolean visibleForPublic) { + Response response; + TagDTO tagToUpdate = serializer.deserializeTag(tagJson); + TaggingService taggingService = getService().getTaggingService(); + boolean successful = taggingService.updateTag(leaderboardName, raceColumnName, fleetName, tagToUpdate, tag, + comment, imageURL, visibleForPublic); + if (successful) { + 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 update tag! " + errorMessage); + } + return response; + + } } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/tagging/TaggingService.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/tagging/TaggingService.java index c65d4b9e45e..fc702515420 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/tagging/TaggingService.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/tagging/TaggingService.java @@ -13,23 +13,30 @@ public interface TaggingService { * Enum used to identify issues. */ public enum ErrorCode { - UNKNOWN_ERROR("Unknown error"), - USER_NOT_FOUND("User not found"), - MISSING_PERMISSIONS("Missing permissions"), - SECURITY_SERIVCE_NOT_FOUND("Security service not found"), - RACELOG_NOT_FOUND("Racelog not found"), - TAG_NOT_REVOKABLE("This tag cannot be revoked"), - TAG_NOT_EMPTY("Tag may not be empty"), - TIMEPOINT_NOT_EMPTY("Timepoint may not be empty"), - TAG_TOO_LONG("Tag is too long"), - COMMENT_TOO_LONG("Comment is too long"); + 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_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 message) { + ErrorCode(String code, String message) { + this.code = code; this.message = message; } + public String getCode() { + return code; + } + public String getMessage() { return message; } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/tagging/TaggingServiceImpl.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/tagging/TaggingServiceImpl.java index b902986f464..4b90e1d526b 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/tagging/TaggingServiceImpl.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/tagging/TaggingServiceImpl.java @@ -48,7 +48,7 @@ public class TaggingServiceImpl implements TaggingService { if (principal != null) { result = principal.toString(); } else { - setLastErrorCode(ErrorCode.USER_NOT_FOUND); + setLastErrorCode(ErrorCode.NOT_LOGGED_IN); } return result; } @@ -75,8 +75,23 @@ public class TaggingServiceImpl implements TaggingService { setLastErrorCode(ErrorCode.RACELOG_NOT_FOUND); successful = false; } else { - raceLog.add(new RaceLogTagEventImpl(tag, comment, imageURL, raceTimepoint, - racingService.getServerAuthor(), raceLog.getCurrentPassId())); + // check if tag already exists + boolean alreadyExists = false; + List publicTags = getPublicTags(leaderboardName, raceColumnName, fleetName); + for (TagDTO publicTag : publicTags) { + if (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); @@ -101,11 +116,17 @@ public class TaggingServiceImpl implements TaggingService { String key = serializer.generateUniqueKey(leaderboardName, raceColumnName, fleetName); String privateTagsJson = securityService.getPreference(username, key); List privateTags = serializer.deserializeTags(privateTagsJson); - privateTags.add(new TagDTO(tag, comment, imageURL, username, false, raceTimepoint, - MillisecondsTimePoint.now())); - securityService.setPreference(username, key, serializer.serializeTags(privateTags)); + 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.USER_NOT_FOUND); + setLastErrorCode(ErrorCode.NOT_LOGGED_IN); successful = false; } } @@ -159,19 +180,21 @@ public class TaggingServiceImpl implements TaggingService { private boolean removePrivateTag(String leaderboardName, String raceColumnName, String fleetName, TagDTO tag) { boolean successful = true; - List privateTags = getPrivateTags(leaderboardName, raceColumnName, fleetName); - if (privateTags != null) { + String username = getCurrentUsername(); + if (username == null) { + setLastErrorCode(ErrorCode.NOT_LOGGED_IN); + successful = false; + } else { + List privateTags = getPrivateTags(leaderboardName, raceColumnName, fleetName); privateTags.remove(tag); - String username = getCurrentUsername(); 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) { securityService.setPreference(username, key, serializer.serializeTags(privateTags)); } - } else { - successful = false; } + return successful; }