From 5cc96918cb3c19670d751833a3845dedab0ab344 Mon Sep 17 00:00:00 2001 From: Massimiliano Assante Date: Thu, 4 Apr 2013 13:41:17 +0000 Subject: [PATCH] Initial import. git-svn-id: http://svn.research-infrastructures.eu/public/d4science/gcube/trunk/portlets/user/news-feed@72922 82a268e6-3cf1-43bd-a215-b396298e98cf --- .classpath | 36 + .project | 59 ++ .settings/.jsdtscope | 15 + .../com.google.appengine.eclipse.core.prefs | 3 + .settings/com.google.gdt.eclipse.core.prefs | 6 + .settings/com.google.gwt.eclipse.core.prefs | 5 + .settings/org.eclipse.core.resources.prefs | 7 + .settings/org.eclipse.jdt.core.prefs | 13 + .settings/org.eclipse.m2e.core.prefs | 5 + .settings/org.eclipse.m2e.wtp.prefs | 3 + .settings/org.eclipse.wst.common.component | 11 + ....eclipse.wst.common.project.facet.core.xml | 7 + ...rg.eclipse.wst.jsdt.ui.superType.container | 1 + .settings/org.maven.ide.eclipse.prefs | 9 + NewsFeedTest-dev.launch | 22 + NewsFeedTest-prod.launch | 22 + pom.xml | 266 ++++++++ .../user/newsfeed/client/FilterType.java | 5 + .../user/newsfeed/client/NewsFeed.java | 81 +++ .../user/newsfeed/client/NewsService.java | 41 ++ .../newsfeed/client/NewsServiceAsync.java | 47 ++ .../client/event/AddCommentEvent.java | 35 + .../client/event/AddCommentEventHandler.java | 7 + .../newsfeed/client/event/AddLikeEvent.java | 36 + .../client/event/AddLikeEventHandler.java | 7 + .../client/event/DeleteCommentEvent.java | 35 + .../event/DeleteCommentEventHandler.java | 7 + .../client/event/DeleteFeedEvent.java | 32 + .../client/event/DeleteFeedEventHandler.java | 7 + .../client/event/EditCommentEvent.java | 36 + .../client/event/EditCommentEventHandler.java | 7 + .../client/event/SeeCommentsEvent.java | 37 ++ .../client/event/SeeCommentsEventHandler.java | 7 + .../newsfeed/client/event/SeeLikesEvent.java | 28 + .../client/event/SeeLikesEventHandler.java | 7 + .../newsfeed/client/event/StopTimerEvent.java | 28 + .../client/event/StopTimerEventHandler.java | 7 + .../newsfeed/client/panels/NewsFeedPanel.java | 592 +++++++++++++++++ .../client/panels/dialog/LikesDialog.java | 73 ++ .../client/templates/AddCommentTemplate.java | 168 +++++ .../templates/AddCommentTemplate.ui.xml | 32 + .../client/templates/FilterPanel.java | 152 +++++ .../client/templates/FilterPanel.ui.xml | 27 + .../client/templates/LikedTemplate.java | 35 + .../client/templates/LikedTemplate.ui.xml | 25 + .../client/templates/LinkPreviewer.java | 38 ++ .../client/templates/LinkPreviewer.ui.xml | 26 + .../client/templates/SingleComment.java | 108 +++ .../client/templates/SingleComment.ui.xml | 61 ++ .../client/templates/TweetTemplate.java | 399 +++++++++++ .../client/templates/TweetTemplate.ui.xml | 38 ++ .../user/newsfeed/server/NewsServiceImpl.java | 624 ++++++++++++++++++ .../newsfeed/server/NotificationsThread.java | 39 ++ .../server/portlet/NewsFeedPortlet.java | 26 + .../user/newsfeed/shared/EnhancedFeed.java | 61 ++ .../portlets/user/newsfeed/NewsFeed.gwt.xml | 29 + src/main/webapp/NewsFeed.css | 441 +++++++++++++ src/main/webapp/NewsFeed.html | 43 ++ src/main/webapp/WEB-INF/jsp/NewsFeed_view.jsp | 20 + src/main/webapp/WEB-INF/liferay-display.xml | 8 + .../WEB-INF/liferay-plugin-package.properties | 9 + src/main/webapp/WEB-INF/liferay-portlet.xml | 29 + src/main/webapp/WEB-INF/portlet.xml | 30 + src/main/webapp/WEB-INF/web.xml | 42 ++ src/main/webapp/js/pagebus.js | 182 +++++ .../user/newsfeed/client/GwtTestNewsFeed.java | 39 ++ .../user/newsfeed/NewsFeedJUnit.gwt.xml | 7 + 67 files changed, 4390 insertions(+) create mode 100644 .classpath create mode 100644 .project create mode 100644 .settings/.jsdtscope create mode 100644 .settings/com.google.appengine.eclipse.core.prefs create mode 100644 .settings/com.google.gdt.eclipse.core.prefs create mode 100644 .settings/com.google.gwt.eclipse.core.prefs create mode 100644 .settings/org.eclipse.core.resources.prefs create mode 100644 .settings/org.eclipse.jdt.core.prefs create mode 100644 .settings/org.eclipse.m2e.core.prefs create mode 100644 .settings/org.eclipse.m2e.wtp.prefs create mode 100644 .settings/org.eclipse.wst.common.component create mode 100644 .settings/org.eclipse.wst.common.project.facet.core.xml create mode 100644 .settings/org.eclipse.wst.jsdt.ui.superType.container create mode 100644 .settings/org.maven.ide.eclipse.prefs create mode 100644 NewsFeedTest-dev.launch create mode 100644 NewsFeedTest-prod.launch create mode 100644 pom.xml create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/FilterType.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/NewsFeed.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/NewsService.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/NewsServiceAsync.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/event/AddCommentEvent.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/event/AddCommentEventHandler.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/event/AddLikeEvent.java create mode 100755 src/main/java/org/gcube/portlets/user/newsfeed/client/event/AddLikeEventHandler.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/event/DeleteCommentEvent.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/event/DeleteCommentEventHandler.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/event/DeleteFeedEvent.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/event/DeleteFeedEventHandler.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/event/EditCommentEvent.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/event/EditCommentEventHandler.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/event/SeeCommentsEvent.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/event/SeeCommentsEventHandler.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/event/SeeLikesEvent.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/event/SeeLikesEventHandler.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/event/StopTimerEvent.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/event/StopTimerEventHandler.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/panels/NewsFeedPanel.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/panels/dialog/LikesDialog.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/templates/AddCommentTemplate.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/templates/AddCommentTemplate.ui.xml create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/templates/FilterPanel.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/templates/FilterPanel.ui.xml create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/templates/LikedTemplate.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/templates/LikedTemplate.ui.xml create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/templates/LinkPreviewer.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/templates/LinkPreviewer.ui.xml create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/templates/SingleComment.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/templates/SingleComment.ui.xml create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/templates/TweetTemplate.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/client/templates/TweetTemplate.ui.xml create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/server/NewsServiceImpl.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/server/NotificationsThread.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/server/portlet/NewsFeedPortlet.java create mode 100644 src/main/java/org/gcube/portlets/user/newsfeed/shared/EnhancedFeed.java create mode 100644 src/main/resources/org/gcube/portlets/user/newsfeed/NewsFeed.gwt.xml create mode 100644 src/main/webapp/NewsFeed.css create mode 100644 src/main/webapp/NewsFeed.html create mode 100644 src/main/webapp/WEB-INF/jsp/NewsFeed_view.jsp create mode 100644 src/main/webapp/WEB-INF/liferay-display.xml create mode 100644 src/main/webapp/WEB-INF/liferay-plugin-package.properties create mode 100644 src/main/webapp/WEB-INF/liferay-portlet.xml create mode 100644 src/main/webapp/WEB-INF/portlet.xml create mode 100644 src/main/webapp/WEB-INF/web.xml create mode 100644 src/main/webapp/js/pagebus.js create mode 100644 src/test/java/org/gcube/portlets/user/newsfeed/client/GwtTestNewsFeed.java create mode 100644 src/test/resources/org/gcube/portlets/user/newsfeed/NewsFeedJUnit.gwt.xml diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..ba1633b --- /dev/null +++ b/.classpath @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..9b84900 --- /dev/null +++ b/.project @@ -0,0 +1,59 @@ + + + news-feed + news-feed project + + + + + org.eclipse.wst.jsdt.core.javascriptValidator + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.wst.validation.validationbuilder + + + + + org.maven.ide.eclipse.maven2Builder + + + + + com.google.gdt.eclipse.core.webAppProjectValidator + + + + + com.google.gwt.eclipse.core.gwtProjectValidator + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + org.eclipse.jem.workbench.JavaEMFNature + org.eclipse.wst.common.modulecore.ModuleCoreNature + org.maven.ide.eclipse.maven2Nature + org.eclipse.jdt.core.javanature + org.eclipse.wst.common.project.facet.core.nature + org.eclipse.wst.jsdt.core.jsNature + com.google.gwt.eclipse.core.gwtNature + + diff --git a/.settings/.jsdtscope b/.settings/.jsdtscope new file mode 100644 index 0000000..ba3c245 --- /dev/null +++ b/.settings/.jsdtscope @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/.settings/com.google.appengine.eclipse.core.prefs b/.settings/com.google.appengine.eclipse.core.prefs new file mode 100644 index 0000000..a60576c --- /dev/null +++ b/.settings/com.google.appengine.eclipse.core.prefs @@ -0,0 +1,3 @@ +#Thu Jun 16 10:18:26 CEST 2011 +eclipse.preferences.version=1 +filesCopiedToWebInfLib= diff --git a/.settings/com.google.gdt.eclipse.core.prefs b/.settings/com.google.gdt.eclipse.core.prefs new file mode 100644 index 0000000..582ee9f --- /dev/null +++ b/.settings/com.google.gdt.eclipse.core.prefs @@ -0,0 +1,6 @@ +#Tue Apr 02 15:59:20 CEST 2013 +eclipse.preferences.version=1 +jarsExcludedFromWebInfLib= +lastWarOutDir=/Users/massi/Documents/workspace/news-feed/target/news-feed-1.0.0-SNAPSHOT +warSrcDir=src/main/webapp +warSrcDirIsOutput=false diff --git a/.settings/com.google.gwt.eclipse.core.prefs b/.settings/com.google.gwt.eclipse.core.prefs new file mode 100644 index 0000000..c803c44 --- /dev/null +++ b/.settings/com.google.gwt.eclipse.core.prefs @@ -0,0 +1,5 @@ +#Thu Jun 16 11:14:17 CEST 2011 +eclipse.preferences.version=1 +entryPointModules= +filesCopiedToWebInfLib=gwt-servlet.jar +gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgc3JjL21haW4vd2ViYXBwXV0+PC9leHRyYS1hcmdzPjx2bS1hcmdzPjwhW0NEQVRBWy1YbXg1MTJtXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLmNvbXBhbnkuU29tZU1vZHVsZTwvZW50cnktcG9pbnQtbW9kdWxlPjwvZ3d0LWNvbXBpbGUtc2V0dGluZ3M+ diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..02122d8 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,7 @@ +#Tue Apr 02 15:52:18 CEST 2013 +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 +encoding/=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..6f980e1 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,13 @@ +#Tue Apr 02 16:01:09 CEST 2013 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..68e3275 --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,5 @@ +#Tue Apr 02 15:52:17 CEST 2013 +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/.settings/org.eclipse.m2e.wtp.prefs b/.settings/org.eclipse.m2e.wtp.prefs new file mode 100644 index 0000000..2ab9c2c --- /dev/null +++ b/.settings/org.eclipse.m2e.wtp.prefs @@ -0,0 +1,3 @@ +#Tue Apr 02 16:00:18 CEST 2013 +eclipse.preferences.version=1 +org.eclipse.m2e.wtp.enabledProjectSpecificPrefs=false diff --git a/.settings/org.eclipse.wst.common.component b/.settings/org.eclipse.wst.common.component new file mode 100644 index 0000000..d6b2bd7 --- /dev/null +++ b/.settings/org.eclipse.wst.common.component @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/.settings/org.eclipse.wst.common.project.facet.core.xml b/.settings/org.eclipse.wst.common.project.facet.core.xml new file mode 100644 index 0000000..7895606 --- /dev/null +++ b/.settings/org.eclipse.wst.common.project.facet.core.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.settings/org.eclipse.wst.jsdt.ui.superType.container b/.settings/org.eclipse.wst.jsdt.ui.superType.container new file mode 100644 index 0000000..3bd5d0a --- /dev/null +++ b/.settings/org.eclipse.wst.jsdt.ui.superType.container @@ -0,0 +1 @@ +org.eclipse.wst.jsdt.launching.baseBrowserLibrary \ No newline at end of file diff --git a/.settings/org.maven.ide.eclipse.prefs b/.settings/org.maven.ide.eclipse.prefs new file mode 100644 index 0000000..c74c58e --- /dev/null +++ b/.settings/org.maven.ide.eclipse.prefs @@ -0,0 +1,9 @@ +#Thu Sep 02 10:42:12 CEST 2010 +activeProfiles= +eclipse.preferences.version=1 +fullBuildGoals=process-test-resources +includeModules=false +resolveWorkspaceProjects=true +resourceFilterGoals=process-resources resources\:testResources +skipCompilerPlugin=true +version=1 diff --git a/NewsFeedTest-dev.launch b/NewsFeedTest-dev.launch new file mode 100644 index 0000000..bc43953 --- /dev/null +++ b/NewsFeedTest-dev.launch @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/NewsFeedTest-prod.launch b/NewsFeedTest-prod.launch new file mode 100644 index 0000000..94c3839 --- /dev/null +++ b/NewsFeedTest-prod.launch @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..b9b8e04 --- /dev/null +++ b/pom.xml @@ -0,0 +1,266 @@ + + + + 4.0.0 + + maven-parent + org.gcube.tools + 1.0.0 + + + + org.gcube.portlets.user + news-feed + war + 1.0.0-SNAPSHOT + + gCube News Feed Portlet + + gCube News Feed for exchanging updates with other users of VREs. + + + scm:svn:http://svn.d4science.research-infrastructures.eu/gcube/trunk/portlets/user/${project.artifactId} + scm:https://svn.d4science.research-infrastructures.eu/gcube/trunk/portlets/user/${project.artifactId} + http://svn.d4science.research-infrastructures.eu/gcube/trunk/portlets/user/${project.artifactId} + + + + 2.4.0 + distro + + 1.6 + 1.6 + ${project.build.directory}/${project.build.finalName} + + UTF-8 + UTF-8 + + + + + + + xerces + xercesImpl + 2.9.1 + provided + + + com.google.gwt + gwt-user + ${gwtVersion} + provided + + + com.google.gwt + gwt-servlet + ${gwtVersion} + provided + + + org.gcube.portal + social-networking-library + [1.0.0-SNAPSHOT, 2.0.0-SNAPSHOT) + provided + + + org.gcube.core + gcf + [1.5.0-SNAPSHOT, 2.0.0-SNAPSHOT) + provided + + + org.gcube.portlets.user + wsmail-widget + [1.1.0-SNAPSHOT, 2.0.0-SNAPSHOT) + + + org.gcube.portlets.user + gcube-widgets + [1.4.0-SNAPSHOT, 2.0.0-SNAPSHOT) + provided + + + org.gcube.portal + custom-portal-handler + [1.2.0-SNAPSHOT, 2.0.0-SNAPSHOT) + provided + + + org.gcube.applicationsupportlayer + aslsocial + [0.1.0-SNAPSHOT, 1.0.0-SNAPSHOT) + provided + + + org.gcube.applicationsupportlayer + aslcore + [3.2.0-SNAPSHOT, 4.0.0-SNAPSHOT) + provided + + + com.google + gwt-jsonmaker + 1.2.1 + + + net.eliasbalasis + tibcopagebus4gwt + 1.2.0 + + + commons-validator + commons-validator + 1.4.0 + provided + + + com.liferay.portal + portal-service + 6.0.6 + provided + + + javax.portlet + portlet-api + 2.0 + provided + + + org.slf4j + slf4j-log4j12 + 1.6.4 + runtime + + + org.slf4j + slf4j-api + 1.6.4 + runtime + + + junit + junit + 4.7 + provided + + + javax.validation + validation-api + 1.0.0.GA + test + + + javax.validation + validation-api + 1.0.0.GA + sources + test + + + + + + ${webappDirectory}/WEB-INF/classes + + + + + + org.codehaus.mojo + gwt-maven-plugin + 2.4.0 + + + + compile + + + + + + + Messages.html + ${webappDirectory} + + + + + + org.apache.maven.plugins + maven-war-plugin + 2.1.1 + + + compile + + exploded + + + + + ${webappDirectory} + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.5 + 1.5 + + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.2 + + + ${distroDirectory}/descriptor.xml + + + + + servicearchive + install + + single + + + + + + org.apache.maven.plugins + maven-resources-plugin + 2.5 + + + copy-profile + install + + copy-resources + + + target + + + ${distroDirectory} + true + + profile.xml + + + + + + + + + + + diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/FilterType.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/FilterType.java new file mode 100644 index 0000000..50ed553 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/FilterType.java @@ -0,0 +1,5 @@ +package org.gcube.portlets.user.newsfeed.client; + +public enum FilterType { + ALL_UPDATES, CONNECTIONS, LIKEDFEEDS, MINE; +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/NewsFeed.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/NewsFeed.java new file mode 100644 index 0000000..de17031 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/NewsFeed.java @@ -0,0 +1,81 @@ +package org.gcube.portlets.user.newsfeed.client; + +import net.eliasbalasis.tibcopagebus4gwt.client.PageBusAdapter; +import net.eliasbalasis.tibcopagebus4gwt.client.PageBusAdapterException; +import net.eliasbalasis.tibcopagebus4gwt.client.PageBusEvent; +import net.eliasbalasis.tibcopagebus4gwt.client.PageBusListener; + +import org.gcube.portal.databook.shared.ClientFeed; +import org.gcube.portal.databook.shared.ClientFeed.ClientFeedJsonizer; +import org.gcube.portlets.user.newsfeed.client.panels.NewsFeedPanel; +import org.jsonmaker.gwt.client.Jsonizer; + +import com.google.gwt.core.client.EntryPoint; +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.logical.shared.ResizeEvent; +import com.google.gwt.event.logical.shared.ResizeHandler; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.RootPanel; + +/** + * Entry point classes define onModuleLoad(). + */ +public class NewsFeed implements EntryPoint { + + private final String UNIQUE_DIV = "newsfeedDIV"; + final PageBusAdapter pageBusAdapter = new PageBusAdapter(); + NewsFeedPanel mainPanel; + /** + * This is the entry point method. + */ + public void onModuleLoad() { + mainPanel = new NewsFeedPanel(); + resize(); + RootPanel.get(UNIQUE_DIV).add(mainPanel); + + Window.addResizeHandler(new ResizeHandler() { + public void onResize(ResizeEvent event) { + int width = RootPanel.get(UNIQUE_DIV).getOffsetWidth(); + mainPanel.setWidth(""+width); + } + }); + + ClientFeed notification = new ClientFeed(); + //Subscribe to message and associate subsequent receptions with custom subscriber data + try + { + pageBusAdapter.PageBusSubscribe("org.gcube.portal.databook.shared", null, null, notification, (Jsonizer)GWT.create(ClientFeedJsonizer.class)); + } + catch (PageBusAdapterException e1) + { + e1.printStackTrace(); + } + + pageBusAdapter.addPageBusSubscriptionCallbackListener(new PageBusListener() { + public void onPageBusSubscriptionCallback(PageBusEvent event) { + // translate JavaScript message contents and subscriber data to their Java equivalents + try { + ClientFeed feed = (ClientFeed)event.getMessage((Jsonizer)GWT.create(ClientFeedJsonizer.class)); + mainPanel.addJustAddedFeed(feed); + + } catch (PageBusAdapterException e) { + e.printStackTrace(); + } + } + public String getName() { + return null; + } + }); + + + } + + private void resize() { + int width = RootPanel.get(UNIQUE_DIV).getOffsetWidth(); + mainPanel.setWidth(""+width); + } + +} 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 new file mode 100644 index 0000000..0dd334c --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/NewsService.java @@ -0,0 +1,41 @@ +package org.gcube.portlets.user.newsfeed.client; + +import java.util.ArrayList; + +import org.gcube.portal.databook.shared.Comment; +import org.gcube.portal.databook.shared.Like; +import org.gcube.portal.databook.shared.UserInfo; +import org.gcube.portlets.user.newsfeed.shared.EnhancedFeed; + +import com.google.gwt.user.client.rpc.RemoteService; +import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; + +/** + * The client side stub for the RPC service. + */ +@RemoteServiceRelativePath("newsServlet") +public interface NewsService extends RemoteService { + ArrayList getAllUpdateUserFeeds(); + + ArrayList getOnlyConnectionsUserFeeds(); + + ArrayList getOnlyMyUserFeeds(); + + ArrayList getOnlyLikedFeeds(); + + boolean like(String feedid, String feedText, String feedOwnerId); + + boolean deleteComment(String commentid, String feedid); + + boolean deleteFeed(String feedid); + + Comment comment(String feedid, String text, String feedOwnerId); + + Comment editComment(Comment toEdit); + + ArrayList getAllLikesByFeed(String feedid); + + ArrayList getAllCommentsByFeed(String feedid); + + UserInfo getUserInfo(); +} 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 new file mode 100644 index 0000000..7a0043d --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/NewsServiceAsync.java @@ -0,0 +1,47 @@ +package org.gcube.portlets.user.newsfeed.client; + +import java.util.ArrayList; + +import org.gcube.portal.databook.shared.Comment; +import org.gcube.portal.databook.shared.Like; +import org.gcube.portal.databook.shared.UserInfo; +import org.gcube.portlets.user.newsfeed.shared.EnhancedFeed; + +import com.google.gwt.user.client.rpc.AsyncCallback; + +/** + * The async counterpart of NewsService. + */ +public interface NewsServiceAsync { + + void getAllUpdateUserFeeds(AsyncCallback> callback); + + void getOnlyConnectionsUserFeeds( + AsyncCallback> callback); + + void like(String feedid, String feedText, String feedOwnerId, + AsyncCallback callback); + + void getAllLikesByFeed(String feedid, + AsyncCallback> callback); + + void getOnlyMyUserFeeds(AsyncCallback> callback); + + void getUserInfo(AsyncCallback callback); + + void comment(String feedid, String text, String feedOwnerId, + AsyncCallback callback); + + void getAllCommentsByFeed(String feedid, + AsyncCallback> callback); + + void deleteComment(String commentid, String feedid, + AsyncCallback callback); + + void deleteFeed(String feedid, AsyncCallback callback); + + void editComment(Comment toEdit, AsyncCallback callback); + + void getOnlyLikedFeeds(AsyncCallback> callback); + +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/event/AddCommentEvent.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/AddCommentEvent.java new file mode 100644 index 0000000..aac246c --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/AddCommentEvent.java @@ -0,0 +1,35 @@ +package org.gcube.portlets.user.newsfeed.client.event; + +import org.gcube.portlets.user.newsfeed.client.templates.TweetTemplate; + +import com.google.gwt.event.shared.GwtEvent; + + + +public class AddCommentEvent extends GwtEvent { + public static Type TYPE = new Type(); + + private TweetTemplate owner; + private String text; + + public TweetTemplate getOwner() { + return owner; + } + public String getText() { + return text; + } + public AddCommentEvent(TweetTemplate owner, String text) { + this.owner = owner; + this.text = text; + } + + @Override + public Type getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(AddCommentEventHandler handler) { + handler.onAddComment(this); + } +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/event/AddCommentEventHandler.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/AddCommentEventHandler.java new file mode 100644 index 0000000..0ab2960 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/AddCommentEventHandler.java @@ -0,0 +1,7 @@ +package org.gcube.portlets.user.newsfeed.client.event; + +import com.google.gwt.event.shared.EventHandler; + +public interface AddCommentEventHandler extends EventHandler { + void onAddComment(AddCommentEvent event); +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/event/AddLikeEvent.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/AddLikeEvent.java new file mode 100644 index 0000000..81460f8 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/AddLikeEvent.java @@ -0,0 +1,36 @@ +package org.gcube.portlets.user.newsfeed.client.event; + +import org.gcube.portlets.user.newsfeed.client.templates.TweetTemplate; + +import com.google.gwt.event.shared.GwtEvent; + + + +public class AddLikeEvent extends GwtEvent { + public static Type TYPE = new Type(); + private TweetTemplate owner; + private final String feedid; + + + + public AddLikeEvent(TweetTemplate owner, String feedid) { + this.feedid = feedid; + this.owner = owner; + } + + public String getFeedId() { + return feedid; + } + public TweetTemplate getOwner() { + return owner; + } + @Override + public Type getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(AddLikeEventHandler handler) { + handler.onAddLike(this); + } +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/event/AddLikeEventHandler.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/AddLikeEventHandler.java new file mode 100755 index 0000000..3a3a5ec --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/AddLikeEventHandler.java @@ -0,0 +1,7 @@ +package org.gcube.portlets.user.newsfeed.client.event; + +import com.google.gwt.event.shared.EventHandler; + +public interface AddLikeEventHandler extends EventHandler { + void onAddLike(AddLikeEvent event); +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/event/DeleteCommentEvent.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/DeleteCommentEvent.java new file mode 100644 index 0000000..b295b77 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/DeleteCommentEvent.java @@ -0,0 +1,35 @@ +package org.gcube.portlets.user.newsfeed.client.event; + +import org.gcube.portlets.user.newsfeed.client.templates.TweetTemplate; + +import com.google.gwt.event.shared.GwtEvent; + + + +public class DeleteCommentEvent extends GwtEvent { + public static Type TYPE = new Type(); + + private TweetTemplate owner; + private String commentid; + + public TweetTemplate getOwner() { + return owner; + } + public String getCommentId() { + return commentid; + } + public DeleteCommentEvent(TweetTemplate owner, String commentid) { + this.owner = owner; + this.commentid = commentid; + } + + @Override + public Type getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(DeleteCommentEventHandler handler) { + handler.onDeleteComment(this); + } +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/event/DeleteCommentEventHandler.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/DeleteCommentEventHandler.java new file mode 100644 index 0000000..7476e01 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/DeleteCommentEventHandler.java @@ -0,0 +1,7 @@ +package org.gcube.portlets.user.newsfeed.client.event; + +import com.google.gwt.event.shared.EventHandler; + +public interface DeleteCommentEventHandler extends EventHandler { + void onDeleteComment(DeleteCommentEvent event); +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/event/DeleteFeedEvent.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/DeleteFeedEvent.java new file mode 100644 index 0000000..117a15b --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/DeleteFeedEvent.java @@ -0,0 +1,32 @@ +package org.gcube.portlets.user.newsfeed.client.event; + +import org.gcube.portlets.user.newsfeed.client.templates.TweetTemplate; + +import com.google.gwt.event.shared.GwtEvent; + + + +public class DeleteFeedEvent extends GwtEvent { + public static Type TYPE = new Type(); + + private TweetTemplate toDelete; + + + public TweetTemplate getToDelete() { + return toDelete; + } + + public DeleteFeedEvent(TweetTemplate toDelete) { + this.toDelete = toDelete; + } + + @Override + public Type getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(DeleteFeedEventHandler handler) { + handler.onDeleteFeed(this); + } +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/event/DeleteFeedEventHandler.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/DeleteFeedEventHandler.java new file mode 100644 index 0000000..67dd86c --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/DeleteFeedEventHandler.java @@ -0,0 +1,7 @@ +package org.gcube.portlets.user.newsfeed.client.event; + +import com.google.gwt.event.shared.EventHandler; + +public interface DeleteFeedEventHandler extends EventHandler { + void onDeleteFeed(DeleteFeedEvent event); +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/event/EditCommentEvent.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/EditCommentEvent.java new file mode 100644 index 0000000..bd7ee86 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/EditCommentEvent.java @@ -0,0 +1,36 @@ +package org.gcube.portlets.user.newsfeed.client.event; + +import org.gcube.portal.databook.shared.Comment; +import org.gcube.portlets.user.newsfeed.client.templates.TweetTemplate; + +import com.google.gwt.event.shared.GwtEvent; + + + +public class EditCommentEvent extends GwtEvent { + public static Type TYPE = new Type(); + + private TweetTemplate owner; + private Comment edited; + + public TweetTemplate getOwner() { + return owner; + } + public Comment getCommentInstance() { + return edited; + } + public EditCommentEvent(TweetTemplate owner, Comment editedComment) { + this.owner = owner; + this.edited = editedComment; + } + + @Override + public Type getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(EditCommentEventHandler handler) { + handler.onEditComment(this); + } +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/event/EditCommentEventHandler.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/EditCommentEventHandler.java new file mode 100644 index 0000000..78c270a --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/EditCommentEventHandler.java @@ -0,0 +1,7 @@ +package org.gcube.portlets.user.newsfeed.client.event; + +import com.google.gwt.event.shared.EventHandler; + +public interface EditCommentEventHandler extends EventHandler { + void onEditComment(EditCommentEvent event); +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/event/SeeCommentsEvent.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/SeeCommentsEvent.java new file mode 100644 index 0000000..b239987 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/SeeCommentsEvent.java @@ -0,0 +1,37 @@ +package org.gcube.portlets.user.newsfeed.client.event; + +import org.gcube.portlets.user.newsfeed.client.templates.TweetTemplate; + +import com.google.gwt.event.shared.GwtEvent; + + + +public class SeeCommentsEvent extends GwtEvent { + public static Type TYPE = new Type(); + + private TweetTemplate owner; + + private boolean commentForm2Add; + + public TweetTemplate getOwner() { + return owner; + } + + public boolean isCommentForm2Add() { + return commentForm2Add; + } + public SeeCommentsEvent(TweetTemplate owner, boolean commentForm2Add) { + this.owner = owner; + this.commentForm2Add = commentForm2Add; + } + + @Override + public Type getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(SeeCommentsEventHandler handler) { + handler.onSeeComments(this); + } +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/event/SeeCommentsEventHandler.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/SeeCommentsEventHandler.java new file mode 100644 index 0000000..33b5e64 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/SeeCommentsEventHandler.java @@ -0,0 +1,7 @@ +package org.gcube.portlets.user.newsfeed.client.event; + +import com.google.gwt.event.shared.EventHandler; + +public interface SeeCommentsEventHandler extends EventHandler { + void onSeeComments(SeeCommentsEvent event); +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/event/SeeLikesEvent.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/SeeLikesEvent.java new file mode 100644 index 0000000..f1e1a17 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/SeeLikesEvent.java @@ -0,0 +1,28 @@ +package org.gcube.portlets.user.newsfeed.client.event; + +import com.google.gwt.event.shared.GwtEvent; + + + +public class SeeLikesEvent extends GwtEvent { + public static Type TYPE = new Type(); + private final String feedid; + + public SeeLikesEvent(String feedid) { + this.feedid = feedid; + } + + public String getFeedId() { + return feedid; + } + + @Override + public Type getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(SeeLikesEventHandler handler) { + handler.onSeeLikes(this); + } +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/event/SeeLikesEventHandler.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/SeeLikesEventHandler.java new file mode 100644 index 0000000..06f71c7 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/SeeLikesEventHandler.java @@ -0,0 +1,7 @@ +package org.gcube.portlets.user.newsfeed.client.event; + +import com.google.gwt.event.shared.EventHandler; + +public interface SeeLikesEventHandler extends EventHandler { + void onSeeLikes(SeeLikesEvent event); +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/event/StopTimerEvent.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/StopTimerEvent.java new file mode 100644 index 0000000..7caa594 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/StopTimerEvent.java @@ -0,0 +1,28 @@ +package org.gcube.portlets.user.newsfeed.client.event; + +import com.google.gwt.event.shared.GwtEvent; + + + +public class StopTimerEvent extends GwtEvent { + public static Type TYPE = new Type(); + + private boolean restart; + public StopTimerEvent(boolean restart) { + this.restart = restart; + } + + public boolean isRestart() { + return restart; + } + + @Override + public Type getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(StopTimerEventHandler handler) { + handler.onStopTimer(this); + } +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/event/StopTimerEventHandler.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/StopTimerEventHandler.java new file mode 100644 index 0000000..c76c02b --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/event/StopTimerEventHandler.java @@ -0,0 +1,7 @@ +package org.gcube.portlets.user.newsfeed.client.event; + +import com.google.gwt.event.shared.EventHandler; + +public interface StopTimerEventHandler extends EventHandler { + void onStopTimer(StopTimerEvent event); +} 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 new file mode 100644 index 0000000..c841c12 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/panels/NewsFeedPanel.java @@ -0,0 +1,592 @@ +package org.gcube.portlets.user.newsfeed.client.panels; + +import java.util.ArrayList; + +import org.gcube.portal.databook.shared.ClientFeed; +import org.gcube.portal.databook.shared.Comment; +import org.gcube.portal.databook.shared.Feed; +import org.gcube.portal.databook.shared.FeedType; +import org.gcube.portal.databook.shared.Like; +import org.gcube.portal.databook.shared.PrivacyLevel; +import org.gcube.portal.databook.shared.UserInfo; +import org.gcube.portlets.user.newsfeed.client.FilterType; +import org.gcube.portlets.user.newsfeed.client.NewsService; +import org.gcube.portlets.user.newsfeed.client.NewsServiceAsync; +import org.gcube.portlets.user.newsfeed.client.event.AddCommentEvent; +import org.gcube.portlets.user.newsfeed.client.event.AddCommentEventHandler; +import org.gcube.portlets.user.newsfeed.client.event.AddLikeEvent; +import org.gcube.portlets.user.newsfeed.client.event.AddLikeEventHandler; +import org.gcube.portlets.user.newsfeed.client.event.DeleteCommentEvent; +import org.gcube.portlets.user.newsfeed.client.event.DeleteCommentEventHandler; +import org.gcube.portlets.user.newsfeed.client.event.DeleteFeedEvent; +import org.gcube.portlets.user.newsfeed.client.event.DeleteFeedEventHandler; +import org.gcube.portlets.user.newsfeed.client.event.EditCommentEvent; +import org.gcube.portlets.user.newsfeed.client.event.EditCommentEventHandler; +import org.gcube.portlets.user.newsfeed.client.event.SeeCommentsEvent; +import org.gcube.portlets.user.newsfeed.client.event.SeeCommentsEventHandler; +import org.gcube.portlets.user.newsfeed.client.event.SeeLikesEvent; +import org.gcube.portlets.user.newsfeed.client.event.SeeLikesEventHandler; +import org.gcube.portlets.user.newsfeed.client.event.StopTimerEvent; +import org.gcube.portlets.user.newsfeed.client.event.StopTimerEventHandler; +import org.gcube.portlets.user.newsfeed.client.panels.dialog.LikesDialog; +import org.gcube.portlets.user.newsfeed.client.templates.FilterPanel; +import org.gcube.portlets.user.newsfeed.client.templates.SingleComment; +import org.gcube.portlets.user.newsfeed.client.templates.TweetTemplate; +import org.gcube.portlets.user.newsfeed.shared.EnhancedFeed; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.RunAsyncCallback; +import com.google.gwt.event.shared.HandlerManager; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.HasAlignment; +import com.google.gwt.user.client.ui.HasVerticalAlignment; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.Image; +import com.google.gwt.user.client.ui.VerticalPanel; +/** + * + * @author Massimiliano Assante, ISTI-CNR + * + */ +public class NewsFeedPanel extends Composite { + + /** + * Create a remote service proxy to talk to the server-side News service. + */ + private final NewsServiceAsync newsService = GWT.create(NewsService.class); + private final HandlerManager eventBus = new HandlerManager(null); + + private VerticalPanel mainPanel = new VerticalPanel(); + private HorizontalPanel filterPanel = new HorizontalPanel(); + private VerticalPanel newsPanel = new VerticalPanel(); + + private static final String warning = GWT.getModuleBaseURL() + "../images/warning_blue.png"; + private static final String spacer = GWT.getModuleBaseURL() + "../images/feeds-spacer.gif"; + public static final String loading = GWT.getModuleBaseURL() + "../images/feeds-loader.gif"; + + public static final String LIKE_LABEL = "Favorite"; + public static final String LIKED_LABEL = "Favorited"; + public static final String COMMENT_LABEL = "Reply"; + public static final String MESSAGE_LABEL = "Message"; + + public static final int delayMillis = 300000; //5 minutes + + private boolean isFirstTweet = false; + + private Image loadingImage; + private UserInfo myUserInfo; + + private FilterType currentFilter; + private Timer feedsTimer; + /** + * events binder + */ + private void bind() { + + eventBus.addHandler(StopTimerEvent.TYPE, new StopTimerEventHandler() { + @Override + public void onStopTimer(StopTimerEvent event) { + GWT.log("Stop timer"); + if (event.isRestart()) + resetTimer(); + else + stopTimer(); + } + }); + + eventBus.addHandler(AddLikeEvent.TYPE, new AddLikeEventHandler() { + @Override + public void onAddLike(AddLikeEvent event) { + resetTimer(); + doAddLike( event.getOwner(), event.getFeedId()); + } + }); + + eventBus.addHandler(AddCommentEvent.TYPE, new AddCommentEventHandler() { + @Override + public void onAddComment(AddCommentEvent event) { + resetTimer(); + doAddComment(event.getOwner(), event.getText()); + } + }); + + eventBus.addHandler(EditCommentEvent.TYPE, new EditCommentEventHandler() { + @Override + public void onEditComment(EditCommentEvent event) { + resetTimer(); + doEditComment(event.getOwner(), event.getCommentInstance()); + } + }); + + + eventBus.addHandler(SeeLikesEvent.TYPE, new SeeLikesEventHandler() { + @Override + public void onSeeLikes(SeeLikesEvent event) { + resetTimer(); + doShowLikes(event.getFeedId()); + } + }); + + eventBus.addHandler(SeeCommentsEvent.TYPE, new SeeCommentsEventHandler() { + @Override + public void onSeeComments(SeeCommentsEvent event) { + resetTimer(); + doShowComments(event.getOwner(), event.isCommentForm2Add()); + } + }); + + eventBus.addHandler(DeleteCommentEvent.TYPE, new DeleteCommentEventHandler() { + @Override + public void onDeleteComment(DeleteCommentEvent event) { + resetTimer(); + doDeleteComment(event.getOwner(), event.getCommentId()); + } + }); + + eventBus.addHandler(DeleteFeedEvent.TYPE, new DeleteFeedEventHandler() { + @Override + public void onDeleteFeed(DeleteFeedEvent event) { + resetTimer(); + doDeleteFeed(event.getToDelete()); + } + }); + } + + + + /** + * + */ + public NewsFeedPanel() { + bind(); + mainPanel.add(filterPanel); + mainPanel.add(newsPanel); + + filterPanel.add(new FilterPanel(this, newsService)); + initWidget(mainPanel); + newsPanel.clear(); + newsPanel.setWidth("100%"); + newsPanel.setHeight("300px"); + newsPanel.setHorizontalAlignment(HasAlignment.ALIGN_CENTER); + newsPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_TOP); + loadingImage = new Image(loading); + newsPanel.add(loadingImage); + + newsService.getUserInfo(new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + } + + @Override + public void onSuccess(UserInfo result) { + myUserInfo = result; + if (result.getUsername().equals("test.user")) { + Window.alert("Your session has expired, please log out and login again"); + } + else { + showAllUpdatesFeeds(); + currentFilter = FilterType.ALL_UPDATES; + } + } + }); + + feedsTimer = new Timer() { + @Override + public void run() { + refreshFeeds(); + } + }; + feedsTimer.scheduleRepeating(delayMillis); + } + + private void resetTimer() { + feedsTimer.cancel(); + feedsTimer.scheduleRepeating(delayMillis); + } + private void stopTimer() { + feedsTimer.cancel(); + } + /** + * + */ + private void refreshFeeds() { + switch (currentFilter) { + case ALL_UPDATES: + showAllUpdatesFeeds(); + break; + case CONNECTIONS: + showOnlyConnectionsFeeds(); + break; + case MINE: + showOnlyMyFeeds(); + break; + } + } + /** + * All Updates + */ + public void showAllUpdatesFeeds() { + showLoader(); + resetTimer(); + newsService.getAllUpdateUserFeeds(new AsyncCallback>() { + @Override + public void onSuccess(ArrayList feeds) { + newsPanel.clear(); + if (feeds != null) { + if (feeds.size() == 0) { + newsPanel.add(new HTML("
" + + "Looks like we've got nothing for you at the moment.
" + + "You may begin by joining some of the available " + + "
Virtual Research Environments.
")); + isFirstTweet = true; + } + else { + newsPanel.setHeight(""); + newsPanel.setHorizontalAlignment(HasAlignment.ALIGN_LEFT); + newsPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_TOP); + for (EnhancedFeed feed : feeds) + newsPanel.add(new TweetTemplate(myUserInfo, feed, eventBus)); + if (feeds.size() < 5) { + newsPanel.add(new Image(spacer)); + } + isFirstTweet = false; + } + } else { + showProblems(); + } + + } + + @Override + public void onFailure(Throwable caught) { + newsPanel.clear(); + newsPanel.add(new HTML("
" + + "Ops! There were problems while retrieving your feeds!.
" + + "Please try again in a short while.
")); + } + }); + } + + /** + * Only User Connections + */ + public void showOnlyConnectionsFeeds() { + showLoader(); + resetTimer(); + newsService.getOnlyConnectionsUserFeeds(new AsyncCallback>() { + @Override + public void onSuccess(ArrayList feeds) { + if (feeds != null) { + newsPanel.clear(); + if (feeds.size() == 0) { + //TODO: provide actual link for Making some friends + newsPanel.add(new HTML("
" + + "Looks like we've got nothing for you at the moment.
" + + "You may begin by adding some friend!
")); + isFirstTweet = true; + } + else { + newsPanel.setHeight(""); + newsPanel.setHorizontalAlignment(HasAlignment.ALIGN_LEFT); + newsPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_TOP); + for (EnhancedFeed feed : feeds) + newsPanel.add(new TweetTemplate(myUserInfo, feed, eventBus)); + if (feeds.size() < 5) { + newsPanel.add(new Image(spacer)); + } + isFirstTweet = false; + } + } else + showProblems(); + + } + + @Override + public void onFailure(Throwable caught) { + loadingImage.setUrl(warning); + newsPanel.add(new HTML("Ops! There were problems while retrieving your feeds! Please try again in a short while")); + } + }); + } + /** + * Only User Feeds + */ + public void showOnlyMyFeeds() { + showLoader(); + resetTimer(); + newsService.getOnlyMyUserFeeds(new AsyncCallback>() { + @Override + public void onSuccess(ArrayList feeds) { + if (feeds != null) { + newsPanel.clear(); + if (feeds.size() == 0) { + newsPanel.add(new HTML("
" + + "Looks like we've got nothing for you at the moment.
" + + "You may begin by sharing an update!
")); + isFirstTweet = true; + } + else { + newsPanel.setHeight(""); + newsPanel.setHorizontalAlignment(HasAlignment.ALIGN_LEFT); + newsPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_TOP); + for (EnhancedFeed feed : feeds) + newsPanel.add(new TweetTemplate(myUserInfo, feed, eventBus)); + if (feeds.size() < 5) { + newsPanel.add(new Image(spacer)); + } + isFirstTweet = false; + } + } else + showProblems(); + + } + + @Override + public void onFailure(Throwable caught) { + loadingImage.setUrl(warning); + newsPanel.add(new HTML("Ops! There were problems while retrieving your feeds! Please try again in a short while")); + } + }); + } + /** + * Only User Liked Feeds + */ + public void showOnlyLikedFeeds() { + showLoader(); + resetTimer(); + newsService.getOnlyLikedFeeds(new AsyncCallback>() { + @Override + public void onSuccess(ArrayList feeds) { + if (feeds != null) { + newsPanel.clear(); + if (feeds.size() == 0) { + newsPanel.add(new HTML("
" + + "Looks like we've got nothing for you at the moment.
" + + "Set an update as your favorite to see it here
")); + isFirstTweet = true; + } + else { + newsPanel.setHeight(""); + newsPanel.setHorizontalAlignment(HasAlignment.ALIGN_LEFT); + newsPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_TOP); + for (EnhancedFeed feed : feeds) + newsPanel.add(new TweetTemplate(myUserInfo, feed, eventBus)); + if (feeds.size() < 5) { + newsPanel.add(new Image(spacer)); + } + isFirstTweet = false; + } + } else + showProblems(); + + } + + @Override + public void onFailure(Throwable caught) { + loadingImage.setUrl(warning); + newsPanel.add(new HTML("Ops! There were problems while retrieving your feeds! Please try again in a short while")); + } + }); + } + + /** + * used when addin directly a feed from the UI (IPC) + * @param userid + * @param fullName + * @param thumbURL + * @param description + */ + public void addJustAddedFeed(ClientFeed cFeed) { + Feed feed = new Feed(cFeed.getKey(), FeedType.SHARE, cFeed.getUserid(), null, "", cFeed.getUri(), cFeed.getLinkUrlThumbnail(), + cFeed.getDescription(), PrivacyLevel.CONNECTION, cFeed.getFullName(), + cFeed.getEmail(), cFeed.getThumbnailURL(), cFeed.getLinkTitle(), cFeed.getLinkDescription(), cFeed.getLinkHost()); + EnhancedFeed toAdd = new EnhancedFeed(feed, false, true); //false cuz he could not have liked this yet and true because is the current user's + + final TweetTemplate tt = new TweetTemplate(myUserInfo, toAdd, eventBus, true); + if (isFirstTweet) { + newsPanel.clear(); + newsPanel.add(new Image(spacer)); + isFirstTweet = false; + } + newsPanel.insert(tt, 0); + Timer t = new Timer() { + + @Override + public void run() { + tt.setcontentAreaStyle("visible"); + + } + }; + + t.schedule(500); + } + + private void showLoader() { + newsPanel.clear(); + newsPanel.setWidth("100%"); + newsPanel.setHeight("300px"); + newsPanel.setHorizontalAlignment(HasAlignment.ALIGN_CENTER); + newsPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE); + newsPanel.add(loadingImage); + } + + private void showProblems() { + newsPanel.clear(); + newsPanel.add(new HTML("
" + + "Ops! There were problems while retrieving your feeds!.
" + + "Looks like we are not able to communicate with the infrastructure,
please try again in a short while.
")); + } + + private void doAddLike(final TweetTemplate owner, final String feedId) { + newsService.like(feedId, owner.getMyFeedText(), owner.getMyFeedUserId(), new AsyncCallback() { + @Override + public void onFailure(Throwable caught) {} + @Override + public void onSuccess(Boolean result) { } + }); + } + + private void doShowLikes(final String feedId) { + GWT.runAsync(new RunAsyncCallback() { + @Override + public void onSuccess() { + final LikesDialog dlg = new LikesDialog(); + dlg.center(); + dlg.show(); + newsService.getAllLikesByFeed(feedId, new AsyncCallback>() { + @Override + public void onFailure(Throwable caught) { + Window.alert("People who liked this could not be retrieved: " + caught.getMessage()); + } + + @Override + public void onSuccess(ArrayList result) { + dlg.showLikes(result); + } + }); + } + public void onFailure(Throwable reason) { + Window.alert("Could not load this component: " + reason.getMessage()); + } + }); + + } + + private void doAddComment(final TweetTemplate owner, String text) { + newsService.comment(owner.getFeedKey(), text, owner.getMyFeedUserId(), new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + Window.alert("Could not deliver this comment: " + caught.getMessage()); + } + @Override + public void onSuccess(Comment result) { + if (result != null) { + owner.addComment(new SingleComment(result, owner, (result.getUserid().equals(myUserInfo.getUsername())) )); + owner.setCommentingDisabled(false); + owner.updateCommentsNumberCount(); + } + else { + Window.alert("Could not deliver this comment. Please try again in a short while."); + } + } + }); + } + + private void doEditComment(final TweetTemplate owner, Comment edited) { + newsService.editComment(edited, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + Window.alert("Could not edit this comment: " + caught.getMessage()); + } + + @Override + public void onSuccess(Comment result) { + if (result != null) { + owner.addComment(new SingleComment(result, owner, (result.getUserid().equals(myUserInfo.getUsername())) )); + owner.setCommentingDisabled(false); + } + else { + Window.alert("Could not deliver this comment. Please try again in a short while."); + } + } + }); + + + } + + private void doShowComments(final TweetTemplate owner, final boolean commentForm2Add) { + owner.showLoadingComments(); + newsService.getAllCommentsByFeed(owner.getFeedKey(), new AsyncCallback>() { + + @Override + public void onFailure(Throwable caught) { + Window.alert("Comments could not be retrieved: " + caught.getMessage()); + } + + @Override + public void onSuccess(ArrayList comments) { + owner.clearComments(); + for (Comment comment :comments) + owner.addComment(new SingleComment(comment, owner,(comment.getUserid().equals(myUserInfo.getUsername())) )); + owner.setCommentsFetched(true); + if (commentForm2Add) + owner.showAddCommentForm(); + owner.updateCommentsNumberCount(); + } + }); + + } + + + protected void doDeleteComment(final TweetTemplate owner, String commentId) { + newsService.deleteComment(commentId, owner.getFeedKey(), new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + Window.alert("Comment could not be deleted: " + caught.getMessage()); + + } + @Override + public void onSuccess(Boolean result) { + if (result) { + doShowComments(owner, false); + owner.updateCommentsNumberCount(); + } else + Window.alert("Comment could not be deleted, please try again in a short while."); + } + }); + + } + + private void doDeleteFeed(final TweetTemplate toDelete) { + if (Window.confirm("Are you sure you want to delete this feed?")) { + newsService.deleteFeed(toDelete.getFeedKey(), new AsyncCallback() { + + @Override + public void onFailure(Throwable caught) { + Window.alert("Feed could not be deleted: " + caught.getMessage()); + } + + @Override + public void onSuccess(Boolean result) { + if (result) { + toDelete.removeFromParent(); + } else + Window.alert("Feed could not be deleted, please try again in a short while."); + } + }); + } + } + + /** + * set the filter type status for automatic reloading of tweets + * @param currentFilter + */ + public void setCurrentFilter(FilterType currentFilter) { + this.currentFilter = currentFilter; + } + +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/panels/dialog/LikesDialog.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/panels/dialog/LikesDialog.java new file mode 100644 index 0000000..236841f --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/panels/dialog/LikesDialog.java @@ -0,0 +1,73 @@ +package org.gcube.portlets.user.newsfeed.client.panels.dialog; + + +import java.util.ArrayList; + +import org.gcube.portal.databook.shared.Like; +import org.gcube.portlets.user.gcubewidgets.client.popup.GCubeDialog; +import org.gcube.portlets.user.newsfeed.client.panels.NewsFeedPanel; +import org.gcube.portlets.user.newsfeed.client.templates.LikedTemplate; + +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.CellPanel; +import com.google.gwt.user.client.ui.HasAlignment; +import com.google.gwt.user.client.ui.HasVerticalAlignment; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.Image; +import com.google.gwt.user.client.ui.ScrollPanel; +import com.google.gwt.user.client.ui.VerticalPanel; + +public class LikesDialog extends GCubeDialog { + + private static final int WIDTH = 420; + private CellPanel mainPanel = new VerticalPanel(); + private VerticalPanel topPanel = new VerticalPanel(); + private HorizontalPanel bottomPanel = new HorizontalPanel(); + private Image loadingImage; + + public LikesDialog() { + super(true); + setText("People who set this " + NewsFeedPanel.LIKE_LABEL); + loadingImage = new Image(NewsFeedPanel.loading); + setSize(""+WIDTH, "100"); + topPanel.setPixelSize(WIDTH, 100); + bottomPanel.setPixelSize(WIDTH, 25); + + topPanel.setHorizontalAlignment(HasAlignment.ALIGN_CENTER); + topPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE); + topPanel.add(loadingImage); + ScrollPanel scroller = new ScrollPanel(); + scroller.setPixelSize(WIDTH+40, 300); + + scroller.add(topPanel); + mainPanel.add(scroller); + mainPanel.add(bottomPanel); + + bottomPanel.setHorizontalAlignment(HasAlignment.ALIGN_RIGHT); + Button close = new Button("Close"); + bottomPanel.add(close); + close.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + hide(); + } + }); + + mainPanel.setCellHeight(bottomPanel, "25px"); + + setWidget(mainPanel); + } + /** + * + * @param likes + */ + public void showLikes(ArrayList likes) { + topPanel.remove(loadingImage); + bottomPanel.setHorizontalAlignment(HasAlignment.ALIGN_RIGHT); + for (Like like : likes) { + topPanel.add(new LikedTemplate(like)); + } + } +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/AddCommentTemplate.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/AddCommentTemplate.java new file mode 100644 index 0000000..50f5090 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/AddCommentTemplate.java @@ -0,0 +1,168 @@ +package org.gcube.portlets.user.newsfeed.client.templates; + +import org.gcube.portal.databook.shared.Comment; +import org.gcube.portal.databook.shared.UserInfo; +import org.gcube.portlets.user.newsfeed.client.event.AddCommentEvent; +import org.gcube.portlets.user.newsfeed.client.event.EditCommentEvent; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.KeyPressEvent; +import com.google.gwt.event.shared.HandlerManager; +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; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.HTMLPanel; +import com.google.gwt.user.client.ui.Image; +import com.google.gwt.user.client.ui.TextArea; +import com.google.gwt.user.client.ui.Widget; + +public class AddCommentTemplate extends Composite{ + + private static CommentTemplateUiBinder uiBinder = GWT + .create(CommentTemplateUiBinder.class); + + private final static String COMMENT_TEXT = "Add a comment ..."; + private final static String ERROR_UPDATE_TEXT = "Looks like empty to me!"; + public static final String avatar_default = GWT.getModuleBaseURL() + "../images/Avatar_default.png"; + private TweetTemplate owner; + private HandlerManager eventBus; + private boolean isEditing = false; + private HTMLPanel commentPanel; + private Comment toEdit; + + interface CommentTemplateUiBinder extends UiBinder { + } + /** + * called on add comment + * @param caller + * @param myUserInfo + * @param eventBus + */ + public AddCommentTemplate(TweetTemplate caller, UserInfo myUserInfo, HandlerManager eventBus) { + initWidget(uiBinder.createAndBindUi(this)); + this.eventBus = eventBus; + owner = caller; + avatarImage.setPixelSize(30, 30); + avatarImage.setUrl(myUserInfo.getAvatarId()); + commentTextArea.setPixelSize(450, 26); + commentTextArea.setText(COMMENT_TEXT); + } + /** + * called on edit comment + * @param caller + * @param editText + */ + public AddCommentTemplate(TweetTemplate caller, Comment toEdit, HTMLPanel commentPanel) { + initWidget(uiBinder.createAndBindUi(this)); + this.eventBus = caller.getEventBus(); + this.commentPanel = commentPanel; + isEditing = true; + this.toEdit = toEdit; + owner = caller; + avatarImage.setPixelSize(30, 30); + avatarImage.setUrl(caller.getMyUserInfo().getAvatarId()); + commentTextArea.setPixelSize(450, 26); + commentTextArea.setText(toEdit.getText()); + mainPanel.removeStyleName("comment-hidden"); + mainPanel.setStyleName("single-comment"); + commentTextArea.addStyleName("dark-color"); + } + + @UiField HTMLPanel mainPanel; + @UiField Image avatarImage; + @UiField TextArea commentTextArea; + @UiField Button submitButton; + @UiField Button cancelButton; + + public void setFocus() { + commentTextArea.setFocus(true); + } + + @UiHandler("submitButton") + void onSubmitClick(ClickEvent e) { + String userComment = commentTextArea.getText().trim(); + if (! checkTextLength(userComment)) { + Window.alert("We found a single word containing more than 50 chars and it's not a link, is it meaningful?"); + return; + } + if (userComment.equals(COMMENT_TEXT) || userComment.equals(ERROR_UPDATE_TEXT) || userComment.equals("")) { + commentTextArea.addStyleName("error"); + commentTextArea.setText(ERROR_UPDATE_TEXT); + return; + } + if (isEditing) { + toEdit.setText(escapeHtml(commentTextArea.getText())); + eventBus.fireEvent(new EditCommentEvent(owner, toEdit)); + } + else + eventBus.fireEvent(new AddCommentEvent(owner, escapeHtml(commentTextArea.getText()))); + this.getWidget().setVisible(false); + owner.setCommentingDisabled(false); + } + + /** + * called when pasting. it tries to avoid pasting long non spaced strings + * @param linkToCheck + */ + private boolean checkTextLength(String textToCheck) { + + String [] parts = textToCheck.split("\\s"); + // check the length of tokens + for( String item : parts ) { + if (! item.startsWith("http")) { //url are accepted as they can be trunked + if (item.length() > 50) { + return false; + } + } + } + return true; + } + + @UiHandler("cancelButton") + void onCancelClick(ClickEvent e) { + this.getWidget().setVisible(false); + owner.setCommentingDisabled(false); + if (isEditing) { + commentPanel.clear(); + SingleComment sc = new SingleComment(toEdit, owner, true); + commentPanel.add(sc); + } + } + + + @UiHandler("commentTextArea") + void onCommentClick(ClickEvent e) { + if (commentTextArea.getText().equals(COMMENT_TEXT) || commentTextArea.getText().equals(ERROR_UPDATE_TEXT) ) { + commentTextArea.setText(""); + commentTextArea.addStyleName("dark-color"); + commentTextArea.removeStyleName("error"); + } + } + + @UiHandler("commentTextArea") + void onCommentKeyPress(KeyPressEvent e) { + if (commentTextArea.getText().equals(COMMENT_TEXT) || commentTextArea.getText().equals(ERROR_UPDATE_TEXT) ) { + commentTextArea.setText(""); + commentTextArea.addStyleName("dark-color"); + commentTextArea.removeStyleName("error"); + } + } + /** + * Escape an html string. Escaping data received from the client helps to + * prevent cross-site script vulnerabilities. + * + * @param html the html string to escape + * @return the escaped string + */ + private String escapeHtml(String html) { + if (html == null) { + return null; + } + return html.replaceAll("&", "&").replaceAll("<", "<") + .replaceAll(">", ">"); + } +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/AddCommentTemplate.ui.xml b/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/AddCommentTemplate.ui.xml new file mode 100644 index 0000000..b450930 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/AddCommentTemplate.ui.xml @@ -0,0 +1,32 @@ + + + + .important { + font-weight: bold; + } + + + + + + + + + + + +
+ + + +
+ +
+
+ + +
+
+
\ No newline at end of file diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/FilterPanel.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/FilterPanel.java new file mode 100644 index 0000000..cc30d0a --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/FilterPanel.java @@ -0,0 +1,152 @@ +package org.gcube.portlets.user.newsfeed.client.templates; + +import org.gcube.portal.databook.shared.UserInfo; +import org.gcube.portlets.user.newsfeed.client.FilterType; +import org.gcube.portlets.user.newsfeed.client.NewsServiceAsync; +import org.gcube.portlets.user.newsfeed.client.panels.NewsFeedPanel; + +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; +import com.google.gwt.user.client.rpc.AsyncCallback; +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 FilterPanel extends Composite { + + private static FilterPanelUiBinder uiBinder = GWT + .create(FilterPanelUiBinder.class); + + interface FilterPanelUiBinder extends UiBinder { + } + + protected static final String ERROR_MESSAGE = "Ops! we encountered some problems, server is not responding, please try again in a short while."; + protected static final String SESSION_EXPIRED = "Your session has expired, please log out and login again"; + + NewsFeedPanel caller; + NewsServiceAsync service; + + public FilterPanel(NewsFeedPanel caller, NewsServiceAsync newsService) { + initWidget(uiBinder.createAndBindUi(this)); + this.caller = caller; + this.service = newsService; + //connectionsLink.setHTML("Connections"); + allUpdatesLink.setHTML("All Updates"); + favoritesLink.setHTML("My Favorites"); + onlyme.setHTML("Only Me"); + //connectionsLink.getElement().getStyle().setCursor(Cursor.POINTER); + allUpdatesLink.getElement().getStyle().setCursor(Cursor.POINTER); + favoritesLink.getElement().getStyle().setCursor(Cursor.POINTER); + onlyme.getElement().getStyle().setCursor(Cursor.POINTER); + + allUpdatesLink.setStyleName("filter-selected"); + } + +// @UiField +// HTML connectionsLink; + @UiField + HTML allUpdatesLink; + @UiField + HTML favoritesLink; + @UiField + HTML onlyme; + + +// @UiHandler("connectionsLink") +// void onConnectionsClick(ClickEvent e) { +// allUpdatesLink.removeStyleName("filter-selected"); +// onlyme.removeStyleName("filter-selected"); +// connectionsLink.setStyleName("filter-selected"); +// caller.setCurrentFilter(FilterType.CONNECTIONS); +// service.getUserInfo(new AsyncCallback() { +// @Override +// public void onFailure(Throwable caught) { +// Window.alert("Ops! we encountered some problems delivering your message, server is not responding, please try again in a short while."); +// } +// +// @Override +// public void onSuccess(UserInfo result) { +// if (result.getUsername().equals("test.user")) { +// Window.alert("Your session has expired, please log out and login again"); +// } +// else +// caller.showOnlyConnectionsFeeds(); +// } +// }); +// } +// + + @UiHandler("favoritesLink") + void onFavoritesClick(ClickEvent e) { + allUpdatesLink.removeStyleName("filter-selected"); + onlyme.removeStyleName("filter-selected"); + favoritesLink.setStyleName("filter-selected"); + caller.setCurrentFilter(FilterType.LIKEDFEEDS); + service.getUserInfo(new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + Window.alert(ERROR_MESSAGE); + } + + @Override + public void onSuccess(UserInfo result) { + if (result.getUsername().equals("test.user")) { + Window.alert(SESSION_EXPIRED); + } + else + caller.showOnlyLikedFeeds(); + } + }); + } + + @UiHandler("allUpdatesLink") + void onAllUpdatesClick(ClickEvent e) { + onlyme.removeStyleName("filter-selected"); + favoritesLink.removeStyleName("filter-selected"); + allUpdatesLink.setStyleName("filter-selected"); + caller.setCurrentFilter(FilterType.ALL_UPDATES); + service.getUserInfo(new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + Window.alert(ERROR_MESSAGE); + } + + @Override + public void onSuccess(UserInfo result) { + if (result.getUsername().equals("test.user")) { + Window.alert(SESSION_EXPIRED); + } + else + caller.showAllUpdatesFeeds(); + } + }); + } + + @UiHandler("onlyme") + void onlyme(ClickEvent e) { + allUpdatesLink.removeStyleName("filter-selected"); + favoritesLink.removeStyleName("filter-selected"); + onlyme.setStyleName("filter-selected"); + caller.setCurrentFilter(FilterType.MINE); + service.getUserInfo(new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + Window.alert(ERROR_MESSAGE); + } + + @Override + public void onSuccess(UserInfo result) { + if (result.getUsername().equals("test.user")) { + Window.alert(SESSION_EXPIRED); + } + else + caller.showOnlyMyFeeds(); + } + }); + } +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/FilterPanel.ui.xml b/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/FilterPanel.ui.xml new file mode 100644 index 0000000..2a781a7 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/FilterPanel.ui.xml @@ -0,0 +1,27 @@ + + + + .important { + font-weight: bold; + } + + +
+
    +
  • + +
  • + + + +
  • + +
  • +
  • + +
  • +
+
+
+
\ No newline at end of file diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/LikedTemplate.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/LikedTemplate.java new file mode 100644 index 0000000..7696703 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/LikedTemplate.java @@ -0,0 +1,35 @@ +package org.gcube.portlets.user.newsfeed.client.templates; + +import org.gcube.portal.databook.client.GCubeSocialNetworking; +import org.gcube.portal.databook.shared.Like; +import org.gcube.portlets.user.newsfeed.client.panels.NewsFeedPanel; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.Image; +import com.google.gwt.user.client.ui.Widget; + +public class LikedTemplate extends Composite { + + private static LikedTemplateUiBinder uiBinder = GWT + .create(LikedTemplateUiBinder.class); + + interface LikedTemplateUiBinder extends UiBinder { + } + + public LikedTemplate(Like like) { + initWidget(uiBinder.createAndBindUi(this)); + if (like.getThumbnailURL() != null) + avatarImage.setUrl(like.getThumbnailURL()); + + avatarImage.setPixelSize(30, 30); + contentArea.setHTML(""+like.getFullName()+" "); + } + @UiField + Image avatarImage; + @UiField + HTML contentArea; +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/LikedTemplate.ui.xml b/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/LikedTemplate.ui.xml new file mode 100644 index 0000000..5eb8a0f --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/LikedTemplate.ui.xml @@ -0,0 +1,25 @@ + + + +
+
+
+ + + +
+ +
+
+ +
+
+ +
+
+
+
+
+
\ No newline at end of file diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/LinkPreviewer.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/LinkPreviewer.java new file mode 100644 index 0000000..d7f127d --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/LinkPreviewer.java @@ -0,0 +1,38 @@ +package org.gcube.portlets.user.newsfeed.client.templates; + + + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.Image; +import com.google.gwt.user.client.ui.Widget; + +public class LinkPreviewer extends Composite { + + private static LinkPreviewUiBinder uiBinder = GWT + .create(LinkPreviewUiBinder.class); + + interface LinkPreviewUiBinder extends UiBinder { + } + + public LinkPreviewer(String title, String titleDesc, String host, String linkThumbUrl, String url) { + initWidget(uiBinder.createAndBindUi(this)); + titleArea.setHTML(""+title+" - " + host+ ""); + urlText.setHTML((url.length() > 70) ? url.substring(0, 70)+"..." : url); + String desc = titleDesc; + descText.setHTML((desc.length() > 256) ? desc.substring(0, 256)+"..." : desc); + image.setUrl(linkThumbUrl); + image.setWidth("80px"); + } + @UiField + HTML titleArea; + @UiField + HTML urlText; + @UiField + HTML descText; + @UiField + Image image; +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/LinkPreviewer.ui.xml b/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/LinkPreviewer.ui.xml new file mode 100644 index 0000000..17df84c --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/LinkPreviewer.ui.xml @@ -0,0 +1,26 @@ + + + + + + + + +
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+
\ No newline at end of file diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/SingleComment.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/SingleComment.java new file mode 100644 index 0000000..f976884 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/SingleComment.java @@ -0,0 +1,108 @@ +package org.gcube.portlets.user.newsfeed.client.templates; + +import org.gcube.portal.databook.client.GCubeSocialNetworking; +import org.gcube.portal.databook.shared.Comment; +import org.gcube.portlets.user.newsfeed.client.event.DeleteCommentEvent; +import org.gcube.portlets.user.newsfeed.client.panels.NewsFeedPanel; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.MouseOutEvent; +import com.google.gwt.event.dom.client.MouseOverEvent; +import com.google.gwt.i18n.client.DateTimeFormat; +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.ui.Composite; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.HTMLPanel; +import com.google.gwt.user.client.ui.Image; +import com.google.gwt.user.client.ui.Widget; + +public class SingleComment extends Composite { + + private static SingleCommentUiBinder uiBinder = GWT + .create(SingleCommentUiBinder.class); + + interface SingleCommentUiBinder extends UiBinder { + } + private static final int MAX_SHOWTEXT_LENGTH = 256; + + private boolean isUsers = false; + private TweetTemplate owner; + private String myCommentid; + private Comment myComment; + + @UiField HTMLPanel mainPanel; + @UiField Image avatarImage; + @UiField HTML commentText; + @UiField HTML timeArea; + @UiField HTML closeImage; + @UiField HTML editImage; + @UiField HTML seeMore; + + public SingleComment(Comment toShow, TweetTemplate owner, boolean isUsers) { + initWidget(uiBinder.createAndBindUi(this)); + this.owner = owner; + this.isUsers = isUsers; + this.myComment = toShow; + myCommentid = toShow.getKey(); + avatarImage.setPixelSize(30, 30); + avatarImage.setUrl(toShow.getThumbnailURL()); + + String commentToShow = toShow.getText(); + if (commentToShow.length() > MAX_SHOWTEXT_LENGTH) { + commentToShow = commentToShow.substring(0, MAX_SHOWTEXT_LENGTH) + "..."; + seeMore.setHTML(" See More "); + } + + commentText.setHTML(""+toShow.getFullName()+" " + commentToShow); + timeArea.setHTML(DateTimeFormat.getFormat("MMMM dd, h:mm a").format(toShow.getTime())); + if (isUsers) { + closeImage.setStyleName("closeImage"); + closeImage.setTitle("Delete"); + editImage.setStyleName("editImage"); + editImage.setTitle("Edit"); + } + } + + @UiHandler("seeMore") + void onSeeMoreClick(ClickEvent e) { + commentText.setHTML(""+myComment.getFullName()+" " + myComment.getText()); + seeMore.setHTML(""); + } + + + + @UiHandler("closeImage") + void onDeleteCommentClick(ClickEvent e) { + if (isUsers) + owner.getEventBus().fireEvent(new DeleteCommentEvent(owner, myCommentid)); + } + + @UiHandler("editImage") + void onEditCommentClick(ClickEvent e) { + if (isUsers) { + AddCommentTemplate addComm = new AddCommentTemplate(owner, myComment, mainPanel); + mainPanel.getElement().setInnerHTML(""); + mainPanel.add(addComm); + } + } + + + @UiHandler("commentText") + public void onHover(MouseOverEvent event) { + if (isUsers) { + closeImage.addStyleName("uiCloseButton"); + editImage.addStyleName("uiEditButton"); + } + } + + @UiHandler("commentText") + public void onHover(MouseOutEvent event) { + if (isUsers) { + closeImage.removeStyleName("uiCloseButton"); + editImage.removeStyleName("uiEditButton"); + } + } +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/SingleComment.ui.xml b/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/SingleComment.ui.xml new file mode 100644 index 0000000..e416cd1 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/SingleComment.ui.xml @@ -0,0 +1,61 @@ + + + + .important { + font-weight: bold; + } + + + + + + + + + +
+ + + +
+ + + +
+
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/TweetTemplate.java b/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/TweetTemplate.java new file mode 100644 index 0000000..5d73b01 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/TweetTemplate.java @@ -0,0 +1,399 @@ +/** + * + */ +package org.gcube.portlets.user.newsfeed.client.templates; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; + +import org.gcube.portal.databook.client.GCubeSocialNetworking; +import org.gcube.portal.databook.shared.Comment; +import org.gcube.portal.databook.shared.Feed; +import org.gcube.portal.databook.shared.UserInfo; +import org.gcube.portlets.user.newsfeed.client.event.AddLikeEvent; +import org.gcube.portlets.user.newsfeed.client.event.DeleteCommentEvent; +import org.gcube.portlets.user.newsfeed.client.event.DeleteFeedEvent; +import org.gcube.portlets.user.newsfeed.client.event.SeeCommentsEvent; +import org.gcube.portlets.user.newsfeed.client.event.SeeLikesEvent; +import org.gcube.portlets.user.newsfeed.client.event.StopTimerEvent; +import org.gcube.portlets.user.newsfeed.client.panels.NewsFeedPanel; +import org.gcube.portlets.user.newsfeed.shared.EnhancedFeed; +import org.gcube.portlets.user.wsmail.client.forms.MailForm; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.RunAsyncCallback; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.MouseOutEvent; +import com.google.gwt.event.dom.client.MouseOverEvent; +import com.google.gwt.event.shared.HandlerManager; +import com.google.gwt.i18n.client.DateTimeFormat; +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.Timer; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.HTMLPanel; +import com.google.gwt.user.client.ui.Image; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.user.client.ui.Widget; + +/** + * @author Massimiliano Assante. ISTI-CNR + * + */ +public class TweetTemplate extends Composite { + + private static TweetTemplateUiBinder uiBinder = GWT + .create(TweetTemplateUiBinder.class); + + interface TweetTemplateUiBinder extends UiBinder { + } + + + public static final String loading = GWT.getModuleBaseURL() + "../images/loading-comments.gif"; + + private static final int MAX_SHOWTEXT_LENGTH = 256; + + private EnhancedFeed myFeed; + private UserInfo myUserInfo; + + private HandlerManager eventBus; + private ArrayList myComments; + private boolean commentingDisabled = false; + private boolean commentsFetched = false; + private int totalComments = 0; + private HTML showAllComments = new HTML(); + private boolean isAppFeed = false; + /** + * tell if this tweet is belonging to the current user + */ + private boolean isUsers = false; + + @UiField + HTML contentArea; + @UiField + HTML seeMore; + @UiField + HTML timeArea; + @UiField + HTML likeArea; + @UiField + HTML commentArea; + @UiField + HTML messageArea; + @UiField + Image avatarImage; + @UiField + HTMLPanel mainHTML; + @UiField + HTML likesNo; + @UiField + HTML commentsNo; + @UiField + VerticalPanel commentsPanel; + @UiField + HTML closeImage; + @UiField + VerticalPanel previewPanel; + @UiField + Label messageSeparator; + + /** + * used when fetching tweets from server + * @param myUserInfo + * @param myFeed + * @param isUsers + * @param eventBus + */ + public TweetTemplate(UserInfo myUserInfo, EnhancedFeed myFeed, HandlerManager eventBus) { + initWidget(uiBinder.createAndBindUi(this)); + this.myUserInfo = myUserInfo; + this.myFeed = myFeed; + isAppFeed = myFeed.getFeed().isApplicationFeed(); + Feed feed = myFeed.getFeed(); + if (feed.getUri() != null && feed.getUri().compareTo("") != 0 && feed.getLinkTitle() != null && feed.getLinkTitle().compareTo("") != 0 ) { + previewPanel.add(new LinkPreviewer(feed.getLinkTitle(), feed.getLinkDescription(), feed.getLinkHost(), feed.getUriThumbnail(), feed.getUri())); + } + this.eventBus = eventBus; + this.isUsers = myFeed.isUsers(); + myComments = new ArrayList(); + if (isUsers) { + closeImage.setStyleName("closeImage"); + closeImage.setTitle("Delete"); + } + //show if the user has already liked this + if (myFeed.isLiked()) { + likeArea.setHTML(NewsFeedPanel.LIKED_LABEL); + + } + else + likeArea.setHTML("" + NewsFeedPanel.LIKE_LABEL + ""); + + commentArea.setHTML("" + NewsFeedPanel.COMMENT_LABEL + ""); + + String feedText = feed.getDescription(); + if ( (! feedText.startsWith(" MAX_SHOWTEXT_LENGTH) { + feedText = feedText.substring(0, MAX_SHOWTEXT_LENGTH) + "..."; + seeMore.setHTML(" See More "); + } + + if (! isAppFeed) { + messageArea.setHTML("" + NewsFeedPanel.MESSAGE_LABEL + ""); + contentArea.setHTML(""+feed.getFullName()+" " + feedText); + } + else { + // messageSeparator.setVisible(false); + contentArea.setHTML(""+feed.getFullName()+" " + feedText); + closeImage.setTitle("Delete this Application feed (Administrator Only)"); + if (isAppFeed) { + try{ + String vreName = feed.getVreid().substring(feed.getVreid().lastIndexOf("/")+1); + messageArea.setHTML(" go App [" +vreName + "]"); + } + catch (Exception e) {} + } + } + + avatarImage.setUrl(feed.getThumbnailURL()); + avatarImage.setPixelSize(50, 50); + try { + String formattedTime = DateTimeFormat.getFormat("MMMM dd, h:mm a").format(feed.getTime()); + timeArea.setHTML(formattedTime); + + if (! feed.getCommentsNo().equals("0")) { + commentsNo.setHTML(feed.getCommentsNo()); + commentsNo.setStyleName("comments-number"); + commentsNo.setTitle("People commented this."); + } + if (! feed.getLikesNo().equals("0")) { + likesNo.setHTML(feed.getLikesNo()); + likesNo.setStyleName("likes-number"); + likesNo.setTitle("Show People who have " + NewsFeedPanel.LIKED_LABEL + " this."); + } + totalComments = Integer.parseInt(feed.getCommentsNo()); + } + catch (NumberFormatException e) { + totalComments = 0; + } + catch (Exception e) { + timeArea.setHTML("just now"); + } + commentsPanel.setStyleName("commentsPanel"); + if (myFeed.getComments() != null && myFeed.getComments().size() > 0) { + if (totalComments > 2) { + showAllComments = getShowAllCommentsLink(totalComments); + commentsPanel.add(showAllComments); + commentsNo.setStyleName("show-comments-number"); + commentsNo.setTitle("Show all Comments"); + } + for (Comment comment : myFeed.getComments()) { + addComment(new SingleComment(comment, this, (comment.getUserid().equals(myUserInfo.getUsername())))); + } + } + } + + /** + * used when getting tweets from the client + * @param myUserInfo + * @param feed + * @param eventBus + * @param hidden + */ + public TweetTemplate(UserInfo myUserInfo, EnhancedFeed feed, HandlerManager eventBus, boolean hidden) { + this(myUserInfo, feed, eventBus); + contentArea.getElement().getParentElement().getParentElement().setClassName("div-table-col content hidden"); + } + + @UiHandler("contentArea") + public void onHover(MouseOutEvent event) { + if (isUsers) + closeImage.removeStyleName("uiCloseButton"); + } + + @UiHandler("contentArea") + public void onHover(MouseOverEvent event) { + if (isUsers) + closeImage.addStyleName("uiCloseButton"); + } + + @UiHandler("closeImage") + void onDeleteFeedClick(ClickEvent e) { + if (isUsers) + eventBus.fireEvent(new DeleteFeedEvent(this)); + } + + + @UiHandler("seeMore") + void onSeeMoreClick(ClickEvent e) { + contentArea.setHTML(""+myFeed.getFeed().getFullName()+" " + myFeed.getFeed().getDescription()); + seeMore.setHTML(""); + } + + + + @UiHandler("likeArea") + void onLikeClick(ClickEvent e) { + if (!likeArea.getText().equals(NewsFeedPanel.LIKED_LABEL)) { + try { + eventBus.fireEvent(new StopTimerEvent(true)); //reset the refresh page timer + + int cur = Integer.parseInt(myFeed.getFeed().getLikesNo()); + cur++; + if (cur == 1) { + myFeed.getFeed().setLikesNo("1"); + likesNo.setHTML("1"); + likesNo.setStyleName("likes-number"); + likesNo.setTitle("People who have " + NewsFeedPanel.LIKED_LABEL + " this"); + } else { + myFeed.getFeed().setLikesNo(""+cur); + likesNo.setHTML(""+cur); + } + eventBus.fireEvent(new AddLikeEvent(this, myFeed.getFeed().getKey())); + likeArea.setHTML(NewsFeedPanel.LIKED_LABEL); + } + catch (NumberFormatException ex) { + likeArea.setHTML("Error on the server"); + } + } + } + + @UiHandler("commentArea") + void onAddCommentClick(ClickEvent e) { + if (! commentingDisabled) { + if (! commentsFetched && totalComments > 2) { //if so, need to load all comments before adding a comment + fireSeeComments(true); + } + showAddCommentForm(); + eventBus.fireEvent(new StopTimerEvent(false)); //stop the refresh page timer + } + else + GWT.log("Commenting disabled"); + } + + @UiHandler("messageArea") + void onMessageClick(ClickEvent e) { + if (! isAppFeed) { + eventBus.fireEvent(new StopTimerEvent(false)); //stop the refresh page timer + final List listToLogin = new ArrayList(); + listToLogin.add(myFeed.getFeed().getEntityId()); + GWT.runAsync(new RunAsyncCallback() { + @Override + public void onSuccess() { + new MailForm(listToLogin); + } + public void onFailure(Throwable reason) { + Window.alert("Could not load this component: " + reason.getMessage()); + } + }); + + } + } + + public void showAddCommentForm() { + final AddCommentTemplate toAdd = new AddCommentTemplate(this, myUserInfo, eventBus); + commentsPanel.add(toAdd); + commentingDisabled = true; + final Timer t = new Timer() { + + @Override + public void run() { + toAdd.setStyleName("comment-show"); + toAdd.setFocus(); + + } + }; + t.schedule(10); + } + + private HTML getShowAllCommentsLink(int commentsNo) { + final HTML toReturn = new HTML(""); + toReturn.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + fireSeeComments(false); + } + }); + return toReturn; + } + + private void fireSeeComments(boolean commentForm2Add) { + eventBus.fireEvent(new SeeCommentsEvent(this, commentForm2Add)); + } + + @UiHandler("likesNo") + void onSeeLikes(ClickEvent e) { + eventBus.fireEvent(new SeeLikesEvent(myFeed.getFeed().getKey())); + } + @UiHandler("commentsNo") + void onSeeComments(ClickEvent e) { + fireSeeComments(false); + } + + public void setcontentAreaStyle(String cssclass) { + contentArea.getElement().getParentElement().getParentElement().setClassName("div-table-col content visible"); + } + + public boolean isCommenting() { + return commentingDisabled; + } + + public void setCommentingDisabled(boolean commenting) { + this.commentingDisabled = commenting; + } + public String getFeedKey() { + return myFeed.getFeed().getKey(); + } + + public void remove(Widget w) { + mainHTML.remove(w); + } + + public void addComment(SingleComment comment) { + myComments.add(comment); + commentsPanel.add(comment); + } + + public void clearComments() { + myComments.clear(); + commentsPanel.clear(); + } + + public void showLoadingComments() { + showAllComments.setHTML("
"); + } + + public boolean isCommentsFetched() { + return commentsFetched; + } + + public void setCommentsFetched(boolean commentsFetched) { + this.commentsFetched = commentsFetched; + } + public HandlerManager getEventBus() { + return eventBus; + } + public void updateCommentsNumberCount() { + if (myComments.size() == 1) { + commentsNo.setStyleName("comments-number"); + commentsNo.setTitle("Persons who have commented this."); + } + commentsNo.setHTML(""+myComments.size()); + } + public UserInfo getMyUserInfo() { + return myUserInfo; + } + + public String getMyFeedUserId() { + return myFeed.getFeed().getEntityId(); + } + + public String getMyFeedText() { + return myFeed.getFeed().getDescription(); + } +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/TweetTemplate.ui.xml b/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/TweetTemplate.ui.xml new file mode 100644 index 0000000..0ce9851 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/client/templates/TweetTemplate.ui.xml @@ -0,0 +1,38 @@ + + + +
+
+
+ +
+ +
+
+ + +
+ +
+ + + - + + - + + + + + +
+ +
+
+ +
+
+
+
+
\ 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 new file mode 100644 index 0000000..93e72b6 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/server/NewsServiceImpl.java @@ -0,0 +1,624 @@ +package org.gcube.portlets.user.newsfeed.server; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.UUID; + +import org.gcube.application.framework.core.session.ASLSession; +import org.gcube.application.framework.core.session.SessionManager; +import org.gcube.applicationsupportlayer.social.ApplicationNotificationsManager; +import org.gcube.applicationsupportlayer.social.NotificationsManager; +import org.gcube.common.core.utils.logging.GCUBEClientLog; +import org.gcube.portal.custom.communitymanager.OrganizationsUtil; +import org.gcube.portal.custom.scopemanager.scopehelper.ScopeHelper; +import org.gcube.portal.databook.server.DBCassandraAstyanaxImpl; +import org.gcube.portal.databook.server.DatabookStore; +import org.gcube.portal.databook.shared.Comment; +import org.gcube.portal.databook.shared.Feed; +import org.gcube.portal.databook.shared.Like; +import org.gcube.portal.databook.shared.UserInfo; +import org.gcube.portal.databook.shared.ex.ColumnNameNotFoundException; +import org.gcube.portal.databook.shared.ex.FeedIDNotFoundException; +import org.gcube.portal.databook.shared.ex.FeedTypeNotFoundException; +import org.gcube.portal.databook.shared.ex.PrivacyLevelTypeNotFoundException; +import org.gcube.portlets.user.newsfeed.client.NewsService; +import org.gcube.portlets.user.newsfeed.shared.EnhancedFeed; +import org.gcube.vomanagement.usermanagement.GroupManager; +import org.gcube.vomanagement.usermanagement.impl.liferay.LiferayGroupManager; + +import com.google.gwt.user.server.rpc.RemoteServiceServlet; +import com.liferay.portal.kernel.exception.PortalException; +import com.liferay.portal.kernel.exception.SystemException; +import com.liferay.portal.kernel.util.WebKeys; +import com.liferay.portal.model.Organization; +import com.liferay.portal.model.Role; +import com.liferay.portal.model.User; +import com.liferay.portal.model.UserModel; +import com.liferay.portal.service.OrganizationLocalServiceUtil; +import com.liferay.portal.service.UserLocalServiceUtil; +import com.liferay.portal.theme.ThemeDisplay; + +/** + * The server side implementation of the RPC service. + */ +@SuppressWarnings("serial") +public class NewsServiceImpl extends RemoteServiceServlet implements NewsService { + + private static GCUBEClientLog _log = new GCUBEClientLog(NewsServiceImpl.class); + /** + * + */ + private static final String ADMIN_ROLE = "Administrator"; + /** + * + */ + private static final String SESSION_ADMIN_ATTR = "SESSION_ADMIN_ATTR"; + + private DatabookStore store; + + private boolean withinPortal = false; + + private final static int MAX_FEEDS_NO = 20; + + + //this is the cache for the user portraitsId that change among portals, the cache avoids to continuosly ask the LR DB the portraitID; + private HashMap portraitIdCache; + + public void init() { + store = new DBCassandraAstyanaxImpl(); + portraitIdCache = new HashMap(); + } + + public void destroy() { + store.closeConnection(); + } + + /** + * the current ASLSession + * @return the session + */ + private ASLSession getASLSession() { + String sessionID = this.getThreadLocalRequest().getSession().getId(); + String user = (String) this.getThreadLocalRequest().getSession().getAttribute(ScopeHelper.USERNAME_ATTRIBUTE); + if (user == null) { + _log.warn("USER IS NULL setting testing user and Running OUTSIDE PORTAL"); + //user = "test.user"; + user = "massimiliano.assante"; + } + else { + withinPortal = true; + } + return SessionManager.getInstance().getASLSession(sessionID, user); + } + + @Override + public ArrayList getAllUpdateUserFeeds() { + String userName = getASLSession().getUsername(); + ArrayList toMerge = new ArrayList(); + HashMap feedsMap = new HashMap(); + try { + if (!withinPortal) { + return getEclipseResult(userName, false); + } + else { + _log.info("****** retrieving feeds for user: " + userName); + 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.trace("Reading feeds for VRE: " + vreid); + ArrayList OrganizationFeeds = (ArrayList) store.getRecentFeedsByVRE(vreid, 10); + for (Feed feed : OrganizationFeeds) { + feedsMap.put(feed.getKey(), feed); + } + } + } + //User Own Feeds + ArrayList userFeeds = (ArrayList) store.getRecentFeedsByUser(userName, 10); + for (Feed feed : userFeeds) { + feedsMap.put(feed.getKey(), feed); + } + + //UserFriends Feeds + ArrayList userFriendsIds = (ArrayList)store.getFriends(userName); + for (String userid : userFriendsIds) { + for (Feed feed : store.getRecentFeedsByUser(userid, 10)) { + feedsMap.put(feed.getKey(), feed); + } + } + + //Portal Feeds + ArrayList portalFeeds = (ArrayList) store.getAllPortalPrivacyLevelFeeds(); + for (Feed feed : portalFeeds) { + feedsMap.put(feed.getKey(), feed); + } + + for (String key: feedsMap.keySet()) { + toMerge.add(feedsMap.get(key)); + } + } + 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); + } + return enhanceFeeds(toReturn); + } 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 + */ + @Override + public ArrayList getOnlyConnectionsUserFeeds() { + ArrayList toMerge = new ArrayList(); + HashMap feedsMap = new HashMap(); + String userName = getASLSession().getUsername(); + try { + if (! withinPortal) { + return getEclipseResult(userName, true); + } + else { + //UserFriends Feeds + ArrayList userFriendsIds = (ArrayList)store.getFriends(userName); + for (String userid : userFriendsIds) { + for (Feed feed : store.getRecentFeedsByUser(userid, 10)) { + feedsMap.put(feed.getKey(), feed); + } + } + for (String key: feedsMap.keySet()) { + toMerge.add(feedsMap.get(key)); + } + 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);; + } + } 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; + } + + + + + /** + * just for testing purposes + * + * @param userName + * @return + * @throws PrivacyLevelTypeNotFoundException + * @throws FeedTypeNotFoundException + * @throws ColumnNameNotFoundException + * @throws FeedIDNotFoundException + */ + private ArrayList getEclipseResult(String userName, boolean onlyConnections) throws PrivacyLevelTypeNotFoundException, FeedTypeNotFoundException, ColumnNameNotFoundException, FeedIDNotFoundException { + ArrayList toMerge = new ArrayList(); + HashMap feedsMap = new HashMap(); + + ArrayList OrganizationFeeds = (ArrayList) store.getRecentFeedsByVRE("/gcube/devsec/devVRE", 10); + for (Feed feed : OrganizationFeeds) { + feedsMap.put(feed.getKey(), feed); + } + + if (! onlyConnections) { + //User Own Feeds + ArrayList userFeeds = (ArrayList) store.getRecentFeedsByUser(userName, 10); + for (Feed feed : userFeeds) + feedsMap.put(feed.getKey(), feed); + // //Portal Feeds + ArrayList portalFeeds = (ArrayList) store.getAllPortalPrivacyLevelFeeds(); + for (Feed feed : portalFeeds) + feedsMap.put(feed.getKey(), feed); + } + //UserFriends Feeds + ArrayList userFriendsIds = (ArrayList)store.getFriends(userName); + for (String userid : userFriendsIds) { + for (Feed feed : store.getRecentFeedsByUser(userid, 10)) { + feedsMap.put(feed.getKey(), feed); + } + } + for (String key: feedsMap.keySet()) { + toMerge.add(feedsMap.get(key)); + } + for (Feed feed : toMerge) { + feed.setThumbnailURL("http://127.0.0.1:8888/images/Avatar_default.png"); + } + + return enhanceFeeds(toMerge); + } + + @Override + public ArrayList getOnlyMyUserFeeds() { + String userName = getASLSession().getUsername(); + _log.trace("getOnly UserFeeds for " + userName); + ArrayList userFeeds = null; + try { + userFeeds = (ArrayList) store.getRecentFeedsByUser(userName, 15); + } catch (Exception e) { + e.printStackTrace(); + } + return enhanceFeeds(userFeeds); + } + + @Override + public ArrayList getOnlyLikedFeeds() { + String userName = getASLSession().getUsername(); + _log.trace("getLiked Feeds for " + userName); + ArrayList userFeeds = null; + try { + userFeeds = (ArrayList) store.getAllLikedFeedsByUser(userName, 25); + } catch (Exception e) { + e.printStackTrace(); + } + return enhanceFeeds(userFeeds); + } + + @Override + public boolean like(String feedid, String feedText, String feedOwnerId) { + boolean likeCommitResult = false; + UserInfo user = getUserInfo(); + Like toLike = new Like(UUID.randomUUID().toString(), user.getUsername(), + new Date(), feedid, user.getFullName(), user.getAvatarId()); + try { + likeCommitResult = store.like(toLike); + } catch (FeedIDNotFoundException e) { + _log.error("Feed not Found for this like " + e.getMessage()); + e.printStackTrace(); + return false; + } + //if the like was correctly delivered notify users involved + if (likeCommitResult) { + //if the user who liked this post is not the user who posted it notify the poster user (Feed owner) + if (! user.getUsername().equals(feedOwnerId)) { + NotificationsManager nm = new ApplicationNotificationsManager(getASLSession()); + boolean nResult = nm.notifyLikedFeed(feedOwnerId, feedid, escapeHtml(feedText)); + _log.trace("Like Notification added? " + nResult); + } + } + return likeCommitResult; + } + /** + * @param feedid the id of the commented feed + * @param commentText the comment text + * @param feedOwnerId the username of the user who created the post that was commented + */ + @Override + public Comment comment(String feedid, String commentText, String feedOwnerId) { + boolean commentCommitResult = false; + _log.trace("Trying to add this comment " + commentText); + UserInfo user = getUserInfo(); + Comment comment = new Comment(UUID.randomUUID().toString(), user.getUsername(), + new Date(), feedid, escapeHtml(commentText), user.getFullName(), user.getAvatarId()); + try { + if (store.addComment(comment)) + commentCommitResult = true; + } catch (FeedIDNotFoundException e) { + _log.error("Feed not Found for this comment " + e.getMessage()); + e.printStackTrace(); + return null; + } + //if the comment was correctly delivered notify users involved + if (commentCommitResult) { + //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)) { + boolean result = nm.notifyOwnCommentReply(feedOwnerId, feedid, escapeHtml(commentText)); + _log.trace("Comment Notification to post owner added? " + result); + } + //if there are other users who liked this post they get notified too, asynchronously with this thread + Thread thread = new Thread(new NotificationsThread(commentText, nm, getAllLikesByFeed(feedid))); + thread.start(); + } + return comment; + } + + private String replaceAmpersand(String toReplace) { + String toReturn = toReplace.replaceAll("&", "&"); + return toReturn; + } + /** + * this method sorts the Feeds in Chronological Reversed order and adds additional user informations + * @param toEnhance + * @return + */ + private ArrayList enhanceFeeds(ArrayList toEnhance) { + ArrayList toReturn = new ArrayList(); + String username = getASLSession().getUsername(); + //sort the Feeds + Collections.sort(toEnhance, Collections.reverseOrder()); + ArrayList likedFeeds = (ArrayList) store.getAllLikedFeedIdsByUser(getASLSession().getUsername()); +// System.out.println("Liked Feed for " + username); +// for (String liked : likedFeeds) { +// System.out.println(liked); +// } + + for (Feed feed : toEnhance) { + feed.setDescription(replaceAmpersand(feed.getDescription())); + if (! feed.isApplicationFeed()) + feed.setThumbnailURL(getUserImagePortraitUrlLocal(feed.getEntityId())); + //if likedFeeds contains this feed key it means the user already Liked it + boolean liked = likedFeeds.contains(feed.getKey()); + int commentsNo = 0; + try { + commentsNo = Integer.parseInt(feed.getCommentsNo()); + } + catch (NumberFormatException e) { + commentsNo = 0; + _log.error("NumberFormatException while reading comments number " + e.getMessage()); + } + if (commentsNo == 0) { + EnhancedFeed toAdd = null; + //create the enhanced feed + if (feed.isApplicationFeed()) { + toAdd = new EnhancedFeed(feed, liked, checkisAdminUser()); + } else + toAdd = new EnhancedFeed(feed, liked, isUsers(feed, username)); + toReturn.add(toAdd); + } else { + ArrayList comments = getAllCommentsByFeed(feed.getKey()); + //sort in chronological order + Collections.sort(comments); + + //TODO: retrieve only the first 2 more recents efficiently from the store + //only the first two first 2 more recent returned to the client initially + + int currCommentsNumber = comments.size(); + //if comments are less than 2 they are the more recent + if (currCommentsNumber < 2) { + EnhancedFeed toAdd = new EnhancedFeed(feed, liked, isUsers(feed, username), comments); + toReturn.add(toAdd); + } else { + //need to get the last two + ArrayList comments2Attach = new ArrayList(); + for (int i = currCommentsNumber -2; i < currCommentsNumber; i++) { + comments2Attach.add(comments.get(i)); + } + EnhancedFeed toAdd = new EnhancedFeed(feed, liked, isUsers(feed, username), comments2Attach); + toReturn.add(toAdd); + } + } + + } + _log.trace("****** ENHANCED FEEDS TOTAL= " + toReturn.size()); + return toReturn; + } + + /** + * this method is needed because user images portrait change id depending on the portal instance + * e.g. a post made from iMarine portal would not show the avatarIMage in D4Science.org + * @param screenname + * @return the url of the image portrait for this portal instance + */ + private String getUserImagePortraitUrlLocal(String screenName) { + if (! withinPortal) { + return ""; + } + StringBuilder thumbnailURL = new StringBuilder("/image/user_male_portrait?img_id="); +// if (portraitIdCache.containsKey(screenName)) +// return thumbnailURL.append(portraitIdCache.get(screenName)).toString(); +// else { + User user = null; + try { + user = UserLocalServiceUtil.getUserByScreenName(OrganizationsUtil.getCompany().getCompanyId(), screenName); +// portraitIdCache.put(screenName, user.getPortraitId()); + } catch (PortalException e) { + e.printStackTrace(); + } catch (SystemException e) { + e.printStackTrace(); + } + return thumbnailURL.append(user.getPortraitId()).toString(); +// } + } + + + @Override + public UserInfo getUserInfo() { + if (getUserFromSession() != null) + return getUserFromSession(); + try { + String username = getASLSession().getUsername(); + String email = username+"@isti.cnr.it"; + String fullName = username+" FULL"; + String thumbnailURL = "images/Avatar_default.png"; + + if (withinPortal) { + UserModel user = UserLocalServiceUtil.getUserByScreenName(OrganizationsUtil.getCompany().getCompanyId(), username); + thumbnailURL = "/image/user_male_portrait?img_id="+user.getPortraitId(); + fullName = user.getFirstName() + " " + user.getLastName(); + email = user.getEmailAddress(); + ThemeDisplay themeDisplay = (ThemeDisplay) this.getThreadLocalRequest().getSession().getAttribute(WebKeys.THEME_DISPLAY); + String accountURL = themeDisplay.getURLMyAccount().toString(); + UserInfo toReturn = new UserInfo(username, fullName, thumbnailURL, user.getEmailAddress(), accountURL, true, false, null); + setUserInSession(toReturn); + return toReturn; + } + else { + _log.info("Returning test USER"); + return new UserInfo(getASLSession().getUsername(), fullName, thumbnailURL, email, "fakeAccountUrl", true, false, null); + } + + } catch (Exception e) { + e.printStackTrace(); + } + return new UserInfo(); + } + + @Override + public ArrayList getAllLikesByFeed(String feedid) { + ArrayList toReturn = (ArrayList) store.getAllLikesByFeed(feedid); + _log.trace("Asking likes for " + feedid); + for (Like like : toReturn) { + like.setThumbnailURL(getUserImagePortraitUrlLocal(like.getUserid())); + } + return toReturn; + } + + @Override + public ArrayList getAllCommentsByFeed(String feedid) { + _log.trace("Asking comments for " + feedid); + ArrayList toReturn = (ArrayList) store.getAllCommentByFeed(feedid); + for (Comment comment : toReturn) { + comment.setThumbnailURL(getUserImagePortraitUrlLocal(comment.getUserid())); + } + Collections.sort(toReturn); + return toReturn; + } + @Override + public boolean deleteComment(String commentid, String feedid) { + _log.trace("Attempting to delete comment " + commentid); + try { + return store.deleteComment(commentid, feedid); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + @Override + public boolean deleteFeed(String feedid) { + _log.trace("Attempting to delete feed " + feedid); + try { + return store.deleteFeed(feedid); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + private UserInfo getUserFromSession() { + return (UserInfo) getASLSession().getAttribute(UserInfo.USER_INFO_ATTR); + } + + private void setUserInSession(UserInfo user) { + getASLSession().setAttribute(UserInfo.USER_INFO_ATTR, user); + } + /** + * tell if a feed is belonging to the current user or not + * @param tocheck + * @param username + * @return true if this feed is of the current user + */ + private boolean isUsers(Feed tocheck, String username) { + return (tocheck.getEntityId().equals(username)); + } + /** + * Escape an html string. Escaping data received from the client helps to + * prevent cross-site script vulnerabilities. + * + * @param html the html string to escape + * @return the escaped string + */ + private String escapeHtml(String html) { + if (html == null) { + return null; + } + return html.replaceAll("&", "&").replaceAll("<", "<") + .replaceAll(">", ">"); + } + + @Override + public Comment editComment(Comment toEdit) { + UserInfo user = getUserInfo(); + Comment edited = new Comment(toEdit.getKey(), toEdit.getUserid(), + new Date(), toEdit.getFeedid(), escapeHtml(toEdit.getText()), user.getFullName(), user.getAvatarId()); + try { + store.editComment(edited); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + return edited; + } + /** + * + * @return true if the user is a portal administrator or not + */ + private boolean checkisAdminUser() { + if (getASLSession().getAttribute(SESSION_ADMIN_ATTR) == null) { + boolean isAdmin = false; + try { + isAdmin = isAdmin(); + } catch (Exception e) { + e.printStackTrace(); + } + getASLSession().setAttribute(SESSION_ADMIN_ATTR, isAdmin); + return isAdmin; + } + return (Boolean) getASLSession().getAttribute(SESSION_ADMIN_ATTR); + } + /** + * tell if the user is a portal administrator or not + * @param username + * @return true if is admin + * @throws SystemException + * @throws PortalException + */ + private boolean isAdmin() throws PortalException, SystemException { + User currUser = OrganizationsUtil.validateUser(getASLSession().getUsername()); + List organizations = OrganizationLocalServiceUtil.getOrganizations(0, OrganizationLocalServiceUtil.getOrganizationsCount()); + Organization rootOrganization = null; + for (Organization organization : organizations) { + if (organization.getName().equals(OrganizationsUtil.getRootOrganizationName() ) ) { + rootOrganization = organization; + break; + } + } + try { + _log.trace("root: " + rootOrganization.getName() ); + return (hasRole(ADMIN_ROLE, rootOrganization.getName(), currUser)); + } + catch (NullPointerException e) { + _log.error("Cannot find root organziation, please check gcube-data.properties file in $CATALINA_HOME/conf folder"); + return false; + } + } + /** + * + * @param rolename + * @param organizationName + * @param user + * @return + * @throws SystemException + */ + private boolean hasRole(String rolename, String organizationName, User user) throws SystemException { + for (Role role : user.getRoles()) + if (role.getName().compareTo(rolename) == 0 ) + return true; + return false; + } +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/server/NotificationsThread.java b/src/main/java/org/gcube/portlets/user/newsfeed/server/NotificationsThread.java new file mode 100644 index 0000000..e1e9568 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/server/NotificationsThread.java @@ -0,0 +1,39 @@ +package org.gcube.portlets.user.newsfeed.server; + +import java.util.ArrayList; + +import org.gcube.applicationsupportlayer.social.NotificationsManager; +import org.gcube.common.core.utils.logging.GCUBEClientLog; +import org.gcube.portal.databook.shared.Like; + +/** + * + * @author Massimiliano Assante ISTI-CNR + * + */ +public class NotificationsThread implements Runnable { + private static GCUBEClientLog _log = new GCUBEClientLog(NotificationsThread.class); + private String commentText; + + private NotificationsManager nm; + private ArrayList likes; + + + public NotificationsThread(String commentText, NotificationsManager nm, ArrayList likes) { + super(); + this.commentText = commentText; + this.nm = nm; + this.likes = likes; + } + + @Override + public void run() { + for (Like fav : likes) { + boolean result = nm.notifyCommentReply(fav.getUserid(), fav.getFeedid(), commentText); + _log.trace("Sending Notification for favorited post comment added to: " + fav.getFullName() + " result?"+ result); + } + + } + + +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/server/portlet/NewsFeedPortlet.java b/src/main/java/org/gcube/portlets/user/newsfeed/server/portlet/NewsFeedPortlet.java new file mode 100644 index 0000000..ffed0ef --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/server/portlet/NewsFeedPortlet.java @@ -0,0 +1,26 @@ +package org.gcube.portlets.user.newsfeed.server.portlet; + +import java.io.IOException; + +import javax.portlet.ActionRequest; +import javax.portlet.ActionResponse; +import javax.portlet.GenericPortlet; +import javax.portlet.PortletException; +import javax.portlet.PortletRequestDispatcher; +import javax.portlet.RenderRequest; +import javax.portlet.RenderResponse; + +import org.gcube.portal.custom.scopemanager.scopehelper.ScopeHelper; + +public class NewsFeedPortlet extends GenericPortlet { + public void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException { + response.setContentType("text/html"); + ScopeHelper.setContext(request); + PortletRequestDispatcher dispatcher = getPortletContext().getRequestDispatcher("/WEB-INF/jsp/NewsFeed_view.jsp"); + dispatcher.include(request, response); + } + + public void processAction(ActionRequest request, ActionResponse response) + throws PortletException, IOException { + } +} diff --git a/src/main/java/org/gcube/portlets/user/newsfeed/shared/EnhancedFeed.java b/src/main/java/org/gcube/portlets/user/newsfeed/shared/EnhancedFeed.java new file mode 100644 index 0000000..03ce831 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/newsfeed/shared/EnhancedFeed.java @@ -0,0 +1,61 @@ +package org.gcube.portlets.user.newsfeed.shared; + +import java.io.Serializable; +import java.util.ArrayList; + +import org.gcube.portal.databook.shared.Comment; +import org.gcube.portal.databook.shared.Feed; +/** + * + * @author massi + * This class contains addtional user related information about a Feed + * e.g. if this user has liked it + */ +@SuppressWarnings("serial") +public class EnhancedFeed implements Serializable{ + private Feed feed; + private boolean liked; + private boolean isUsers; + private ArrayList comments; + public EnhancedFeed() { + super(); + } + public EnhancedFeed(Feed feed, boolean liked, boolean isUsers) { + super(); + this.feed = feed; + this.liked = liked; + this.isUsers = isUsers; + } + + public EnhancedFeed(Feed feed, boolean liked, boolean isUsers, ArrayList comments) { + super(); + this.isUsers = isUsers; + this.feed = feed; + this.liked = liked; + this.comments = comments; + } + public ArrayList getComments() { + return comments; + } + public void setComments(ArrayList comments) { + this.comments = comments; + } + public Feed getFeed() { + return feed; + } + public void setFeed(Feed feed) { + this.feed = feed; + } + public boolean isLiked() { + return liked; + } + public void setLiked(boolean liked) { + this.liked = liked; + } + public boolean isUsers() { + return isUsers; + } + public void setUsers(boolean isUsers) { + this.isUsers = isUsers; + } +} 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 new file mode 100644 index 0000000..f7f269d --- /dev/null +++ b/src/main/resources/org/gcube/portlets/user/newsfeed/NewsFeed.gwt.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/webapp/NewsFeed.css b/src/main/webapp/NewsFeed.css new file mode 100644 index 0000000..1062aaa --- /dev/null +++ b/src/main/webapp/NewsFeed.css @@ -0,0 +1,441 @@ +@import url(http://fonts.googleapis.com/css?family=Architects+Daughter); + +/* FILTER Widget*/ +table { + border-collapse: separate !important; + border-spacing: 0; +} + +.nofeed-message { + line-height: 40px; + font-family: 'Architects Daughter', arial, sans-serif; + font-size: 15px; +} + +.feed-filters { + margin-top: -5px; + width: 600px; +} + +.feed-filters ul { + list-style: none; + margin: 0; + padding-left: 0; +} + +.feed-filters li a { + color: #444; + text-decoration: none; +} + +.feed-filters li a:hover { + text-decoration: none; + color: #336699; + transition: color .25s ease-in-out; + -moz-transition: color .25s ease-in-out; + -webkit-transition: color .25s ease-in-out; +} + +.feed-filters li { + color: #444; + font-family: 'Lucida Grande', Verdana, 'Bitstream Vera Sans', Arial, + sans-serif; + font-size: 15px; + float: left; + height: 2em; + line-height: 2em; + display: block; + text-decoration: none; + text-align: center; + padding-left: 10px; + padding-right: 25px; + background-attachment: scroll; + background-color: transparent; + background-image: url("images/grid_small_dot.png"); + background-position: 100% 50%; + background-repeat: no-repeat; + background-size: auto auto; +} + +.feed-filters li.final { + background-image: none; +} + +.filter-selected a { + color: #336699 !important; + font-weight: bold; +} + +/* Other */ +.linkpreview-image { + margin: 3px; +} + +.link-preview { + padding: 5px; + font-family: 'lucida grande', tahoma, verdana, arial, sans-serif; + background-clip: border-box; + background-color: #FBFBFB; + background-image: none; + border: 1px solid #DDD; + border-radius: 4px; + width: 500px; + margin-top: 5px; + margin-left: 5px; +} + +.linkpreview-bgcolor { + background-color: #FBFBFB; +} + +.linkpreview-title { + margin-top: 3px; + font-size: 12px; + font-weight: bold; + line-height: 15px; +} + +.linkpreview-url { + margin-top: 10px; + color: #666; + font-size: 10px; +} + +.linkpreview-desc { + padding-top: 5px; + color: #333; + font-size: 10px; +} + +.member-photo { + display: block; + padding: 2px; + border: 1px solid #E6E6E6; +} + +.commentsPanel { + width: 430px !important; + padding-left: 5px; +} + +.comment-hidden { + opacity: 0; +} + +.comment-show { + background-color: #EDEFF4; + opacity: 1; + transition: opacity .45s ease-in-out; + -moz-transition: opacity .45s ease-in-out; + -webkit-transition: opacity .45s ease-in-out; + -ms-transition: opacity .45s ease-in-out; +} + +.commentsPanel { + width: 99%; + margin-top: 4px; +} + +.more-comment { + background-color: #EDEFF4; + width: 100%; + text-align: center; + padding-top: 3px; + border-bottom-color: #FFF; + border-bottom-style: solid; + border-bottom-width: 1px; +} + +.single-comment { + background-color: #EDEFF4; + border-bottom-color: #FFF; + border-bottom-style: solid; + border-bottom-width: 1px; + border-left: 2px solid #DDD; + padding-left: 3px; +} + +.comment-bgcolor { + background-color: #EDEFF4; +} + +.uiCloseButton { + background: url(images/close.png) 0px 0px no-repeat; + height: 15px; + width: 15px; +} + +.closeImage { + height: 15px; + width: 15px; +} + +.closeImage:hover { + background: url(images/close.png) 0px -16px no-repeat; + cursor: pointer; + cursor: hand; +} + +.closeImage:active { + background: url(images/close.png) 0px -32px no-repeat; +} + +.uiEditButton { + background: url(images/edit.png) 0px 0px no-repeat; + height: 15px; + width: 15px; +} + +.editImage { + height: 15px; + width: 15px; +} + +.editImage:hover { + background: url(images/edit.png) 0px -16px no-repeat; + cursor: pointer; + cursor: hand; +} + +.editImage:active { + background: url(images/edit.png) 0px -32px no-repeat; +} + +.user-comment { + width: 99%; + color: #333; + font-family: 'Lucida Grande', Verdana, 'Bitstream Vera Sans', Arial, + sans-serif; + font-size: 11px; + border-color: #999; + border-width: 1px; + letter-spacing: normal; +} + +.user-comment-time { + width: 99%; + color: #999; + font-family: 'Lucida Grande', Verdana, 'Bitstream Vera Sans', Arial, + sans-serif; + font-size: 11px; + white-space: nowrap; +} + +.post-comment { + height: 30px; + width: 99%; + color: #999; + font-family: 'Lucida Grande', Verdana, 'Bitstream Vera Sans', Arial, + sans-serif; + font-size: 11px; + padding: 4px 2px; + border-color: #999; + border-width: 1px; + letter-spacing: normal; + margin-top: 5px; +} + +a.seemore,a.seemore:visited { + font-family: 'Lucida Grande', Verdana, 'Bitstream Vera Sans', Arial, + sans-serif; + cursor: pointer; + cursor: hand; + font-size: 9px; + white-space: nowrap; + color: #3B5998; +} + +a.seemore:hover { + opacity: 0.8; + text-decoration: underline; +} + +a.person-link { + font-family: 'Helvetica Neue', Arial, sans-serif; + font-size: 16x; + line-height: 18px; + color: #3B5998; +} + +a.person-link,a.person-link:visited { + cursor: pointer; + cursor: hand; + font-size: 16x; + text-decoration: none; + color: #3B5998; +} + +a.person-link:hover { + opacity: 0.8; + font-size: 16x; + text-decoration: underline; +} + +.tweet-content { + font-family: 'Helvetica Neue', Arial, sans-serif; + font-size: 14px; + line-height: 18px; + color: #333; +} + +.tweet-separator { + padding-left: 5px; + padding-right: 5px; +} + +.tweet-actions { + padding-top: 5px; + font-family: 'Lucida Grande', Verdana, 'Bitstream Vera Sans', Arial, + sans-serif; + font-size: 11px; + text-align: left; + overflow: hidden; + clear: both; +} + +.tweet-actions .time { + padding-left: 15px; + padding-right: 15px; + white-space: nowrap; +} + +.tweet-actions .likes-number { + color: #3B5998; + width: 20px; + padding-left: 25px; + background: url("images/star_blue.png") no-repeat left; +} + +.tweet-actions .likes-number:hover,.show-comments-number:hover { + opacity: 0.8; + cursor: pointer; + cursor: hand; +} + +.tweet-actions .comments-number,.show-comments-number { + color: #3B5998; + width: 20px; + padding-left: 25px; + background: url("images/comment_edit.png") no-repeat left; +} + +.tweet-actions div { + color: #666; +} + +.tweet-actions div a { + color: #3B5998; + cursor: pointer; + cursor: hand; +} + +.tweet-actions div a:hover { + opacity: 0.8; + text-decoration: underline; +} + +a.link,a.link:active,a.link:visited { + font-family: 'Lucida Grande', Verdana, 'Bitstream Vera Sans', Arial, + sans-serif; + font-size: 12px; + cursor: pointer; + cursor: hand; + text-decoration: none; + color: #3B5998; +} + +a.link:hover { + opacity: 0.8; + text-decoration: underline; +} + +/* TABLES */ +.div-table-400 { + display: table; + width: 400px; + background-clip: border-box; + background-image: none; + background-origin: padding-box; + border-bottom-color: #DADADA; + border-bottom-style: solid; + border-bottom-width: 1px; + padding: 12px 7px 3px 7px; +} + +.div-table { + display: table; + width: 590px; + background-clip: border-box; + background-image: none; + background-origin: padding-box; + border-bottom-color: #DADADA; + border-bottom-style: solid; + border-bottom-width: 1px; + padding: 10px 7px 3px 7px; +} + +.div-table-row { + display: table-row; + text-align: left; +} + +.div-table-col { + display: table-cell; + text-align: left; +} + +.div-table-col.content { + padding-left: 10px; + vertical-align: top; +} + +.div-table-col.edit { + padding-left: 5px; + vertical-align: top; + width: 15px; +} + +.div-table-col.close { + padding-left: 5px; + vertical-align: top; + width: 15px; +} + +.div-table-col.photo { + width: 60px; +} + +.div-table-col.labels { + width: 60px; +} + +.div-table-col.smallphoto { + width: 40px; +} + +.visible { + background-color: #FFF; + transition: background .75s ease-in-out; + -moz-transition: background .75s ease-in-out; + -webkit-transition: background .75s ease-in-out; +} + +.hidden { + background-color: #fdffcc; +} + +.error { + color: #FFF; + background-color: pink; + transition: background .45s ease-in-out; + -moz-transition: background .45s ease-in-out; + -webkit-transition: background .45s ease-in-out; +} + +.dark-color { + color: #333; + background-color: #FFF; + transition: background .25s ease-in-out; + -moz-transition: background .25s ease-in-out; + -webkit-transition: background .25s ease-in-out; +} \ No newline at end of file diff --git a/src/main/webapp/NewsFeed.html b/src/main/webapp/NewsFeed.html new file mode 100644 index 0000000..ba726be --- /dev/null +++ b/src/main/webapp/NewsFeed.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + +News Feed + + + + + + + + + + + + + + + + +
+
+ + diff --git a/src/main/webapp/WEB-INF/jsp/NewsFeed_view.jsp b/src/main/webapp/WEB-INF/jsp/NewsFeed_view.jsp new file mode 100644 index 0000000..3cca2dc --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/NewsFeed_view.jsp @@ -0,0 +1,20 @@ +<%@page contentType="text/html"%> +<%@page pageEncoding="UTF-8"%> + +<%-- Uncomment below lines to add portlet taglibs to jsp +<%@ page import="javax.portlet.*"%> +<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet"%> + + +--%> + + + + + +
diff --git a/src/main/webapp/WEB-INF/liferay-display.xml b/src/main/webapp/WEB-INF/liferay-display.xml new file mode 100644 index 0000000..e90e2e7 --- /dev/null +++ b/src/main/webapp/WEB-INF/liferay-display.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/liferay-plugin-package.properties b/src/main/webapp/WEB-INF/liferay-plugin-package.properties new file mode 100644 index 0000000..26c3e64 --- /dev/null +++ b/src/main/webapp/WEB-INF/liferay-plugin-package.properties @@ -0,0 +1,9 @@ +name=NewsFeed +module-group-id=liferay +module-incremental-version=1 +tags= +short-description= +change-log= +page-url=http://www.d4science.org +author=D4Science.org +licenses=EUPL \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/liferay-portlet.xml b/src/main/webapp/WEB-INF/liferay-portlet.xml new file mode 100644 index 0000000..903f230 --- /dev/null +++ b/src/main/webapp/WEB-INF/liferay-portlet.xml @@ -0,0 +1,29 @@ + + + + + + NewsFeed + false + false + false + /NewsFeed.css + /js/pagebus.js + + + administrator + Administrator + + + guest + Guest + + + power-user + Power User + + + user + User + + diff --git a/src/main/webapp/WEB-INF/portlet.xml b/src/main/webapp/WEB-INF/portlet.xml new file mode 100644 index 0000000..19e41f7 --- /dev/null +++ b/src/main/webapp/WEB-INF/portlet.xml @@ -0,0 +1,30 @@ + + + + + NewsFeed + NewsFeed + org.gcube.portlets.user.newsfeed.server.portlet.NewsFeedPortlet + + view-jsp + /view.jsp + + 0 + + text/html + + + News feed + News feed + News feed + + + administrator + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..86d6d1a --- /dev/null +++ b/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,42 @@ + + + + + + newsServlet + org.gcube.portlets.user.newsfeed.server.NewsServiceImpl + + + + newsServlet + /newsfeed/newsServlet + + + + mailWisdgetServlet + org.gcube.portlets.user.wsmail.server.WsMailServiceImpl + + + + mailWisdgetServlet + /newsfeed/mailWisdgetServlet + + + + workspaceServiceImpl + org.gcube.portlets.user.workspace.lighttree.server.WorkspaceServiceImpl + + + workspaceServiceImpl + /newsfeed/WorkspaceLightService + + + + + NewsFeed.html + + + diff --git a/src/main/webapp/js/pagebus.js b/src/main/webapp/js/pagebus.js new file mode 100644 index 0000000..77b03e2 --- /dev/null +++ b/src/main/webapp/js/pagebus.js @@ -0,0 +1,182 @@ +/** + * Copyright (c) 2006-2007, TIBCO Software Inc. + * Use, modification, and distribution subject to terms of license. + * + * TIBCO(R) PageBus 1.1.0 + */ + +if(typeof window.PageBus == 'undefined') { + +PageBus = { + version: "1.1.0", + S: {c:{},s:[]}, + X: 0, + P: 0, + U: [], + H: "undefined" +}; + +PageBus.subscribe = function(name, scope, callback, subscriberData) +{ + if(name == null) + this._badName(); + if(scope == null) + scope = window; + var path = name.split("."); + var sub = { f: callback, d: subscriberData, i: this.X++, p: path, w: scope }; + for(var i = 0; i < path.length; i++) { + if((path[i].indexOf("*") != -1) && (path[i] != "*") && (path[i] != "**")) + this._badName(); + } + this._subscribe(this.S, path, 0, sub); + return sub; +} + +PageBus.publish = function (name, message) +{ + if((name == null) || (name.indexOf("*") != -1)) + this._badName(); + var path = name.split("."); + if(this.P > 100) + this._throw("StackOverflow"); + try { + this.P++; + this._publish(this.S, path, 0, name, message); + } + catch(err) { + this.P--; + throw err; + } + try { + this.P--; + if((this.U.length > 0) && (this.P == 0)) { + for(var i = 0; i < this.U.length; i++) + this.unsubscribe(this.U[i]); + this.U = []; + } + } + catch(err) { + // All unsubscribe exceptions should already have + // been handled when unsubscribe was called in the + // publish callback. This is a repeat appearance + // of this exception. Discard it. + } +} + +PageBus.unsubscribe = function(sub) +{ + this._unsubscribe(this.S, sub.p, 0, sub.i); +} + +/* + * @private @jsxobf-clobber + */ +PageBus._throw = function(n) +{ + throw new Error("PageBus." + n); +} + +/* + * @private @jsxobf-clobber + */ +PageBus._badName = function(n) +{ + this._throw("BadName"); +} + +/* + * @private @jsxobf-clobber + */ +PageBus._subscribe = function(tree, path, index, sub) +{ + var tok = path[index]; + if(tok == "") + this._badName(); + if(index == path.length) + tree.s.push(sub); + else { + if(typeof tree.c == this.H) + tree.c = {}; + if(typeof tree.c[tok] == this.H) { + try { + tree.c[tok] = { c: {}, s: [] }; + this._subscribe(tree.c[tok], path, index + 1, sub); + } + catch(err) { + delete tree.c[tok]; + throw err; + } + } + else + this._subscribe( tree.c[tok], path, index + 1, sub ); + } +} + +/* + * @private @jsxobf-clobber + */ +PageBus._publish = function(tree, path, index, name, msg) { + if(path[index] == "") + this._badName(); + if(typeof tree != this.H) { + if(index < path.length) { + this._publish(tree.c[path[index]], path, index + 1, name, msg); + this._publish(tree.c["*"], path, index + 1, name, msg); + this._call(tree.c["**"], name, msg); + } + else + this._call(tree, name, msg); + } +} + +/* + * @private @jsxobf-clobber + */ +PageBus._call = function(node, name, msg) { + if(typeof node != this.H) { + var callbacks = node.s; + var max = callbacks.length; + for(var i = 0; i < max; i++) + if(callbacks[i].f != null) + callbacks[i].f.apply(callbacks[i].w, [name, msg, callbacks[i].d]); + } +} + +/* + * @jsxobf-clobber + */ +PageBus._unsubscribe = function(tree, path, index, sid) { + if(typeof tree != this.H) { + if(index < path.length) { + var childNode = tree.c[path[index]]; + this._unsubscribe(childNode, path, index + 1, sid); + if(childNode.s.length == 0) { + for(var x in childNode.c) // not empty. We're done. + return; + delete tree.c[path[index]]; // if we got here, c is empty + } + return; + } + else { + var callbacks = tree.s; + var max = callbacks.length; + for(var i = 0; i < max; i++) { + if(sid == callbacks[i].i) { + if(this.P > 0) { + if(callbacks[i].f == null) + this._throw("BadParameter"); + callbacks[i].f = null; + this.U.push(callbacks[i]); + } + else + callbacks.splice(i, 1); + return; + } + } + // Not found. Fall through + } + } + this._throw("BadParameter"); +} + +} \ No newline at end of file diff --git a/src/test/java/org/gcube/portlets/user/newsfeed/client/GwtTestNewsFeed.java b/src/test/java/org/gcube/portlets/user/newsfeed/client/GwtTestNewsFeed.java new file mode 100644 index 0000000..35718b6 --- /dev/null +++ b/src/test/java/org/gcube/portlets/user/newsfeed/client/GwtTestNewsFeed.java @@ -0,0 +1,39 @@ +package org.gcube.portlets.user.newsfeed.client; + +import com.google.gwt.junit.client.GWTTestCase; + +/** + * GWT JUnit integration tests must extend GWTTestCase. + * Using "GwtTest*" naming pattern exclude them from running with + * surefire during the test phase. + * + * If you run the tests using the Maven command line, you will have to + * navigate with your browser to a specific url given by Maven. + * See http://mojo.codehaus.org/gwt-maven-plugin/user-guide/testing.html + * for details. + */ +public class GwtTestNewsFeed extends GWTTestCase { + + /** + * Must refer to a valid module that sources this class. + */ + public String getModuleName() { + return "org.gcube.portlets.user.newsfeed.NewsFeedJUnit"; + } + + /** + * Tests the FieldVerifier. + */ + public void testFieldVerifier() { + + } + + /** + * This test will send a request to the server using the greetServer method in + * GreetingService and verify the response. + */ + public void testGreetingService() { + } + + +} diff --git a/src/test/resources/org/gcube/portlets/user/newsfeed/NewsFeedJUnit.gwt.xml b/src/test/resources/org/gcube/portlets/user/newsfeed/NewsFeedJUnit.gwt.xml new file mode 100644 index 0000000..0f93997 --- /dev/null +++ b/src/test/resources/org/gcube/portlets/user/newsfeed/NewsFeedJUnit.gwt.xml @@ -0,0 +1,7 @@ + + + + + + +