diff --git a/META-INF/KeySpace Structure.xlsx b/META-INF/KeySpace Structure.xlsx index 219ee57..649ddf1 100644 Binary files a/META-INF/KeySpace Structure.xlsx and b/META-INF/KeySpace Structure.xlsx differ diff --git a/src/main/java/org/gcube/portal/databook/server/CassandraClusterConnection.java b/src/main/java/org/gcube/portal/databook/server/CassandraClusterConnection.java index 8fba175..31d1654 100644 --- a/src/main/java/org/gcube/portal/databook/server/CassandraClusterConnection.java +++ b/src/main/java/org/gcube/portal/databook/server/CassandraClusterConnection.java @@ -111,14 +111,14 @@ public class CassandraClusterConnection { // The Keyspace instance can be shared among different requests if(myKeyspace == null){ synchronized(this){ - if(myKeyspace == null){ // double check lock + if(myKeyspace == null){ // double checked lock AstyanaxContext context = new AstyanaxContext.Builder() .forCluster(clusterName) .forKeyspace(keyspaceName) .withAstyanaxConfiguration( new AstyanaxConfigurationImpl() .setDiscoveryType(NodeDiscoveryType.NONE) // use only the host given as seeds (do not discover) - .setConnectionPoolType(ConnectionPoolType.ROUND_ROBIN) // how handle connections of the the connection pool + .setConnectionPoolType(ConnectionPoolType.ROUND_ROBIN) // how to handle connections of the the connection pool ) .withConnectionPoolConfiguration( new ConnectionPoolConfigurationImpl("MyConnectionPool") @@ -239,7 +239,9 @@ public class CassandraClusterConnection { ColumnFamilyDefinition cfDefUserTimeline = getDynamicCFDef(DBCassandraAstyanaxImpl.USER_TIMELINE_FEEDS); ColumnFamilyDefinition cfDefUserLikedFeeds = getDynamicCFDef(DBCassandraAstyanaxImpl.USER_LIKED_FEEDS); ColumnFamilyDefinition cfDefUserNotifications = getDynamicCFDef(DBCassandraAstyanaxImpl.USER_NOTIFICATIONS); + ColumnFamilyDefinition cfDefUserNotificationsUnread = getDynamicCFDef(DBCassandraAstyanaxImpl.USER_NOTIFICATIONS_UNREAD); ColumnFamilyDefinition cfDefUserMessagesNotifications = getDynamicCFDef(DBCassandraAstyanaxImpl.USER_MESSAGES_NOTIFICATIONS); + ColumnFamilyDefinition cfDefUserMessagesNotificationsUnread = getDynamicCFDef(DBCassandraAstyanaxImpl.USER_MESSAGES_NOTIFICATIONS_UNREAD); ColumnFamilyDefinition cfDefUserNotificationsPreferences = getDynamicCFDef(DBCassandraAstyanaxImpl.USER_NOTIFICATIONS_PREFERENCES); ColumnFamilyDefinition cfDefHashtagsCounter = getDynamicCFDef(DBCassandraAstyanaxImpl.HASHTAGS_COUNTER); ColumnFamilyDefinition cfDefHashtagTimeline = getDynamicCFDef(DBCassandraAstyanaxImpl.HASHTAGGED_FEEDS); @@ -260,7 +262,9 @@ public class CassandraClusterConnection { .addColumnFamily(cfDefAPPTimeline) .addColumnFamily(cfDefUserTimeline) .addColumnFamily(cfDefUserNotifications) + .addColumnFamily(cfDefUserNotificationsUnread) .addColumnFamily(cfDefUserMessagesNotifications) + .addColumnFamily(cfDefUserMessagesNotificationsUnread) .addColumnFamily(cfDefUserNotificationsPreferences) .addColumnFamily(cfDefUserLikedFeeds) .addColumnFamily(cfDefHashtagsCounter) diff --git a/src/main/java/org/gcube/portal/databook/server/DBCassandraAstyanaxImpl.java b/src/main/java/org/gcube/portal/databook/server/DBCassandraAstyanaxImpl.java index 169518d..069d1ce 100644 --- a/src/main/java/org/gcube/portal/databook/server/DBCassandraAstyanaxImpl.java +++ b/src/main/java/org/gcube/portal/databook/server/DBCassandraAstyanaxImpl.java @@ -78,8 +78,10 @@ public final class DBCassandraAstyanaxImpl implements DatabookStore { public static final String USER_TIMELINE_FEEDS = "USERTimeline"; public static final String APP_TIMELINE_FEEDS = "AppTimeline"; public static final String USER_LIKED_FEEDS = "USERLikes"; - public static final String USER_NOTIFICATIONS = "USERNotifications"; // regular user notifications timeline + public static final String USER_NOTIFICATIONS = "USERNotifications"; // regular user notifications timeline (both read and unread) + public static final String USER_NOTIFICATIONS_UNREAD = "USERNotificationsUnread"; // only unread user notifications public static final String USER_MESSAGES_NOTIFICATIONS = "USERMessagesNotifications"; // user messages notifications timeline + public static final String USER_MESSAGES_NOTIFICATIONS_UNREAD = "USERMessagesNotificationsUnread"; // only unread user notification messages public static final String USER_NOTIFICATIONS_PREFERENCES = "USERNotificationsPreferences"; // preferences for notifications public static final String HASHTAGS_COUNTER = "HashtagsCounter"; // count the hashtags per group and type public static final String HASHTAGGED_FEEDS = "HashtaggedFeeds"; // contains hashtags per type associated with vre and feed @@ -140,10 +142,18 @@ public final class DBCassandraAstyanaxImpl implements DatabookStore { USER_NOTIFICATIONS, // Column Family Name StringSerializer.get(), // Key Serializer StringSerializer.get()); // Column Serializer + private static ColumnFamily cf_UserNotificationsUnread = new ColumnFamily( + USER_NOTIFICATIONS_UNREAD, // Column Family Name + StringSerializer.get(), // Key Serializer + StringSerializer.get()); // Column Serializer private static ColumnFamily cf_UserMessageNotifications = new ColumnFamily( USER_MESSAGES_NOTIFICATIONS, // Column Family Name StringSerializer.get(), // Key Serializer StringSerializer.get()); // Column Serializer + private static ColumnFamily cf_UserMessageNotificationsUnread = new ColumnFamily( + USER_MESSAGES_NOTIFICATIONS_UNREAD, // Column Family Name + StringSerializer.get(), // Key Serializer + StringSerializer.get()); // Column Serializer protected static ColumnFamily cf_UserNotificationsPreferences = new ColumnFamily( USER_NOTIFICATIONS_PREFERENCES, // Column Family Name StringSerializer.get(), // Key Serializer @@ -192,14 +202,14 @@ public final class DBCassandraAstyanaxImpl implements DatabookStore { public DBCassandraAstyanaxImpl() { conn = new CassandraClusterConnection(false); } - + /** * public constructor, no dropping schema is allowed, infrastructureName is given. */ public DBCassandraAstyanaxImpl(String infrastructureName) { conn = new CassandraClusterConnection(false, infrastructureName); } - + /** * execute the mutation batch * @param m @@ -891,13 +901,21 @@ public final class DBCassandraAstyanaxImpl implements DatabookStore { .putColumn("SenderFullName", n.getSenderFullName(), null) .putColumn("SenderThumbnail", n.getSenderThumbnail(), null); - if (n.getType() != NotificationType.MESSAGE) + if (n.getType() != NotificationType.MESSAGE){ //an entry in the user Notifications Timeline m.withRow(cf_UserNotifications, n.getUserid()).putColumn(n.getTime().getTime()+"", n.getKey().toString(), null); - else + + // another entry in the Unread Notifications Timeline + m.withRow(cf_UserNotificationsUnread, n.getUserid()).putColumn(n.getTime().getTime()+"", n.getKey().toString(), null); + } + else{ //an entry in the user Messages Notifications Timeline m.withRow(cf_UserMessageNotifications, n.getUserid()).putColumn(n.getTime().getTime()+"", n.getKey().toString(), null); + //an entry in the user Unread Messages Notifications Timeline + m.withRow(cf_UserMessageNotificationsUnread, n.getUserid()).putColumn(n.getTime().getTime()+"", n.getKey().toString(), null); + } + return execute(m); } @@ -949,6 +967,16 @@ public final class DBCassandraAstyanaxImpl implements DatabookStore { MutationBatch m = conn.getKeyspace().prepareMutationBatch(); //an entry in the feed CF m.withRow(cf_Notifications, notificationidToSet).putColumn("Read", true, null); + + // delete the notification's key from the unread notifications or unread notification messages column family, according to the type + if(toSet.getType().equals(NotificationType.MESSAGE)){ + m.withRow(cf_UserMessageNotificationsUnread, toSet.getUserid()).deleteColumn(toSet.getTime().getTime()+""); + } + else{ + m.withRow(cf_UserNotificationsUnread, toSet.getUserid()).deleteColumn(toSet.getTime().getTime()+""); + } + + // execute the operations try { m.execute(); } catch (ConnectionException e) { @@ -983,6 +1011,31 @@ public final class DBCassandraAstyanaxImpl implements DatabookStore { } return toReturn; } + /** + * Return a list of not read notifications by user userid + * @param userid user identifier + * @return simply return a list of not read user notifications UUID in chronological order from the oldest to the more recent + */ + private ArrayList getUnreadUserNotificationsIds(String userid) { + OperationResult> result = null; + try { + result = conn.getKeyspace().prepareQuery(cf_UserNotificationsUnread) + .getKeySlice(userid) + .execute(); + } catch (ConnectionException e) { + e.printStackTrace(); + } + + ArrayList toReturn = new ArrayList(); + + // Iterate rows and their columns + for (Row row : result.getResult()) { + for (Column column : row.getColumns()) { + toReturn.add(column.getStringValue()); + } + } + return toReturn; + } /** * * @param userid user identifier @@ -1008,6 +1061,31 @@ public final class DBCassandraAstyanaxImpl implements DatabookStore { } return toReturn; } + /** + * Returns UUIDS of not read user notification messages + * @param userid user identifier + * @return simply return a list of yet to read user messages notifications UUID in chronological order from the oldest to the more recent + */ + private ArrayList getUserMessagesUnreadNotificationsIds(String userid) { + OperationResult> result = null; + try { + result = conn.getKeyspace().prepareQuery(cf_UserMessageNotificationsUnread) + .getKeySlice(userid) + .execute(); + } catch (ConnectionException e) { + e.printStackTrace(); + } + + ArrayList toReturn = new ArrayList(); + + // Iterate rows and their columns + for (Row row : result.getResult()) { + for (Column column : row.getColumns()) { + toReturn.add(column.getStringValue()); + } + } + return toReturn; + } /** * {@inheritDoc} */ @@ -1036,16 +1114,17 @@ public final class DBCassandraAstyanaxImpl implements DatabookStore { @Override public List getUnreadNotificationsByUser(String userid) throws NotificationTypeNotFoundException, ColumnNameNotFoundException, NotificationIDNotFoundException { ArrayList toReturn = new ArrayList(); - ArrayList notificationsIDs = getUserNotificationsIds(userid); + ArrayList notificationsIDs = getUnreadUserNotificationsIds(userid); //need them in reverse order for (int i = notificationsIDs.size()-1; i >= 0; i--) { - Notification toAdd = readNotification(notificationsIDs.get(i)); - if ((!toAdd.isRead()) && (toAdd.getType() != NotificationType.MESSAGE) ) { //i just set the first notification unread to read (much faster cuz i check only the newest) - toReturn.add(toAdd); - break; + try{ + toReturn.add(readNotification(notificationsIDs.get(i))); + }catch(Exception e){ + _log.error("Unable to read notification with key " + notificationsIDs.get(i)); } } + return toReturn; } /** @@ -1085,19 +1164,16 @@ public final class DBCassandraAstyanaxImpl implements DatabookStore { */ @Override public boolean setAllNotificationReadByUser(String userid) throws NotificationTypeNotFoundException, ColumnNameNotFoundException { - ArrayList notificationsIDs = getUserNotificationsIds(userid); - //need them in reverse order + // get the list of unread notifications + ArrayList notificationsIDs = getUnreadUserNotificationsIds(userid); + for (int i = notificationsIDs.size()-1; i >= 0; i--) { - Notification toAdd; - try { - toAdd = readNotification(notificationsIDs.get(i)); - if ((!toAdd.isRead()) && (toAdd.getType() != NotificationType.MESSAGE) ) { //while I encounter unread notifications keep putting them to read, else exit - setNotificationRead(toAdd.getKey()); - } - else { - break; - } + try{ + + // set to read (and automatically remove from the unread column family) + setNotificationRead(notificationsIDs.get(i)); + } catch (NotificationIDNotFoundException e) { _log.error("Could not set read notification with id =" + notificationsIDs.get(i)); } @@ -1110,16 +1186,17 @@ public final class DBCassandraAstyanaxImpl implements DatabookStore { @Override public List getUnreadNotificationMessagesByUser(String userid) throws NotificationTypeNotFoundException, ColumnNameNotFoundException, NotificationIDNotFoundException { ArrayList toReturn = new ArrayList(); - ArrayList notificationsIDs = getUserMessagesNotificationsIds(userid); + ArrayList notificationsIDs = getUserMessagesUnreadNotificationsIds(userid); //need them in reverse order for (int i = notificationsIDs.size()-1; i >= 0; i--) { - Notification toAdd = readNotification(notificationsIDs.get(i)); - if ((!toAdd.isRead()) && (toAdd.getType() == NotificationType.MESSAGE) ) {//i just set the first message notification unread to read (much faster) - toReturn.add(toAdd); - break; + try{ + toReturn.add(readNotification(notificationsIDs.get(i))); + }catch(Exception e){ + _log.error("Unable to read notification message with key " + notificationsIDs.get(i)); } } + return toReturn; } /** @@ -1127,40 +1204,18 @@ public final class DBCassandraAstyanaxImpl implements DatabookStore { */ @Override public boolean checkUnreadNotifications(String userid) throws NotificationTypeNotFoundException, ColumnNameNotFoundException { - ArrayList notificationsIDs = getUserNotificationsIds(userid); - //since #readNotification costs time and newer notifications are iterarate first (with the reverse for below) - //i just see if the first non message notification (UserNotifications TimeLine) is read or not and return the value instead of iterating them one by one looking for unread() - //need them in reverse order - for (int i = notificationsIDs.size()-1; i >= 0; i--) { - Notification toAdd; - try { - toAdd = readNotification(notificationsIDs.get(i)); - if (toAdd.getType() != NotificationType.MESSAGE) - return ! toAdd.isRead(); - } catch (NotificationIDNotFoundException e) { - _log.error("Notification not found with id = " + notificationsIDs.get(i)); - return false; - } - } - return false; + // are there notifications to read? + return getUnreadUserNotificationsIds(userid).size() > 0; } /** * {@inheritDoc} */ @Override public boolean checkUnreadMessagesNotifications(String userid) throws NotificationIDNotFoundException, NotificationTypeNotFoundException, ColumnNameNotFoundException { - ArrayList notificationsIDs = getUserMessagesNotificationsIds(userid); - //since #readNotification costs time and newer notifications are iterarate first (with the reverse for below) - //i just see if the first message notification (UserMessagesNotifications TL) is read or not and return the value instead of iterating them one by one looking for unread() - //need them in reverse order - for (int i = notificationsIDs.size()-1; i >= 0; i--) { - Notification toAdd = readNotification(notificationsIDs.get(i)); - if (toAdd.getType() == NotificationType.MESSAGE) - return ! toAdd.isRead(); - } - return false; + // are there messages to read? + return getUserMessagesUnreadNotificationsIds(userid).size() > 0; } /* *