package org.gcube.portal.social.networking.ws.methods.v2; import java.util.ArrayList; import java.util.List; import javax.validation.Valid; import javax.validation.ValidationException; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.gcube.common.authorization.library.provider.AuthorizationProvider; import org.gcube.common.authorization.library.utils.Caller; import org.gcube.common.scope.api.ScopeProvider; import org.gcube.portal.databook.server.DatabookStore; import org.gcube.portal.databook.shared.ApplicationProfile; import org.gcube.portal.databook.shared.Feed; import org.gcube.portal.databook.shared.Post; import org.gcube.portal.databook.shared.RangePosts; import org.gcube.portal.databook.shared.ex.ColumnNameNotFoundException; import org.gcube.portal.databook.shared.ex.FeedTypeNotFoundException; import org.gcube.portal.databook.shared.ex.PrivacyLevelTypeNotFoundException; import org.gcube.portal.social.networking.ws.inputs.PostInputBean; import org.gcube.portal.social.networking.ws.outputs.ResponseBean; import org.gcube.portal.social.networking.ws.utils.CassandraConnection; import org.gcube.portal.social.networking.ws.utils.ErrorMessages; import org.gcube.portal.social.networking.ws.utils.Filters; import org.gcube.portal.social.networking.ws.utils.SocialUtils; import org.slf4j.LoggerFactory; import com.webcohesion.enunciate.metadata.rs.RequestHeader; import com.webcohesion.enunciate.metadata.rs.RequestHeaders; import com.webcohesion.enunciate.metadata.rs.ResponseCode; import com.webcohesion.enunciate.metadata.rs.StatusCodes; /** * REST interface for the social networking library (posts). */ @Path("2/posts") @RequestHeaders ({ @RequestHeader( name = "Authorization", description = "Bearer token, see https://dev.d4science.org/how-to-access-resources"), @RequestHeader( name = "Content-Type", description = "application/json") }) public class Posts { // Logger private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Posts.class); @GET @Path("get-posts-user-since/") @Produces(MediaType.APPLICATION_JSON) @StatusCodes ({ @ResponseCode ( code = 200, condition = "Successful retrieval of posts, reported in the 'result' field of the returned object"), @ResponseCode ( code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT) }) /** * Retrieve posts of the auth token's owner, and allow to filter them by time" * @param timeInMillis The reference time since when retrieving posts * @return the posts * @throws ValidationException */ public Response getRecentPostsByUserAndDate( @QueryParam("time") @Min(value = 0, message="time cannot be negative") long timeInMillis ) throws ValidationException{ ResponseBean responseBean = new ResponseBean(); Status status = Status.OK; Caller caller = AuthorizationProvider.instance.get(); String context = ScopeProvider.instance.get(); String username = caller.getClient().getId(); List posts = null; try{ logger.info("Retrieving post for user id " + username + " and reference time " + timeInMillis); posts = CassandraConnection.getInstance().getDatabookStore().getRecentPostsByUserAndDate(username, timeInMillis); Filters.filterPostsPerContext(posts, context); Filters.hideSensitiveInformation(posts, caller.getClient().getId()); responseBean.setResult(posts); responseBean.setMessage(""); responseBean.setSuccess(true); }catch(Exception e){ logger.error("Unable to retrieve such posts.", e); responseBean.setMessage(e.getMessage()); responseBean.setSuccess(false); status = Status.INTERNAL_SERVER_ERROR; } return Response.status(status).entity(responseBean).build(); } @GET @Path("get-posts-user/") @Produces(MediaType.APPLICATION_JSON) @StatusCodes ({ @ResponseCode ( code = 200, condition = "Successful retrieval of posts, reported in the 'result' field of the returned object"), @ResponseCode ( code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT) }) /** * Retrieve all user's posts * @return all posts of the auth token's owner in the context identified by the token */ public Response getAllPostsByUser() { Caller caller = AuthorizationProvider.instance.get(); String username = caller.getClient().getId(); String context = ScopeProvider.instance.get(); ResponseBean responseBean = new ResponseBean(); Status status = Status.OK; List posts = null; try{ logger.debug("Retrieving posts for user with id " + username); posts = CassandraConnection.getInstance().getDatabookStore().getAllPostsByUser(username); Filters.filterPostsPerContext(posts, context); Filters.hideSensitiveInformation(posts, caller.getClient().getId()); responseBean.setResult(posts); responseBean.setMessage(""); responseBean.setSuccess(true); }catch(Exception e){ logger.error("Unable to retrieve such feeds.", e); responseBean.setMessage(e.getMessage()); responseBean.setSuccess(false); status = Status.INTERNAL_SERVER_ERROR; } return Response.status(status).entity(responseBean).build(); } @GET @Path("get-posts-user-quantity/") @Produces(MediaType.APPLICATION_JSON) @StatusCodes ({ @ResponseCode ( code = 200, condition = "Successful retrieval of posts, reported in the 'result' field of the returned object"), @ResponseCode ( code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT) }) /** * Retrieve a given quantity of latest user's posts * @param quantity the number of latest post to get * @return all posts of the auth token's owner in the context identified by the token, in reverse chronological order up to quantity (at most) * @throws ValidationException */ public Response getQuantityPostsByUser( @DefaultValue("10") @QueryParam("quantity") @Min(value=0, message="quantity cannot be negative") int quantity) throws ValidationException{ Caller caller = AuthorizationProvider.instance.get(); String username = caller.getClient().getId(); String context = ScopeProvider.instance.get(); ResponseBean responseBean = new ResponseBean(); Status status = Status.OK; List posts = new ArrayList<>(); // if quantity is zero, just return an empty list if(quantity == 0){ responseBean.setSuccess(true); return Response.status(status).entity(responseBean).build(); } try{ logger.debug("getRecentPostsByUser first, posts made by user " + username); posts = CassandraConnection.getInstance().getDatabookStore().getAllPostsByUser(username); if (posts != null) logger.debug("got " + posts.size() + " posts"); logger.debug("Retrieving last " + quantity + " posts made by user " + username + " in context = "+context); Filters.filterPostsPerContext(posts, context); quantity = (quantity > posts.size()) ? posts.size() : quantity; posts = posts.subList(0, quantity); Filters.hideSensitiveInformation(posts, caller.getClient().getId()); responseBean.setResult(posts); responseBean.setSuccess(true); }catch(Exception e){ logger.error("Unable to retrieve such posts.", e); responseBean.setMessage(e.getMessage()); responseBean.setSuccess(false); status = Status.INTERNAL_SERVER_ERROR; } return Response.status(status).entity(responseBean).build(); } @POST @Path("write-post-user") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @StatusCodes ({ @ResponseCode ( code = 201, condition = "Successfull created, the new post is reported in the 'result' field of the returned object"), @ResponseCode ( code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT) }) /** * Create a new user post having as owner the auth token's owner * @param post The post to be written * @return * @throws ValidationException */ public Response writePostUser( @NotNull(message="Post to write is missing") @Valid PostInputBean post) throws ValidationException{ logger.debug("Request of writing a feed coming from user " + post); Caller caller = AuthorizationProvider.instance.get(); String username = caller.getClient().getId(); String context = ScopeProvider.instance.get(); ResponseBean responseBean = new ResponseBean(); Status status = Status.CREATED; // parse String postText = post.getText(); String previewTitle = post.getPreviewtitle(); String previewDescription = post.getPreviewdescription(); String previewHost = post.getPreviewhost(); String previewUrl = post.getPreviewurl(); String httpImageUrl = post.getHttpimageurl(); boolean enableNotification = post.isEnablenotification(); // convert enablenotification parameter if(enableNotification) logger.info("Enable notification for this user post."); else logger.info("Disable notification for this user post."); // try to share logger.debug("Trying to share user post..."); Feed res = SocialUtils.shareUserUpdate( username, postText, context, previewTitle, previewDescription, previewHost, previewUrl, httpImageUrl, enableNotification ); if(res != null){ logger.debug("Post correctly written by user " + username); responseBean.setResult(res); responseBean.setSuccess(true); return Response.status(status).entity(responseBean).build(); } logger.error("Unable to write post."); responseBean.setMessage("Unable to write post"); responseBean.setSuccess(false); status = Status.INTERNAL_SERVER_ERROR; return Response.status(status).entity(responseBean).build(); } @GET @Path("get-posts-app/") @Produces(MediaType.APPLICATION_JSON) @StatusCodes ({ @ResponseCode ( code = 200, condition = "Successfull created, the new post is reported in the 'result' field of the returned object"), @ResponseCode ( code = 403, condition = "\"There is no application profile with such token"), @ResponseCode ( code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT) }) /** * Retrieve the application's posts * @return the application's posts belonging to the token's owner (i.e., an application)" */ public Response getAllPostsByApp() { Caller caller = AuthorizationProvider.instance.get(); String appId = caller.getClient().getId(); String context = ScopeProvider.instance.get(); ResponseBean responseBean = new ResponseBean(); Status status = Status.OK; // check if the token actually matches an application ApplicationProfile appProfile = SocialUtils.getProfileFromInfrastrucure(appId, context); if(appProfile == null){ logger.error("The given token is not belonging to an application!!!"); status = Status.FORBIDDEN; responseBean.setSuccess(false); responseBean.setMessage(ErrorMessages.NOT_APP_TOKEN); return Response.status(status).entity(responseBean).build(); } try{ logger.debug("Retrieving posts for app with id " + appId); List feeds = CassandraConnection.getInstance().getDatabookStore().getAllPostsByApp(appId); Filters.filterPostsPerContext(feeds, context); responseBean.setSuccess(true); responseBean.setResult(feeds); }catch(Exception e){ logger.error("Unable to retrieve such feeds.", e); responseBean.setMessage(e.getMessage()); responseBean.setSuccess(false); status = Status.INTERNAL_SERVER_ERROR; } return Response.status(status).entity(responseBean).build(); } @POST @Path("write-post-app") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @StatusCodes ({ @ResponseCode ( code = 200, condition = "Successfull created, the new post is reported in the 'result' field of the returned object"), @ResponseCode ( code = 403, condition = "\"There is no application profile with such token"), @ResponseCode ( code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT) }) /** * Create a new application post having as owner-application the token's owner (the IAM Client) * @param post The post to be written * @return */ public Response writePostApp( @NotNull(message="Post to write is null") @Valid PostInputBean post){ Caller caller = AuthorizationProvider.instance.get(); String appId = caller.getClient().getId(); String context = ScopeProvider.instance.get(); ResponseBean responseBean = new ResponseBean(); Status status = Status.CREATED; logger.debug("Request of writing a feed coming from an application."); // check if the token actually matches an application profile ApplicationProfile appProfile = SocialUtils.getProfileFromInfrastrucure(appId, context); if(appProfile == null){ logger.error("The given token doesn't belong to an application!!!"); responseBean.setSuccess(false); responseBean.setMessage(ErrorMessages.NOT_APP_TOKEN); status = Status.FORBIDDEN; return Response.status(status).entity(responseBean).build(); } // parse String postText = post.getText(); String previewTitle = post.getPreviewtitle(); String previewDescription = post.getPreviewdescription(); String httpImageUrl = post.getHttpimageurl(); boolean enableNotification = post.isEnablenotification(); String params = post.getParams(); // convert enablenotification parameter if(enableNotification) logger.debug("Enable notification for this application post."); else logger.debug("Disable notification for this application post."); // write feed + notification if it is the case Feed written = SocialUtils.shareApplicationUpdate( postText, params, previewTitle, previewDescription, httpImageUrl, appProfile, caller, enableNotification ); if(written != null){ responseBean.setResult(written); responseBean.setSuccess(true); return Response.status(status).entity(responseBean).build(); } logger.error("Unable to write post."); responseBean.setMessage("Unable to write post"); responseBean.setSuccess(false); status = Status.INTERNAL_SERVER_ERROR; return Response.status(status).entity(responseBean).build(); } @GET @Path("get-posts-vre/") @Produces(MediaType.APPLICATION_JSON) @StatusCodes ({ @ResponseCode ( code = 201, condition = "Sccessfull retrieved posts, they are reported in the 'result' field of the returned object"), @ResponseCode ( code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT) }) /** * * @return all the posts in the context bound to the auth token */ public Response getAllPostsByVRE() { String context = ScopeProvider.instance.get(); Caller caller = AuthorizationProvider.instance.get(); logger.debug("Retrieving all posts coming from vre = " + context); ResponseBean responseBean = new ResponseBean(); Status status = Status.OK; try{ List posts = CassandraConnection.getInstance().getDatabookStore().getAllPostsByVRE(context); Filters.hideSensitiveInformation(posts, caller.getClient().getId()); responseBean.setResult(posts); responseBean.setSuccess(true); }catch(Exception e){ logger.error("Unable to retrieve posts for vre = " + context, e); status = Status.INTERNAL_SERVER_ERROR; responseBean.setMessage(e.toString()); responseBean.setSuccess(false); } return Response.status(status).entity(responseBean).build(); } @GET @Path("get-recent-posts-vre-by-range/") @Produces(MediaType.APPLICATION_JSON) @StatusCodes ({ @ResponseCode ( code = 201, condition = "Sccessfull retrieved posts, they are reported in the 'result' field of the returned object"), @ResponseCode ( code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT) }) /** * return the most recent posts for this vre up to quantity param and the last index of the posts in the timeline * lastReturnedPostTimelineIndex is useful to know from where to start the range the next time you ask, because there are deletions * * @param from the range start (most recent feeds for this vre) has to be greater than 0 * @param quantity the number of most recent feeds for this vre starting from "from" param * @return a RangePosts containing of most recent feeds for this vre * @throws FeedTypeNotFoundException * @throws PrivacyLevelTypeNotFoundException * @throws ColumnNameNotFoundException */ public Response getRecentPostsByVREAndRange( @QueryParam("from") @Min(value=1, message="from cannot be negative") int from, @QueryParam("quantity") @Min(value=1, message="quantity cannot be negative") int quantity) throws ValidationException { String context = ScopeProvider.instance.get(); Caller caller = AuthorizationProvider.instance.get(); logger.debug("Retrieving all posts coming from vre = " + context); ResponseBean responseBean = new ResponseBean(); Status status = Status.OK; try{ RangePosts rangePosts = CassandraConnection.getInstance().getDatabookStore().getRecentPostsByVREAndRange(context, from, quantity); Filters.hideSensitiveInformation(rangePosts.getPosts(), caller.getClient().getId()); responseBean.setResult(rangePosts); responseBean.setSuccess(true); }catch(Exception e){ logger.error("Unable to retrieve posts for vre = " + context, e); status = Status.INTERNAL_SERVER_ERROR; responseBean.setMessage(e.toString()); responseBean.setSuccess(false); } return Response.status(status).entity(responseBean).build(); } @GET @Path("get-posts-by-hashtag/") @Produces({MediaType.APPLICATION_JSON}) @StatusCodes ({ @ResponseCode ( code = 201, condition = "Sccessfull retrieved posts, they are reported in the 'result' field of the returned object"), @ResponseCode ( code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT) }) /** * Retrieve posts containing the hashtag in the context bound to the auth token * @param hashtag he hashtag to be contained within the posts * @return the posts in the context bound to the auth token matching the hashtag * @throws ValidationException */ public Response getPostsByHashTags( @QueryParam("hashtag") @NotNull(message="hashtag cannot be missing") String hashtag) throws ValidationException { Caller caller = AuthorizationProvider.instance.get(); String username = caller.getClient().getId(); String context = ScopeProvider.instance.get(); ResponseBean responseBean = new ResponseBean(); Status status = Status.OK; logger.info("User " + username + " has requested posts containing hashtag " + hashtag + " in context " + context); try{ DatabookStore datastore = CassandraConnection.getInstance().getDatabookStore(); List posts = datastore.getVREPostsByHashtag(context, hashtag); Filters.hideSensitiveInformation(posts, caller.getClient().getId()); responseBean.setResult(posts); responseBean.setSuccess(true); }catch(Exception e){ logger.error("Failed to retrieve hashtags", e); status = Status.INTERNAL_SERVER_ERROR; } return Response.status(status).entity(responseBean).build(); } @GET @Path("get-id-liked-posts/") @Produces({MediaType.APPLICATION_JSON}) @StatusCodes ({ @ResponseCode ( code = 201, condition = "Sccessfull retrieved ids, they are reported in the 'result' field of the returned object"), @ResponseCode ( code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT) }) /** * Retrieve ids (UUID) of the liked by the user * @return ids (UUID) of the liked by the user in the context bound to the auth token */ public Response getAllLikedPostIdsByUser() { Caller caller = AuthorizationProvider.instance.get(); String username = caller.getClient().getId(); String context = ScopeProvider.instance.get(); logger.debug("Retrieving all liked posts for user with id " + username + " in context " + context); List retrievedLikedPostsIds = null; ResponseBean responseBean = new ResponseBean(); Status status = Status.OK; try{ DatabookStore datastore = CassandraConnection.getInstance().getDatabookStore(); retrievedLikedPostsIds = datastore.getAllLikedPostIdsByUser(username); Filters.filterFeedsPerContextById(retrievedLikedPostsIds, context); responseBean.setResult(retrievedLikedPostsIds); responseBean.setSuccess(true); logger.debug("Ids of liked posts by " + username + " retrieved"); }catch(Exception e){ logger.error("Unable to read such ids of liked Posts.", e); responseBean.setMessage(e.getMessage()); responseBean.setSuccess(false); status = Status.INTERNAL_SERVER_ERROR; } return Response.status(status).entity(responseBean).build(); } @GET @Path("get-liked-posts/") @Produces(MediaType.APPLICATION_JSON) @StatusCodes ({ @ResponseCode ( code = 200, condition = "Successfull retrieved posts, they are reported in the 'result' field of the returned object"), @ResponseCode ( code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT) }) /** * Retrieve posts liked by the user * @param limit The maximum number of posts to be retrieved * @return posts liked by the user (up to a given quantity) in the context bound to the auth token * @throws ValidationException */ public Response getAllLikedPostsByUser( @DefaultValue("10") @QueryParam("limit") @Min(message="limit cannot be negative", value = 0) int limit) throws ValidationException{ Caller caller = AuthorizationProvider.instance.get(); String username = caller.getClient().getId(); String context = ScopeProvider.instance.get(); List retrievedLikedPosts = null; ResponseBean responseBean = new ResponseBean(); Status status = Status.OK; try{ logger.debug("Retrieving " + limit + " liked posts for user with id " + username + " in context " + context); retrievedLikedPosts = CassandraConnection.getInstance().getDatabookStore().getAllLikedPostsByUser(username, limit); Filters.filterPostsPerContext(retrievedLikedPosts, context); Filters.hideSensitiveInformation(retrievedLikedPosts, caller.getClient().getId()); responseBean.setResult(retrievedLikedPosts); responseBean.setSuccess(true); logger.debug("Liked posts by " + username + " retrieved"); }catch(Exception e){ logger.error("Unable to read such liked posts.", e); responseBean.setMessage(e.getMessage()); responseBean.setSuccess(false); status = Status.INTERNAL_SERVER_ERROR; } return Response.status(status).entity(responseBean).build(); } }