package org.gcube.portal.social.networking.ws.methods.v2; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; 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.applicationsupportlayer.social.ApplicationNotificationsManager; import org.gcube.applicationsupportlayer.social.NotificationsManager; import org.gcube.applicationsupportlayer.social.ScopeBeanExt; import org.gcube.applicationsupportlayer.social.shared.SocialNetworkingSite; import org.gcube.applicationsupportlayer.social.shared.SocialNetworkingUser; 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.common.scope.impl.ScopeBean; import org.gcube.portal.databook.shared.Notification; import org.gcube.portal.databook.shared.NotificationType; import org.gcube.portal.databook.shared.RunningJob; import org.gcube.portal.notifications.bean.GenericItemBean; import org.gcube.portal.notifications.thread.JobStatusNotificationThread; import org.gcube.portal.social.networking.caches.SocialNetworkingSiteFinder; import org.gcube.portal.social.networking.caches.UsersCache; import org.gcube.portal.social.networking.liferay.ws.GroupManagerWSBuilder; import org.gcube.portal.social.networking.liferay.ws.LiferayJSONWsCredentials; import org.gcube.portal.social.networking.liferay.ws.UserManagerWSBuilder; import org.gcube.portal.social.networking.ws.mappers.CatalogueEventTypeMapper; import org.gcube.portal.social.networking.ws.mappers.JobMapper; import org.gcube.portal.social.networking.ws.mappers.WorkspaceItemMapper; 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.social_networking.socialnetworking.model.beans.JobNotificationBean; import org.gcube.social_networking.socialnetworking.model.beans.catalogue.CatalogueEvent; import org.gcube.social_networking.socialnetworking.model.beans.workspace.WorkspaceEvent; import org.gcube.social_networking.socialnetworking.model.beans.workspace.AddedItemEvent; import org.gcube.social_networking.socialnetworking.model.beans.workspace.DeletedItemEvent; import org.gcube.social_networking.socialnetworking.model.beans.workspace.FolderAddedUserEvent; import org.gcube.social_networking.socialnetworking.model.beans.workspace.FolderAdminDowngradeEvent; import org.gcube.social_networking.socialnetworking.model.beans.workspace.FolderAdminUpgradeEvent; import org.gcube.social_networking.socialnetworking.model.beans.workspace.FolderRemovedUserEvent; import org.gcube.social_networking.socialnetworking.model.beans.workspace.RenamedFolderEvent; import org.gcube.social_networking.socialnetworking.model.beans.workspace.SharedFolderEvent; import org.gcube.social_networking.socialnetworking.model.beans.workspace.UnsharedFolderEvent; import org.gcube.social_networking.socialnetworking.model.beans.workspace.UpdatedItemEvent; import org.gcube.vomanagement.usermanagement.GroupManager; import org.gcube.vomanagement.usermanagement.UserManager; import org.gcube.vomanagement.usermanagement.exception.GroupRetrievalFault; import org.gcube.vomanagement.usermanagement.exception.UserManagementSystemException; import org.gcube.vomanagement.usermanagement.model.GCubeUser; 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 (notifications). */ @Path("2/notifications") @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 Notifications { // Logger private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Notifications.class); @GET @Path("get-range-notifications/") @Produces(MediaType.APPLICATION_JSON) /** * Retrieve notifications of the gcube-token's owner * @param from must be greater or equal to 1, range[0, infinity] * @param quantity quantity must be greater or equal to 0 * @return notifications up to quantity * @throws ValidationException */ @StatusCodes ({ @ResponseCode ( code = 200, condition = "Notifications retrieved and reported in the 'result' field of the returned object"), @ResponseCode ( code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT) }) public Response getRangeNotifications( @DefaultValue("1") @QueryParam("from") @Min(value=1, message="from must be greater or equal to 1") int from, @DefaultValue("10") @QueryParam("quantity") @Min(value=0, message="quantity must be greater or equal to 0") int quantity ) throws ValidationException{ Caller caller = AuthorizationProvider.instance.get(); String username = caller.getClient().getId(); logger.debug("Retrieving " + quantity + " notifications of user = " + username + " from " + from); ResponseBean responseBean = new ResponseBean(); Status status = Status.OK; List notifications = null; try{ notifications = CassandraConnection.getInstance().getDatabookStore().getRangeNotificationsByUser(username, from, quantity); responseBean.setResult(notifications); responseBean.setSuccess(true); logger.debug("List of notifications retrieved"); }catch(Exception e){ logger.error("Unable to retrieve such notifications.", e); responseBean.setMessage(e.getMessage()); responseBean.setSuccess(false); status = Status.INTERNAL_SERVER_ERROR; } return Response.status(status).entity(responseBean).build(); } /** * Send a JOB notification to a given recipient * @param job The job bean * @return * @throws ValidationException */ @POST @Path("notify-job-status/") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @StatusCodes ({ @ResponseCode ( code = 200, condition = "Notification is sent correctly"), @ResponseCode ( code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT) }) public Response notifyJobStatus( @NotNull(message="input is missing") @Valid JobNotificationBean job) throws ValidationException{ Caller caller = AuthorizationProvider.instance.get(); String context = ScopeProvider.instance.get(); ResponseBean responseBean = new ResponseBean(); Status status = Status.OK; String appQualifier = caller.getClient().getId(); logger.info("Received request from app " + appQualifier + " to notify job status described by bean " + job); try{ String recipient = job.getRecipient(); GCubeUser userRecipient = UserManagerWSBuilder.getInstance().getUserManager().getUserByUsername(recipient); GenericItemBean recipientBean = new GenericItemBean(userRecipient.getUsername(), userRecipient.getUsername(), userRecipient.getFullname(), userRecipient.getUserAvatarURL()); // notifications are sent by using the user allowed to use liferay's json apis SocialNetworkingSite site = SocialNetworkingSiteFinder.getSocialNetworkingSiteFromScope(context); GCubeUser senderUser = UserManagerWSBuilder.getInstance().getUserManager().getUserByEmail(LiferayJSONWsCredentials.getSingleton().getUser()); SocialNetworkingUser user = new SocialNetworkingUser(senderUser.getUsername(), senderUser.getEmail(), senderUser.getFullname(), senderUser.getUserAvatarURL()); NotificationsManager nm = new ApplicationNotificationsManager(UserManagerWSBuilder.getInstance().getUserManager(), site, context, user); RunningJob theJob = JobMapper.getJob(job); new Thread(new JobStatusNotificationThread(theJob, Arrays.asList(recipientBean), nm)).start(); responseBean.setSuccess(true); responseBean.setResult("Notification thread started"); }catch(Exception e){ logger.error("Unable to send job notification", e); responseBean.setSuccess(false); responseBean.setMessage(e.getMessage()); status = Status.INTERNAL_SERVER_ERROR; } return Response.status(status).entity(responseBean).build(); } /** * Send a Catalogue notification to a given user * @param event * @return * @throws ValidationException */ @POST @Path("catalogue/") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @StatusCodes ({ @ResponseCode ( code = 200, condition = "Catalogue Notification is sent correctly"), @ResponseCode ( code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT) }) public Response catalogue( @NotNull(message="input is missing") @Valid CatalogueEvent event) throws ValidationException{ Caller caller = AuthorizationProvider.instance.get(); String context = ScopeProvider.instance.get(); String username = caller.getClient().getId(); logger.debug("catalogue notifications from user = " + username); ResponseBean responseBean = new ResponseBean(); Status status = Status.OK; boolean deliveryResult = false; try { logger.debug("catalogue notifications type is " + event.getType()); SocialNetworkingSite site = SocialNetworkingSiteFinder.getSocialNetworkingSiteFromScope(context); UserManager um = UserManagerWSBuilder.getInstance().getUserManager(); GCubeUser senderUser = um.getUserByUsername(username); SocialNetworkingUser user = new SocialNetworkingUser(senderUser.getUsername(), senderUser.getEmail(), senderUser.getFullname(), senderUser.getUserAvatarURL()); NotificationsManager nm = new ApplicationNotificationsManager(UserManagerWSBuilder.getInstance().getUserManager(), site, context, user); String[] idsToNotify = event.getIdsToNotify(); if (! event.idsAsGroup()) { for (int i = 0; i < idsToNotify.length; i++) { String userIdToNotify = idsToNotify[i]; String username2Notify = null; try { username2Notify = um.getUserByUsername(userIdToNotify).getUsername(); } catch (Exception e) { status = Status.BAD_REQUEST; logger.error("Username not found", e); responseBean.setSuccess(false); responseBean.setMessage("Username not found, got: " + userIdToNotify); return Response.status(status).entity(responseBean).build(); } deliveryResult = nm.notifyCatalogueEvent( CatalogueEventTypeMapper.getType(event.getType()), username2Notify, event.getItemId(), event.getNotifyText(), event.getItemURL()); } } else { //the ids are contexts for (int i = 0; i < idsToNotify.length; i++) { String contextId = idsToNotify[i]; try { ScopeBean scope = new ScopeBean(contextId); if (scope.type() != ScopeBean.Type.VRE) { logger.error("Context not a VRE"); status = Status.BAD_REQUEST; responseBean.setSuccess(false); responseBean.setMessage("Not a VRE Context, only VREs are supported"); return Response.status(status).entity(responseBean).build(); } else { // it is a context and it is a valid VRE String[] userIdsToNotify = getUsernamesByContext(scope).toArray(new String[0]); //resolve the members for (int j = 0; j < userIdsToNotify.length; j++) { String userIdToNotify = userIdsToNotify[j]; deliveryResult = nm.notifyCatalogueEvent( CatalogueEventTypeMapper.getType(event.getType()), userIdToNotify, event.getItemId(), event.getNotifyText(), event.getItemURL()); } } } catch(IllegalArgumentException e) { status = Status.BAD_REQUEST; logger.error("Context not valid", e); responseBean.setSuccess(false); responseBean.setMessage("Context not valid, must start with / " + e.getMessage()); return Response.status(status).entity(responseBean).build(); } } } } catch(Exception e){ logger.error("Unable to send job notification", e); responseBean.setSuccess(false); responseBean.setMessage(e.getMessage()); status = Status.INTERNAL_SERVER_ERROR; } logger.debug("catalogue notifications should have been sent"); if (deliveryResult) { responseBean.setSuccess(true); responseBean.setMessage("catalogue notification delivered correctly"); responseBean.setResult(new Boolean(true)); } else { responseBean.setSuccess(false); responseBean.setMessage("An error occurred between this service and Cassandra DB, notification not delivered correctly"); responseBean.setResult(new Boolean(false)); } return Response.status(status).entity(responseBean).build(); } /** * Send a Workspace notification to a given user * @param event * @return * @throws ValidationException */ @POST @Path("workspace/") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @StatusCodes ({ @ResponseCode ( code = 200, condition = "Workspace Notification is sent correctly"), @ResponseCode ( code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT) }) public Response workspace( @NotNull(message="input is missing") @Valid WorkspaceEvent event) throws ValidationException{ Caller caller = AuthorizationProvider.instance.get(); String context = ScopeProvider.instance.get(); String username = caller.getClient().getId(); logger.debug("workspace notifications from user = " + username); ResponseBean responseBean = new ResponseBean(); Status status = Status.OK; boolean deliveryResult = false; try { logger.debug("workspace notifications type is " + event.getType()); SocialNetworkingSite site = SocialNetworkingSiteFinder.getSocialNetworkingSiteFromScope(context); UserManager um = UserManagerWSBuilder.getInstance().getUserManager(); GCubeUser senderUser = um.getUserByUsername(username); SocialNetworkingUser user = new SocialNetworkingUser(senderUser.getUsername(), senderUser.getEmail(), senderUser.getFullname(), senderUser.getUserAvatarURL()); NotificationsManager nm = new ApplicationNotificationsManager(UserManagerWSBuilder.getInstance().getUserManager(), site, context, user); String[] idsToNotify = event.getIdsToNotify(); if (! event.idsAsGroup()) { for (int i = 0; i < idsToNotify.length; i++) { String userIdToNotify = idsToNotify[i]; String username2Notify = ""; try { username2Notify = um.getUserByUsername(userIdToNotify).getUsername(); } catch (Exception e) { status = Status.NOT_ACCEPTABLE; logger.error("Username not found", e); responseBean.setSuccess(false); responseBean.setMessage("Username not found, received: " + userIdToNotify); return Response.status(status).entity(responseBean).build(); } deliveryResult = notifyWorkspaceEvent(event, nm, username2Notify); } } else { //the ids are contexts for (int i = 0; i < idsToNotify.length; i++) { String contextId = idsToNotify[i]; try { ScopeBean scope = new ScopeBean(contextId); if (scope.type() != ScopeBean.Type.VRE) { logger.error("Context not a VRE"); status = Status.BAD_REQUEST; responseBean.setSuccess(false); responseBean.setMessage("Not a VRE Context, only VREs are supported"); return Response.status(status).entity(responseBean).build(); } else { // it is a context and it is a valid VRE String[] userIdsToNotify = getUsernamesByContext(scope).toArray(new String[0]); //resolve the members for (int j = 0; j < userIdsToNotify.length; j++) { String userIdToNotify = userIdsToNotify[j]; deliveryResult = notifyWorkspaceEvent(event, nm, userIdToNotify); } } } catch(IllegalArgumentException e) { status = Status.BAD_REQUEST; logger.error("Context not valid", e); responseBean.setSuccess(false); responseBean.setMessage("Context not valid, must start with / " + e.getMessage()); return Response.status(status).entity(responseBean).build(); } } } } catch(Exception e){ logger.error("Unable to send job notification", e); responseBean.setSuccess(false); responseBean.setMessage(e.getMessage()); status = Status.INTERNAL_SERVER_ERROR; } logger.debug("workspace notifications should have been sent"); if (deliveryResult) { responseBean.setSuccess(true); responseBean.setMessage("Workspace notification delivered correctly"); responseBean.setResult(new Boolean(true)); } else { responseBean.setSuccess(false); responseBean.setMessage("An error occurred between this service and Cassandra DB, notification not delivered correctly"); responseBean.setResult(new Boolean(false)); } return Response.status(status).entity(responseBean).build(); } /** * * @param event * @param nm * @param idToNotify * @return */ private boolean notifyWorkspaceEvent(WorkspaceEvent event, NotificationsManager nm, String idToNotify) { try { switch (event.getType()) { case ITEM_NEW: { AddedItemEvent itemBean = (AddedItemEvent) event; return nm.notifyAddedItem(idToNotify, WorkspaceItemMapper.getFileItem(itemBean.getItem()), WorkspaceItemMapper.getSharedFolder(itemBean.getItem().getParent())); } case ITEM_UPDATE: { UpdatedItemEvent itemBean = (UpdatedItemEvent) event; return nm.notifyUpdatedItem(idToNotify, WorkspaceItemMapper.getFileItem(itemBean.getItem()), WorkspaceItemMapper.getSharedFolder(itemBean.getItem().getParent())); } case ITEM_DELETE: { DeletedItemEvent itemBean = (DeletedItemEvent) event; return nm.notifyRemovedItem(idToNotify, itemBean.getItemName(), WorkspaceItemMapper.getSharedFolder(itemBean.getFolder())); } case FOLDER_SHARE: { SharedFolderEvent itemBean = (SharedFolderEvent) event; return nm.notifyFolderSharing(idToNotify, WorkspaceItemMapper.getSharedFolder(itemBean.getFolder())); } case FOLDER_UNSHARE: { UnsharedFolderEvent itemBean = (UnsharedFolderEvent) event; return nm.notifyFolderUnsharing(idToNotify, itemBean.getUnsharedFolderId(), itemBean.getUnsharedFolderName()); } case FOLDER_RENAME: { RenamedFolderEvent itemBean = (RenamedFolderEvent) event; return nm.notifyFolderRenaming(idToNotify, itemBean.getPreviousName(), itemBean.getNewName(), itemBean.getRenamedFolderId()); } case FOLDER_ADMIN_UPGRADE: { FolderAdminUpgradeEvent itemBean = (FolderAdminUpgradeEvent) event; return nm.notifyAdministratorUpgrade(idToNotify, WorkspaceItemMapper.getSharedFolder(itemBean.getFolder())); } case FOLDER_ADMIN_DOWNGRADE: { FolderAdminDowngradeEvent itemBean = (FolderAdminDowngradeEvent) event; return nm.notifyAdministratorDowngrade(idToNotify, WorkspaceItemMapper.getSharedFolder(itemBean.getFolder())); } case FOLDER_ADDEDUSER: { FolderAddedUserEvent itemBean = (FolderAddedUserEvent) event; UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager(); return nm.notifyFolderAddedUsers(idToNotify, WorkspaceItemMapper.getSharedFolder(itemBean.getFolder()), itemBean.getNewAddedUserIds(), userManager); } case FOLDER_REMOVEDUSER: { FolderRemovedUserEvent itemBean = (FolderRemovedUserEvent) event; return nm.notifyFolderRemovedUser(idToNotify, WorkspaceItemMapper.getSharedFolder(itemBean.getFolder())); } default: break; } } catch(Exception e){ logger.error("Unable to send job notification", e); return false; } return false; } /** * * @param context * @return * @throws Exception */ private List getUsernamesByContext(ScopeBean context) throws Exception { List usernames = new ArrayList(); GroupManager groupManager = GroupManagerWSBuilder.getInstance().getGroupManager(); UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager(); long groupId = groupManager.getGroupIdFromInfrastructureScope(context.toString()); // first retrieve ids List userIds = userManager.getUserIdsByGroup(groupId); // check info in cache when available UsersCache cache = UsersCache.getSingleton(); for (Long userId : userIds) { if(cache.getUser(userId) == null){ GCubeUser theUser = userManager.getUserById(userId); if(theUser != null){ usernames.add(theUser.getUsername()); cache.pushEntry(userId, theUser); } }else usernames.add(cache.getUser(userId).getUsername()); } return usernames; } }