diff --git a/.classpath b/.classpath index 7b07fe6..0881b6d 100644 --- a/.classpath +++ b/.classpath @@ -1,6 +1,6 @@ - + @@ -31,5 +31,5 @@ - + diff --git a/.settings/com.google.gdt.eclipse.core.prefs b/.settings/com.google.gdt.eclipse.core.prefs index bd2d835..ef1d0b5 100644 --- a/.settings/com.google.gdt.eclipse.core.prefs +++ b/.settings/com.google.gdt.eclipse.core.prefs @@ -1,5 +1,5 @@ eclipse.preferences.version=1 jarsExcludedFromWebInfLib= -lastWarOutDir=/Users/massi/Documents/workspace/news-feed/target/news-feed-1.7.1-SNAPSHOT +lastWarOutDir=/Users/massi/Documents/workspace/news-feed/target/news-feed-1.8.0-SNAPSHOT warSrcDir=src/main/webapp warSrcDirIsOutput=false diff --git a/.settings/org.eclipse.wst.common.component b/.settings/org.eclipse.wst.common.component index 704948a..a875f43 100644 --- a/.settings/org.eclipse.wst.common.component +++ b/.settings/org.eclipse.wst.common.component @@ -4,7 +4,7 @@ - + uses diff --git a/pom.xml b/pom.xml index fab5918..775b7d3 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.gcube.portlets.user news-feed war - 1.7.1-SNAPSHOT + 1.8.0-SNAPSHOT gCube News Feed Portlet diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/NewsService.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/NewsService.java index e49a5e9..ab1625f 100644 --- a/src/main/java/org/gcube/portlets/user/newsfeed/client/NewsService.java +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/NewsService.java @@ -25,6 +25,8 @@ public interface NewsService extends RemoteService { ArrayList getOnlyLikedFeeds(); + ArrayList getFeedsByHashtag(String hashtag); + MoreFeedsBean getMoreFeeds(int from, int quantity); boolean like(String feedid, String feedText, String feedOwnerId); diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/NewsServiceAsync.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/NewsServiceAsync.java index 20d82f7..a6c3c0c 100644 --- a/src/main/java/org/gcube/portlets/user/newsfeed/client/NewsServiceAsync.java +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/NewsServiceAsync.java @@ -58,5 +58,8 @@ public interface NewsServiceAsync { void getOrganizationUsers(String currentScope, AsyncCallback> callback); + + void getFeedsByHashtag(String hashtag, + AsyncCallback> callback); } diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/panels/NewsFeedPanel.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/panels/NewsFeedPanel.java index 0329dd0..4e518ea 100644 --- a/src/main/java/org/gcube/portlets/user/newsfeed/client/panels/NewsFeedPanel.java +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/panels/NewsFeedPanel.java @@ -39,6 +39,7 @@ import org.gcube.portlets.user.newsfeed.client.event.UnLikeEvent; import org.gcube.portlets.user.newsfeed.client.event.UnLikeEventHandler; import org.gcube.portlets.user.newsfeed.client.ui.FilterPanel; import org.gcube.portlets.user.newsfeed.client.ui.NewFeedsAvailable; +import org.gcube.portlets.user.newsfeed.client.ui.ResultsFor; import org.gcube.portlets.user.newsfeed.client.ui.ShowMoreFeeds; import org.gcube.portlets.user.newsfeed.client.ui.SingleComment; import org.gcube.portlets.user.newsfeed.client.ui.TweetTemplate; @@ -55,7 +56,9 @@ import org.gcube.portlets.widgets.userselection.shared.ItemSelectableBean; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.RunAsyncCallback; + import java.util.HashMap; + import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.shared.HandlerManager; @@ -111,7 +114,7 @@ public class NewsFeedPanel extends Composite { public static final String MESSAGE_LABEL = "Message"; private String vreLabel; - + private boolean showFeedTimelineSource = false; private boolean isInfrastructure = false; @@ -159,7 +162,7 @@ public class NewsFeedPanel extends Composite { doAddLike( event.getOwner(), event.getFeedId()); } }); - + eventBus.addHandler(UnLikeEvent.TYPE, new UnLikeEventHandler() { @Override public void onUnLike(UnLikeEvent event) { @@ -251,7 +254,7 @@ public class NewsFeedPanel extends Composite { newsPanel.add(loadingImage); CheckSession.getInstance().startPolling(); - + newsService.getUserSettings(new AsyncCallback() { @Override public void onFailure(Throwable caught) { @@ -268,11 +271,19 @@ public class NewsFeedPanel extends Composite { doShowSessionExpired(); } else { + GWT.log("checking params "); if (getFeedToShowId() != null) { String feedKey = getFeedToShowId(); showSingleFeed(feedKey); filterPanel.removeFilterSelected(); - } else { + } + else if (getHashtagParam() != null) { + String hashtag = Encoder.decode(getHashtagParam()); + GWT.log("Found hashtag " + hashtag); + showFeedsByHashtag(hashtag); + filterPanel.removeFilterSelected(); + } + else { showAllUpdatesFeeds(); } currentFilter = FilterType.ALL_UPDATES; @@ -340,6 +351,13 @@ public class NewsFeedPanel extends Composite { private String getFeedToShowId() { return Window.Location.getParameter(GET_OID_PARAMETER); } + /** + * check if it has to show the feeds given an hashtag + * @return + */ + private String getHashtagParam() { + return Window.Location.getParameter(Encoder.encode(GCubeSocialNetworking.HASHTAG_OID)); + } /** * used from notification referrals (see this Post) * @param feedKey @@ -350,7 +368,7 @@ public class NewsFeedPanel extends Composite { @Override public void onSuccess(EnhancedFeed result) { if (result.getFeed().getType() == FeedType.DISABLED) { - + String usrLink = ""+result.getFeed().getFullName()+ @@ -437,7 +455,67 @@ public class NewsFeedPanel extends Composite { } }); } - + /** + * get the hashtagged feeds + * @param hashtag to look for + */ + private void showFeedsByHashtag(final String hashtag) { + showLoader(); + newsService.getUserSettings(new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + doStopFeedsTimer(); + } + @Override + public void onSuccess(UserSettings result) { + if (result.getUserInfo().getUsername().equals("test.user")) { + doStopFeedsTimer(); + doShowSessionExpired(); + } else { + /** + * this check avoids the 2 tabs open in 2 different scope, if the previous tab was open at VRE Level and then antoher + * is open at infra level the first tab stops checking for updates + */ + if (result.getCurrentScope().compareTo(currentScope) == 0) { + newsService.getFeedsByHashtag(hashtag, new AsyncCallback>() { + @Override + public void onSuccess(ArrayList feeds) { + filterPanelWrapper.setVisible(false); + newsPanel.clear(); + if (feeds != null) { + if (feeds.size() == 0) { + newsPanel.add(new ResultsFor(hashtag)); + newsPanel.add(new HTML("
" + + "Sorry, looks like we found no updates with topic: " + hashtag +"
")); + isFirstTweet = true; + } + else { + newsPanel.setHeight(""); + newsPanel.setHorizontalAlignment(HasAlignment.ALIGN_LEFT); + newsPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_TOP); + newsPanel.add(new ResultsFor(hashtag)); + for (EnhancedFeed feed : feeds) { + newsPanel.add(new TweetTemplate(false, showFeedTimelineSource, myUserInfo, feed, eventBus)); //in the view + } + if (feeds.size() < 5) { + newsPanel.add(new Image(spacer)); + } + isFirstTweet = false; + } + } else { + showProblems(); + } + } + @Override + public void onFailure(Throwable caught) { + showProblems(); + } + }); + } + } + } + }); + } /** * called when a user click on the are new updates @@ -775,7 +853,7 @@ public class NewsFeedPanel extends Composite { } }); } - + protected void doUnLike(TweetTemplate owner, String feedId) { newsService.unlike(feedId, owner.getMyFeedText(), owner.getMyFeedUserId(), new AsyncCallback() { @Override @@ -787,7 +865,7 @@ public class NewsFeedPanel extends Composite { } } }); - + } private void doShowSessionExpired() { @@ -801,7 +879,7 @@ public class NewsFeedPanel extends Composite { } }); } - + private void doShowLikes(final String feedId) { GWT.runAsync(UserSelectionDialog.class, new RunAsyncCallback() { @Override diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/ui/ResultsFor.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/ui/ResultsFor.java new file mode 100644 index 0000000..f4ba205 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/ui/ResultsFor.java @@ -0,0 +1,40 @@ +package org.gcube.portlets.user.newsfeed.client.ui; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Style.Cursor; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.uibinder.client.UiHandler; +import com.google.gwt.user.client.Window.Location; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.Widget; + +public class ResultsFor extends Composite { + + private static ResultsForUiBinder uiBinder = GWT + .create(ResultsForUiBinder.class); + + interface ResultsForUiBinder extends UiBinder { + } + + @UiField HTML allUpdatesLink; + @UiField HTML resultForDiv; + public ResultsFor(String hashtag) { + initWidget(uiBinder.createAndBindUi(this)); + allUpdatesLink.setHTML("
All Updates"); + allUpdatesLink.getElement().getStyle().setCursor(Cursor.POINTER); + resultForDiv.setHTML("results for " + hashtag+""); + resultForDiv.setStyleName("filter-selected"); + } + + @UiHandler("allUpdatesLink") + void onAllUpdatesClick(ClickEvent e) { + String href = Location.getHref(); + if (href.contains("?")) + href = href.substring(0, href.indexOf("?")); + Location.assign(href); + } + +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/ui/ResultsFor.ui.xml b/src/main/java/org/gcube/portlets/user/newsfeed/client/ui/ResultsFor.ui.xml new file mode 100644 index 0000000..36a403e --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/ui/ResultsFor.ui.xml @@ -0,0 +1,16 @@ + + + +
+
    +
  • + +
  • +
  • + +
  • +
+
+
+
\ No newline at end of file diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/server/NewsServiceImpl.java b/src/main/java/org/gcube/portlets/user/newsfeed/server/NewsServiceImpl.java index cc761ec..54ba0e9 100644 --- a/src/main/java/org/gcube/portlets/user/newsfeed/server/NewsServiceImpl.java +++ b/src/main/java/org/gcube/portlets/user/newsfeed/server/NewsServiceImpl.java @@ -82,8 +82,6 @@ public class NewsServiceImpl extends RemoteServiceServlet implements NewsService */ private DatabookStore store; - private boolean withinPortal = false; - private final static int MAX_FEEDS_NO = 45; public void init() { @@ -104,11 +102,8 @@ public class NewsServiceImpl extends RemoteServiceServlet implements NewsService if (user == null) { _log.warn("USER IS NULL setting testing user and Running OUTSIDE PORTAL"); user = getDevelopmentUser(); - SessionManager.getInstance().getASLSession(sessionID, user).setScope("/gcube/devNext/NextNext"); - } - else { - withinPortal = true; - } + SessionManager.getInstance().getASLSession(sessionID, user).setScope("/gcube/devsec/USTORE_VRE"); + } return SessionManager.getInstance().getASLSession(sessionID, user); } /** @@ -117,9 +112,23 @@ public class NewsServiceImpl extends RemoteServiceServlet implements NewsService */ public String getDevelopmentUser() { String user = TEST_USER; -// user = "massimiliano.assante"; + //user = "massimiliano.assante"; return user; } + /** + * + * @return true if you're running into the portal, false if in development + */ + private boolean isWithinPortal() { + try { + UserLocalServiceUtil.getService(); + return true; + } + catch (com.liferay.portal.kernel.bean.BeanLocatorException ex) { + _log.trace("Development Mode ON"); + return false; + } + } /** * this is the first method called by the web app */ @@ -199,7 +208,7 @@ public class NewsServiceImpl extends RemoteServiceServlet implements NewsService ArrayList toMerge = new ArrayList(); HashMap feedsMap = new HashMap(); try { - if (!withinPortal) { + if (!isWithinPortal()) { return getEclipseResult(userName, feedsNoPerCategory, false); } else { @@ -276,6 +285,85 @@ public class NewsServiceImpl extends RemoteServiceServlet implements NewsService return null; } + /** + * return the feeds having the hashtag passed as param the user has access on + * @param hashtag the hashtag to look for including '#' + */ + @Override + public ArrayList getFeedsByHashtag(String hashtag) { + ASLSession session = getASLSession(); + String userName = session.getUsername(); + ArrayList toMerge = new ArrayList(); + HashMap feedsMap = new HashMap(); + try { + //in case the portal is restarted and you have the social home open it will get test.user (no callback to set session info) + //this check just return nothing if that happens + if (userName.compareTo("test.user") == 0) { + _log.debug("Found " + userName + " returning nothing"); + return null; + } + + _log.info("****** retrieving feeds for user: " + userName); + + /** + * this handles the case where the portlet is deployed outside of VREs (regular) + */ + if (isInfrastructureScope()) { + User currUser = OrganizationsUtil.validateUser(userName); + //VRE Feeds + for (Organization org : currUser.getOrganizations()) { + GroupManager gm = new LiferayGroupManager(); + if (gm.isVRE(org.getOrganizationId()+"")) { + String vreid = gm.getScope(""+org.getOrganizationId()); //get the scope + _log.debug("Reading hastagged feeds for VRE: " + vreid + " hashtag=" + hashtag); + + ArrayList OrganizationFeeds = (ArrayList) store.getVREFeedsByHashtag(vreid, hashtag); + for (Feed feed : OrganizationFeeds) { + feedsMap.put(feed.getKey(), feed); + } + } + } + } + //else must be in a VRE scope + else { + String vreid = session.getScopeName(); + _log.trace("News Feed in VRE, Reading feeds for VRE: " + vreid); + ArrayList OrganizationFeeds = (ArrayList) store.getVREFeedsByHashtag(vreid, hashtag); + for (Feed feed : OrganizationFeeds) { + feedsMap.put(feed.getKey(), feed); + } + } + + for (String key: feedsMap.keySet()) { + toMerge.add(feedsMap.get(key)); + } + + //sort the feeds in reverse chronological order + Collections.sort(toMerge, Collections.reverseOrder()); + + ArrayList toReturn = new ArrayList(); + //return only feeds + if (toMerge.size() > MAX_FEEDS_NO) + for (int i = 0; i < MAX_FEEDS_NO; i++) + toReturn.add(toMerge.get(i)); + else { + return enhanceFeeds(toMerge, 2); + } + return enhanceFeeds(toReturn, 2); + } catch (PrivacyLevelTypeNotFoundException e) { + _log.error("Privacy Level not Found " + e.getMessage()); + e.printStackTrace(); + } catch (FeedTypeNotFoundException e) { + _log.error("Feed Type not Found " + e.getMessage()); + e.printStackTrace(); + } catch (ColumnNameNotFoundException e) { + _log.error("Column name not Found " + e.getMessage()); + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } /** * return only the user connection feeds */ @@ -285,7 +373,7 @@ public class NewsServiceImpl extends RemoteServiceServlet implements NewsService HashMap feedsMap = new HashMap(); String userName = getASLSession().getUsername(); try { - if (! withinPortal) { + if (! isWithinPortal()) { return getEclipseResult(userName, NewsConstants.FEEDS_NO_PER_CATEGORY, true); } else { @@ -459,11 +547,11 @@ public class NewsServiceImpl extends RemoteServiceServlet implements NewsService public boolean like(String feedid, String feedText, String feedOwnerId) { boolean likeCommitResult = false; UserInfo user = getUserSettings().getUserInfo(); - + if (user.getUsername().compareTo(TEST_USER) == 0) { return false; } - + Like toLike = new Like(UUID.randomUUID().toString(), user.getUsername(), new Date(), feedid, user.getFullName(), user.getAvatarId()); try { @@ -534,7 +622,7 @@ public class NewsServiceImpl extends RemoteServiceServlet implements NewsService return null; } //if the comment was correctly delivered && is not an app feed notify users involved - if (commentCommitResult && withinPortal) { + if (commentCommitResult && isWithinPortal()) { //if the user who commented this post is not the user who posted it notify the poster user (Feed owner) NotificationsManager nm = new ApplicationNotificationsManager(getASLSession()); if (! user.getUsername().equals(feedOwnerId) && (!isAppFeed)) { @@ -585,7 +673,8 @@ public class NewsServiceImpl extends RemoteServiceServlet implements NewsService */ private ArrayList enhanceFeeds(ArrayList toEnhance, int commentsNumberPerFeed) { ArrayList toReturn = new ArrayList(); - String username = getASLSession().getUsername(); + ASLSession session = getASLSession(); + String username = session.getUsername(); ArrayList likedFeeds = (ArrayList) store.getAllLikedFeedIdsByUser(getASLSession().getUsername()); boolean skip = false; @@ -652,7 +741,7 @@ public class NewsServiceImpl extends RemoteServiceServlet implements NewsService * @return the url of the image portrait for this portal instance */ private String getUserImagePortraitUrlLocal(String screenName) { - if (! withinPortal) { + if (! isWithinPortal()) { return ""; } StringBuilder thumbnailURL = new StringBuilder("/image/user_male_portrait?img_id="); @@ -709,9 +798,18 @@ public class NewsServiceImpl extends RemoteServiceServlet implements NewsService } @Override public boolean deleteFeed(String feedid) { - _log.trace("Attempting to delete feed " + feedid); + _log.trace("Called delete feed " + feedid); try { + Feed toDelete = store.readFeed(feedid); + List hashtags = Utils.getHashTags(toDelete.getDescription()); + if (hashtags != null && !hashtags.isEmpty()) { + _log.trace("The feed has hashtags, attempting to delete them ... " + hashtags.toString()); + boolean deletedHashtag = store.deleteHashTags(feedid, toDelete.getVreid(), hashtags); + _log.trace("deletedHashtag? " + deletedHashtag); + } + _log.trace("Attempting to delete feed " + feedid); return store.deleteFeed(feedid); + } catch (Exception e) { e.printStackTrace(); return false; @@ -719,7 +817,7 @@ public class NewsServiceImpl extends RemoteServiceServlet implements NewsService } @Override public ArrayList getOrganizationUsers(String currentScope) { - ArrayList toReturn = Utils.getOrganizationUsers(currentScope, getASLSession().getUsername(), withinPortal); + ArrayList toReturn = Utils.getOrganizationUsers(currentScope, getASLSession().getUsername(), isWithinPortal()); _log.trace("Returning " + toReturn.size() + " users for scope " + currentScope); return toReturn; } @@ -784,7 +882,7 @@ public class NewsServiceImpl extends RemoteServiceServlet implements NewsService if (fullNames == null) return new ArrayList(); else { - ArrayList allUsers = Utils.getOrganizationUsers("/"+OrganizationsUtil.getRootOrganizationName(), getASLSession().getUsername(), withinPortal); + ArrayList allUsers = Utils.getOrganizationUsers("/"+OrganizationsUtil.getRootOrganizationName(), getASLSession().getUsername(), isWithinPortal()); ArrayList toReturn = new ArrayList(); for (String fullName : fullNames) for (PickingUser puser : allUsers) { @@ -804,7 +902,7 @@ public class NewsServiceImpl extends RemoteServiceServlet implements NewsService * @throws PortalException */ private boolean isAdmin() throws PortalException, SystemException { - if (! withinPortal) + if (! isWithinPortal()) return false; try { @@ -926,4 +1024,6 @@ public class NewsServiceImpl extends RemoteServiceServlet implements NewsService + + } diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/server/Utils.java b/src/main/java/org/gcube/portlets/user/newsfeed/server/Utils.java index 0612613..b7c6b1d 100644 --- a/src/main/java/org/gcube/portlets/user/newsfeed/server/Utils.java +++ b/src/main/java/org/gcube/portlets/user/newsfeed/server/Utils.java @@ -4,6 +4,8 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.apache.commons.codec.binary.Base64; import org.gcube.common.scope.impl.ScopeBean; @@ -76,6 +78,20 @@ public class Utils { } return portalUsers; } + /** + * utility method that extract the hashtags from a text + * @param postText + * @return the list of hashtags present in the text + */ + protected static List getHashTags(String postText) { + List hashtags = new ArrayList<>(); + Pattern MY_PATTERN = Pattern.compile("#(\\w+)"); + Matcher matcher = MY_PATTERN.matcher(postText); + while (matcher.find()) { + hashtags.add("#"+matcher.group(1)); + } + return hashtags; + } /** * Escape an html string. Escaping data received from the client helps to * prevent cross-site script vulnerabilities. diff --git a/src/main/resources/org/gcube/portlets/user/newsfeed/NewsFeed.gwt.xml b/src/main/resources/org/gcube/portlets/user/newsfeed/NewsFeed.gwt.xml index 61246ea..6db3cdf 100644 --- a/src/main/resources/org/gcube/portlets/user/newsfeed/NewsFeed.gwt.xml +++ b/src/main/resources/org/gcube/portlets/user/newsfeed/NewsFeed.gwt.xml @@ -4,7 +4,7 @@ - + diff --git a/src/main/webapp/NewsFeed.css b/src/main/webapp/NewsFeed.css index 9f5e2b5..f7affaa 100644 --- a/src/main/webapp/NewsFeed.css +++ b/src/main/webapp/NewsFeed.css @@ -5,7 +5,10 @@ table { border-spacing: 0; } - +.result-hashtag { + color: #555; + font-size: 20px; +} /* Superpose TextArea and Highlight DIV trick starts here */ #comment-supercontainer { position: relative; @@ -180,6 +183,11 @@ table { background-image: none; } +.feed-filters li.feed-breadcrumb { + background-image: url("images/arrow-right.png"); +} + + .filter-selected a { color: #336699 !important; font-weight: bold; diff --git a/src/main/webapp/images/arrow-right.png b/src/main/webapp/images/arrow-right.png new file mode 100644 index 0000000..b278de8 Binary files /dev/null and b/src/main/webapp/images/arrow-right.png differ