diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..339f4fb --- /dev/null +++ b/.classpath @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..a5483d8 --- /dev/null +++ b/.project @@ -0,0 +1,59 @@ + + + share-updates + share-updates 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..ac8e4f0 --- /dev/null +++ b/.settings/com.google.gdt.eclipse.core.prefs @@ -0,0 +1,6 @@ +#Sat Jan 26 18:07:22 CET 2013 +eclipse.preferences.version=1 +jarsExcludedFromWebInfLib= +lastWarOutDir=/Users/massi/Documents/workspace/share-updates/target/share-updates-0.1.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..f706c57 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,7 @@ +#Sat Jan 26 18:07:22 CET 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..ad26666 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,9 @@ +#Thu Sep 02 10:42:20 CEST 2010 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.compliance=1.5 +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.5 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..1f0157a --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,5 @@ +#Sat Jan 26 18:07:21 CET 2013 +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/.settings/org.eclipse.wst.common.component b/.settings/org.eclipse.wst.common.component new file mode 100644 index 0000000..fe006d9 --- /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..18c1b9a --- /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/ShareUpdatesTest-dev.launch b/ShareUpdatesTest-dev.launch new file mode 100644 index 0000000..358c11e --- /dev/null +++ b/ShareUpdatesTest-dev.launch @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/ShareUpdatesTest-prod.launch b/ShareUpdatesTest-prod.launch new file mode 100644 index 0000000..d07bee6 --- /dev/null +++ b/ShareUpdatesTest-prod.launch @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/distro/INSTALL b/distro/INSTALL new file mode 100644 index 0000000..139597f --- /dev/null +++ b/distro/INSTALL @@ -0,0 +1,2 @@ + + diff --git a/distro/LICENSE b/distro/LICENSE new file mode 100644 index 0000000..cdb5851 --- /dev/null +++ b/distro/LICENSE @@ -0,0 +1,7 @@ +gCube System - License +------------------------------------------------------------ + +The gCube/gCore software is licensed as Free Open Source software conveying to the EUPL (http://ec.europa.eu/idabc/eupl). +The software and documentation is provided by its authors/distributors "as is" and no expressed or +implied warranty is given for its use, quality or fitness for a particular case. + diff --git a/distro/MAINTAINERS b/distro/MAINTAINERS new file mode 100644 index 0000000..680cebb --- /dev/null +++ b/distro/MAINTAINERS @@ -0,0 +1,6 @@ +Mantainers +------- + +* Massimiliano Assante (massimiliano.assante@isti.cnr.it), CNR Pisa, + Istituto di Scienza e Tecnologie dell'Informazione "A. Faedo". + diff --git a/distro/README b/distro/README new file mode 100644 index 0000000..663c2c1 --- /dev/null +++ b/distro/README @@ -0,0 +1,35 @@ +The gCube System - Social Library +------------------------------------------------------------ + +This work is partially funded by the European Commission in the +context of the iMarine project (www.i-marine.eu), under the 1st call of FP7 IST priority. + +Authors +------- +Massimiliano Assante +* +Version and Release Date +------------------------ +Jan 2013 + + +Description +----------- +Social networking Library + +Download information +-------------------- +Source code is available from SVN: +https://svn.d4science.research-infrastructures.eu/gcube/trunk/portlets/user/share-updates + +Binaries can be downloaded from: +http://maven.research-infrastructures.eu/nexus/index.html#welcome + +Documentation +------------- +Documentation is available on-line from the Projects Documentation Wiki: + +Licensing +--------- + +This software is licensed under the terms you may find in the file named "LICENSE" in this directory. diff --git a/distro/changelog.xml b/distro/changelog.xml new file mode 100644 index 0000000..827eeb5 --- /dev/null +++ b/distro/changelog.xml @@ -0,0 +1,6 @@ + + + First Release + + diff --git a/distro/descriptor.xml b/distro/descriptor.xml new file mode 100644 index 0000000..4efc827 --- /dev/null +++ b/distro/descriptor.xml @@ -0,0 +1,48 @@ + + servicearchive + + tar.gz + + / + + + ${distroDirectory} + / + true + + README + LICENSE + INSTALL + MAINTAINERS + changelog.xml + + 755 + true + + + target/apidocs + /${artifactId}/doc/api + true + 755 + + + + + ${distroDirectory}/profile.xml + ./ + true + + + target/${build.finalName}.war + /${artifactId} + + + ${distroDirectory}/svnpath.txt + /${artifactId} + true + + + \ No newline at end of file diff --git a/distro/profile.xml b/distro/profile.xml new file mode 100644 index 0000000..51c3b6b --- /dev/null +++ b/distro/profile.xml @@ -0,0 +1,25 @@ + + + + Service + + ${description} + PortletUser + ${artifactId} + ${version} + + + ${artifactId} + ${version} + + ${groupId} + ${artifactId} + ${version} + + + target/${build.finalName}.war + + + + + diff --git a/distro/svnpath.txt b/distro/svnpath.txt new file mode 100644 index 0000000..edacb04 --- /dev/null +++ b/distro/svnpath.txt @@ -0,0 +1 @@ +${scm.url} \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..256dae1 --- /dev/null +++ b/pom.xml @@ -0,0 +1,281 @@ + + + + 4.0.0 + + maven-parent + org.gcube.tools + 1.0.0 + + + + org.gcube.portlets.user + share-updates + war + 0.1.0-SNAPSHOT + + gCube Share Updates Portlet + + gCube Share Updates 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.slf4j + slf4j-log4j12 + 1.6.4 + runtime + + + org.slf4j + slf4j-api + 1.6.4 + runtime + + + com.sencha.gxt + gxt + 2.2.5 + provided + + + commons-validator + commons-validator + 1.4.0 + provided + + + org.gcube.portal + social-networking-library + 1.0.0-SNAPSHOT + provided + + + org.gcube.core + gcf + [1.4.0,1.6.0] + provided + + + org.gcube.portlets.user + gcube-widgets + 1.4.0-SNAPSHOT + provided + + + org.gcube.portal + custom-portal-handler + 1.2.0-SNAPSHOT + provided + + + org.gcube.applicationsupportlayer + aslcore + 3.2.0-SNAPSHOT + provided + + + com.google + gwt-jsonmaker + 1.2.1 + + + org.htmlparser + htmllexer + 2.1 + + + org.htmlparser + htmlparser + 2.1 + + + net.sourceforge.htmlcleaner + htmlcleaner + 2.2 + + + net.sf.jtidy + jtidy + r938 + + + net.eliasbalasis + tibcopagebus4gwt + 1.2.0 + + + com.liferay.portal + portal-service + 6.0.6 + provided + + + javax.portlet + portlet-api + 2.0 + provided + + + 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/shareupdates/client/ShareUpdateService.java b/src/main/java/org/gcube/portlets/user/shareupdates/client/ShareUpdateService.java new file mode 100644 index 0000000..e07373c --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/shareupdates/client/ShareUpdateService.java @@ -0,0 +1,22 @@ +package org.gcube.portlets.user.shareupdates.client; + +import org.gcube.portal.databook.shared.ClientFeed; +import org.gcube.portal.databook.shared.FeedType; +import org.gcube.portal.databook.shared.PrivacyLevel; +import org.gcube.portal.databook.shared.UserInfo; +import org.gcube.portlets.user.shareupdates.shared.LinkPreview; + +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("shareupdateServlet") +public interface ShareUpdateService extends RemoteService { + ClientFeed share(String feedText, FeedType type, PrivacyLevel pLevel, String vreName, String linkTitle, String linkDesc, String url, String urlThumbnail, String host); + + UserInfo getUserInfo(); + + LinkPreview checkLink(String linkToCheck); +} diff --git a/src/main/java/org/gcube/portlets/user/shareupdates/client/ShareUpdateServiceAsync.java b/src/main/java/org/gcube/portlets/user/shareupdates/client/ShareUpdateServiceAsync.java new file mode 100644 index 0000000..af91dd6 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/shareupdates/client/ShareUpdateServiceAsync.java @@ -0,0 +1,20 @@ +package org.gcube.portlets.user.shareupdates.client; + +import org.gcube.portal.databook.shared.ClientFeed; +import org.gcube.portal.databook.shared.FeedType; +import org.gcube.portal.databook.shared.PrivacyLevel; +import org.gcube.portal.databook.shared.UserInfo; +import org.gcube.portlets.user.shareupdates.shared.LinkPreview; + +import com.google.gwt.user.client.rpc.AsyncCallback; + +/** + * The async counterpart of ShareUpdateService. + */ +public interface ShareUpdateServiceAsync { + void share(String feedText, FeedType type, PrivacyLevel pLevel, + String vreName, String linkTitle, String linkDesc, String url, + String urlThumbnail, String host, AsyncCallback callback); + void getUserInfo(AsyncCallback callback); + void checkLink(String linkToCheck, AsyncCallback callback); +} diff --git a/src/main/java/org/gcube/portlets/user/shareupdates/client/ShareUpdates.java b/src/main/java/org/gcube/portlets/user/shareupdates/client/ShareUpdates.java new file mode 100644 index 0000000..b2ed5c6 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/shareupdates/client/ShareUpdates.java @@ -0,0 +1,20 @@ +package org.gcube.portlets.user.shareupdates.client; + +import org.gcube.portlets.user.shareupdates.client.form.ShareUpdateForm; + +import com.google.gwt.core.client.EntryPoint; +import com.google.gwt.user.client.ui.RootPanel; + +/** + * Entry point classes define onModuleLoad(). + */ +public class ShareUpdates implements EntryPoint { + + /** + * This is the entry point method. + */ + public void onModuleLoad() { + RootPanel.get("shareUpdateDiv").add(new ShareUpdateForm()); + + } +} diff --git a/src/main/java/org/gcube/portlets/user/shareupdates/client/form/ImageSwitcher.java b/src/main/java/org/gcube/portlets/user/shareupdates/client/form/ImageSwitcher.java new file mode 100644 index 0000000..b708aff --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/shareupdates/client/form/ImageSwitcher.java @@ -0,0 +1,95 @@ +package org.gcube.portlets.user.shareupdates.client.form; + +import java.util.ArrayList; + +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.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.HasHorizontalAlignment; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.Image; +import com.google.gwt.user.client.ui.VerticalPanel; + +public class ImageSwitcher extends Composite{ + + private ArrayList imageUrls; + private VerticalPanel mainPanel = new VerticalPanel(); + private HorizontalPanel controlPanel = new HorizontalPanel(); + private Image image = new Image(); + private HTML prev = new HTML(" << "); + private HTML label = new HTML("1 of 1"); + private HTML next = new HTML(" >> "); + private int currentIndex = 0; + + public ImageSwitcher() { + super(); + image.setWidth("80px"); + mainPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER); + mainPanel.setPixelSize(100, 100); + mainPanel.add(image); + + controlPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER); + controlPanel.add(prev); + controlPanel.add(label); + controlPanel.add(next); + + mainPanel.add(controlPanel); + + prev.setStyleName("small-text-arrow"); + label.setStyleName("small-text"); + next.setStyleName("small-text-arrow"); + + + initWidget(mainPanel); + + next.addClickHandler(new ClickHandler() { + public void onClick(ClickEvent event) { + if (imageUrls != null && currentIndex < imageUrls.size()) { + currentIndex++; + image.setUrl(imageUrls.get(currentIndex)); + label.setHTML((currentIndex+1)+" of " + imageUrls.size()); + } + } + }); + + prev.addClickHandler(new ClickHandler() { + public void onClick(ClickEvent event) { + if (imageUrls != null && currentIndex > 0) { + currentIndex--; + image.setUrl(imageUrls.get(currentIndex)); + label.setHTML((currentIndex+1)+" of " + imageUrls.size()); + } + } + }); + } + + + + protected void setImages(ArrayList imageUrls) { + this.imageUrls = imageUrls; + if (imageUrls.size() == 0) { + mainPanel.remove(controlPanel); + return; + } + image.setUrl(imageUrls.get(0)); + GWT.log("images no. " + imageUrls.size()); + if (imageUrls.size() < 2) { + prev.setVisible(false); + next.setVisible(false); + } else { + enableSwitch(imageUrls); + } + } + + private void enableSwitch(ArrayList imageUrls) { + label.setHTML("1 of " + imageUrls.size()); + currentIndex = 0; + } + + protected String getSelectedImageURL() { + return image.getUrl(); + } +} diff --git a/src/main/java/org/gcube/portlets/user/shareupdates/client/form/LinkLoader.java b/src/main/java/org/gcube/portlets/user/shareupdates/client/form/LinkLoader.java new file mode 100644 index 0000000..122cd49 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/shareupdates/client/form/LinkLoader.java @@ -0,0 +1,22 @@ +package org.gcube.portlets.user.shareupdates.client.form; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; + +public class LinkLoader extends Composite { + + private static LinkLoaderUiBinder uiBinder = GWT + .create(LinkLoaderUiBinder.class); + + interface LinkLoaderUiBinder extends UiBinder { + } + + public LinkLoader() { + initWidget(uiBinder.createAndBindUi(this)); + } + + + +} diff --git a/src/main/java/org/gcube/portlets/user/shareupdates/client/form/LinkLoader.ui.xml b/src/main/java/org/gcube/portlets/user/shareupdates/client/form/LinkLoader.ui.xml new file mode 100644 index 0000000..d3de5a3 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/shareupdates/client/form/LinkLoader.ui.xml @@ -0,0 +1,8 @@ + + + + +
+
+
\ No newline at end of file diff --git a/src/main/java/org/gcube/portlets/user/shareupdates/client/form/LinkPlaceholder.java b/src/main/java/org/gcube/portlets/user/shareupdates/client/form/LinkPlaceholder.java new file mode 100644 index 0000000..94d4f70 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/shareupdates/client/form/LinkPlaceholder.java @@ -0,0 +1,11 @@ +package org.gcube.portlets.user.shareupdates.client.form; + +import com.google.gwt.user.client.ui.SimplePanel; +/** + * + * @author massi + * + */ +public class LinkPlaceholder extends SimplePanel { + +} diff --git a/src/main/java/org/gcube/portlets/user/shareupdates/client/form/LinkPreviewer.java b/src/main/java/org/gcube/portlets/user/shareupdates/client/form/LinkPreviewer.java new file mode 100644 index 0000000..fefa3ee --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/shareupdates/client/form/LinkPreviewer.java @@ -0,0 +1,83 @@ +package org.gcube.portlets.user.shareupdates.client.form; + +import org.gcube.portlets.user.shareupdates.shared.LinkPreview; + +import com.google.gwt.core.client.GWT; +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.ui.CheckBox; +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 LinkPreviewer extends Composite { + + private static LinkPreviewUiBinder uiBinder = GWT + .create(LinkPreviewUiBinder.class); + + interface LinkPreviewUiBinder extends UiBinder { + } + + private ShareUpdateForm parent; + + private LinkPreview toShow; + + public LinkPreviewer(ShareUpdateForm parent, LinkPreview toShow) { + initWidget(uiBinder.createAndBindUi(this)); + closeImage.setStyleName("su-closeImage"); + closeImage.setTitle("Cancel"); + this.parent = parent; + this.toShow = toShow; + + titleArea.setHTML(""+toShow.getTitle()+" - " + toShow.getHost() + ""); + String url = toShow.getUrl(); + urlText.setHTML((url.length() > 80) ? url.substring(0, 80)+"..." : url); + String desc = toShow.getDescription(); + descText.setHTML((desc.length() > 256) ? desc.substring(0, 256)+"..." : desc); + switcher.setImages(toShow.getImageUrls()); + } + + @UiField HTML closeImage; + @UiField ImageSwitcher switcher; + + @UiField + HTML titleArea; + @UiField + HTML urlText; + @UiField + HTML descText; + @UiField + CheckBox hideCheckBox; + + public ImageSwitcher getSwitcher() { + return switcher; + } + + @UiHandler("closeImage") + void onDeleteFeedClick(ClickEvent e) { + parent.cancelPreview(); + } + + @UiHandler("hideCheckBox") + void onClick(ClickEvent e) { + descText.setVisible(!hideCheckBox.getValue()); + } + + public String getLinkTitle() { + return toShow.getTitle(); + } + public String getLinkDescription() { + return hideCheckBox.getValue() ? "" : toShow.getDescription(); + } + public String getUrl() { + return toShow.getUrl(); + } + public String getHost() { + return toShow.getHost(); + } + public String getUrlThumbnail() { + return switcher.getSelectedImageURL(); + } +} diff --git a/src/main/java/org/gcube/portlets/user/shareupdates/client/form/LinkPreviewer.ui.xml b/src/main/java/org/gcube/portlets/user/shareupdates/client/form/LinkPreviewer.ui.xml new file mode 100644 index 0000000..1242154 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/shareupdates/client/form/LinkPreviewer.ui.xml @@ -0,0 +1,35 @@ + + + + .important { + font-weight: bold; + } + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/org/gcube/portlets/user/shareupdates/client/form/MyTextArea.java b/src/main/java/org/gcube/portlets/user/shareupdates/client/form/MyTextArea.java new file mode 100644 index 0000000..b97041c --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/shareupdates/client/form/MyTextArea.java @@ -0,0 +1,66 @@ +/** + * + */ +package org.gcube.portlets.user.shareupdates.client.form; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Element; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.TextArea; + +/** + * @author massi + * + */ +public class MyTextArea extends TextArea { + + /** + * + */ + public MyTextArea() { + sinkEvents(Event.ONPASTE); + sinkEvents(Event.ONCONTEXTMENU); + } + + /** + * @param element + */ + public MyTextArea(Element element) { + super(element); + } + + /** + * paste event overridden + */ + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + switch (event.getTypeInt()) { + case Event.ONPASTE: { + final String before = getText(); + GWT.log("BEFORE:" + before); + Timer t = new Timer() { + @Override + public void run() { + String toCheck = getText().replaceAll(before, ""); + ShareUpdateForm.get().checkLink(toCheck); + } + }; + t.schedule(100); + break; + } + case Event.ONCONTEXTMENU: { + removeSampleText(); + break; + } + } + } + protected void removeSampleText() { + if (getText().equals(ShareUpdateForm.SHARE_UPDATE_TEXT) || getText().equals(ShareUpdateForm.ERROR_UPDATE_TEXT) ) { + setText(""); + addStyleName("dark-color"); + removeStyleName("error"); + } + } +} diff --git a/src/main/java/org/gcube/portlets/user/shareupdates/client/form/ShareUpdateForm.java b/src/main/java/org/gcube/portlets/user/shareupdates/client/form/ShareUpdateForm.java new file mode 100644 index 0000000..e6a0062 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/shareupdates/client/form/ShareUpdateForm.java @@ -0,0 +1,295 @@ +package org.gcube.portlets.user.shareupdates.client.form; + +import net.eliasbalasis.tibcopagebus4gwt.client.PageBusAdapter; +import net.eliasbalasis.tibcopagebus4gwt.client.PageBusAdapterException; + +import org.gcube.portal.databook.shared.ClientFeed; +import org.gcube.portal.databook.shared.ClientFeed.ClientFeedJsonizer; +import org.gcube.portal.databook.shared.FeedType; +import org.gcube.portal.databook.shared.PrivacyLevel; +import org.gcube.portal.databook.shared.UserInfo; +import org.gcube.portlets.user.shareupdates.client.ShareUpdateService; +import org.gcube.portlets.user.shareupdates.client.ShareUpdateServiceAsync; +import org.gcube.portlets.user.shareupdates.shared.LinkPreview; +import org.jsonmaker.gwt.client.Jsonizer; + +import com.google.gwt.core.client.GWT; +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.Button; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Image; +import com.google.gwt.user.client.ui.ListBox; +import com.google.gwt.user.client.ui.Widget; +/** + * + * @author Massimiliano Assante + * @version 1.2 Nov 2012 + * + */ +public class ShareUpdateForm extends Composite { + /** + * Create a remote service proxy to talk to the server-side Greeting service. + */ + private final ShareUpdateServiceAsync shareupdateService = GWT + .create(ShareUpdateService.class); + + final PageBusAdapter pageBusAdapter = new PageBusAdapter(); + protected final static String SHARE_UPDATE_TEXT = "Share an update or paste a link"; + protected final static String ERROR_UPDATE_TEXT = "Looks like empty to me!"; + private final static String LISTBOX_LEVEL = " - "; + + public static final String loading = GWT.getModuleBaseURL() + "../images/avatarLoader.gif"; + public static final String avatar_default = GWT.getModuleBaseURL() + "../images/Avatar_default.png"; + + private static ShareUpdateFormUiBinder uiBinder = GWT + .create(ShareUpdateFormUiBinder.class); + + private LinkPreviewer myLinkPreviewer; + + interface ShareUpdateFormUiBinder extends UiBinder { + } + + private static ShareUpdateForm singleton; + + public static ShareUpdateForm get() { + return singleton; + } + + + UserInfo myUserInfo; + + public ShareUpdateForm() { + initWidget(uiBinder.createAndBindUi(this)); + singleton = this; + + avatarImage.setUrl(loading); + shareTextArea.setText(SHARE_UPDATE_TEXT); + shareupdateService.getUserInfo(new AsyncCallback() { + public void onFailure(Throwable caught) { + avatarImage.setSize("60px", "60px"); + avatarImage.setUrl(avatar_default); + } + + public void onSuccess(UserInfo user) { + myUserInfo = user; + avatarImage.getElement().getParentElement().setAttribute("href", user.getAccountURL()); + avatarImage.setSize("60px", "60px"); + avatarImage.setUrl(user.getAvatarId()); + if (myUserInfo.getOwnVREs().size() > 1){ + privacyLevel.addItem("My VREs", PrivacyLevel.VRES.toString()); + for (String vreId : myUserInfo.getOwnVREs().keySet()) + privacyLevel.addItem(LISTBOX_LEVEL + myUserInfo.getOwnVREs().get(vreId), vreId); + } + else if (myUserInfo.getOwnVREs().size() == 1) + for (String vreId : myUserInfo.getOwnVREs().keySet()) + privacyLevel.addItem(LISTBOX_LEVEL + myUserInfo.getOwnVREs().get(vreId), vreId); + privacyLevel.addItem("My Connections", PrivacyLevel.CONNECTION.toString()); + if (user.isAdmin()) + privacyLevel.addItem("Anyone", PrivacyLevel.PORTAL.toString()); + } + }); + } + @UiField + LinkPlaceholder preview; + + @UiField + Button submitButton; + + @UiField + Image avatarImage; + + @UiField MyTextArea shareTextArea; + + @UiField ListBox privacyLevel = new ListBox(false); + + @UiHandler("shareTextArea") + void onShareUpdateClick(ClickEvent e) { + shareTextArea.removeSampleText(); + } + + @UiHandler("submitButton") + void onClick(ClickEvent e) { + shareupdateService.getUserInfo(new AsyncCallback() { + 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."); + } + public void onSuccess(UserInfo result) { + if (result.getUsername().equals("test.user")) { + Window.alert("Your session has expired, please log out and login again"); + return; + } + myUserInfo = result; + String toShare = shareTextArea.getText().trim(); + if (toShare.equals(SHARE_UPDATE_TEXT) || toShare.equals(ERROR_UPDATE_TEXT) || toShare.equals("")) { + shareTextArea.addStyleName("error"); + shareTextArea.setText(ERROR_UPDATE_TEXT); + return; + } + //then you can post + postTweet(toShare); + } + }); + } + /** + * + * @param textToPost + */ + private void postTweet(String textToPost) { + String toShare = escapeHtml(textToPost); + if (! checkTextLength(toShare)) { + Window.alert("We found a single word containing more than 50 chars and it's not a link, is it meaningful?"); + return; + } + + + submitButton.setEnabled(false); + shareTextArea.setEnabled(false); + + String vreId = ""; + if (getPrivacyLevel() == PrivacyLevel.SINGLE_VRE) { + vreId = privacyLevel.getValue(privacyLevel.getSelectedIndex()); + } + + String linkTitle = ""; + String linkDescription = ""; + String linkUrl = ""; + String linkUrlThumbnail = ""; + String linkHost = ""; + + if (myLinkPreviewer != null) { + linkTitle = myLinkPreviewer.getLinkTitle(); + linkDescription = myLinkPreviewer.getLinkDescription(); + linkUrl = myLinkPreviewer.getUrl(); + linkUrlThumbnail = myLinkPreviewer.getUrlThumbnail(); + linkHost = myLinkPreviewer.getHost(); + } + shareupdateService.share(toShare, FeedType.TWEET, getPrivacyLevel(), vreId, linkTitle, linkDescription, linkUrl, linkUrlThumbnail, linkHost, new AsyncCallback() { + public void onFailure(Throwable caught) { + submitButton.setEnabled(true); + shareTextArea.setEnabled(true); + shareTextArea.setText(SHARE_UPDATE_TEXT); + preview.clear(); + myLinkPreviewer = null; + } + + public void onSuccess(ClientFeed feed) { + submitButton.setEnabled(true); + shareTextArea.setEnabled(true); + shareTextArea.setText(SHARE_UPDATE_TEXT); + preview.clear(); + myLinkPreviewer = null; + if (feed == null) + Window.alert("Ops! we encountered some problems delivering your message, please try again in a short while."); + else { + // publish a message with the refresh notification + try { + pageBusAdapter.PageBusPublish("org.gcube.portal.databook.shared", feed, (Jsonizer)GWT.create(ClientFeedJsonizer.class)); + } catch (PageBusAdapterException ex) { + GWT.log(ex.getMessage()); + } + } + } + }); + } + + private PrivacyLevel getPrivacyLevel() { + String selected = privacyLevel.getValue(privacyLevel.getSelectedIndex()); + if (selected.compareTo(PrivacyLevel.CONNECTION.toString()) == 0) + return PrivacyLevel.CONNECTION; + else if (selected.compareTo(PrivacyLevel.VRES.toString()) == 0) + return PrivacyLevel.VRES; + else if (selected.compareTo(PrivacyLevel.PRIVATE.toString()) == 0) + return PrivacyLevel.PRIVATE; + else if (selected.compareTo(PrivacyLevel.PORTAL.toString()) == 0) + return PrivacyLevel.PORTAL; + else + return PrivacyLevel.SINGLE_VRE; + + } + + + + /** + * 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(">", ">"); + } + /** + * called when pasting a possible link + * @param linkToCheck + */ + protected void checkLink(String textToCheck) { + if (myLinkPreviewer == null) { + String [] parts = textToCheck.split("\\s"); + // Attempt to convert each item into an URL. + for( String item : parts ) { + if (item.startsWith("http")) { + preview.add(new LinkLoader()); + //GWT.log("It's http link:" + linkToCheck); + shareupdateService.checkLink(textToCheck, new AsyncCallback() { + public void onFailure(Throwable caught) { + preview.clear(); + } + + public void onSuccess(LinkPreview result) { + preview.clear(); + if (result != null) + addPreview(result); + } + }); + break; + } + } + + } else { + Window.alert("You cannot post two links, please remove the previous one first."); + } + } + + /** + * 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; + } + /** + * add the link preview in the view + * @param result + */ + private void addPreview(LinkPreview result) { + myLinkPreviewer = new LinkPreviewer(this, result); + preview.add(myLinkPreviewer); + } + /** + * + */ + protected void cancelPreview() { + preview.clear(); + myLinkPreviewer = null; + } +} diff --git a/src/main/java/org/gcube/portlets/user/shareupdates/client/form/ShareUpdateForm.ui.xml b/src/main/java/org/gcube/portlets/user/shareupdates/client/form/ShareUpdateForm.ui.xml new file mode 100644 index 0000000..1f53943 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/shareupdates/client/form/ShareUpdateForm.ui.xml @@ -0,0 +1,48 @@ + + + + .important { + font-weight: bold; + } + + + + + + + + +
+ + + + +
+ +
+
+ + + + + + + + +
+
+ privacy level: + +
+
+
+ + + +
+
+ +
+
\ No newline at end of file diff --git a/src/main/java/org/gcube/portlets/user/shareupdates/server/ShareUpdateServiceImpl.java b/src/main/java/org/gcube/portlets/user/shareupdates/server/ShareUpdateServiceImpl.java new file mode 100644 index 0000000..d9d4b4e --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/shareupdates/server/ShareUpdateServiceImpl.java @@ -0,0 +1,564 @@ +package org.gcube.portlets.user.shareupdates.server; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.UUID; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import org.apache.commons.validator.routines.UrlValidator; +import org.gcube.application.framework.core.session.ASLSession; +import org.gcube.application.framework.core.session.SessionManager; +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.ClientFeed; +import org.gcube.portal.databook.shared.Feed; +import org.gcube.portal.databook.shared.FeedType; +import org.gcube.portal.databook.shared.PrivacyLevel; +import org.gcube.portal.databook.shared.UserInfo; +import org.gcube.portal.databook.shared.ex.FeedIDNotFoundException; +import org.gcube.portlets.user.shareupdates.client.ShareUpdateService; +import org.gcube.portlets.user.shareupdates.server.metaseeker.MetaSeeker; +import org.gcube.portlets.user.shareupdates.server.opengraph.OpenGraph; +import org.gcube.portlets.user.shareupdates.shared.LinkPreview; +import org.gcube.vomanagement.usermanagement.GroupManager; +import org.gcube.vomanagement.usermanagement.impl.liferay.LiferayGroupManager; +import org.gcube.vomanagement.usermanagement.model.GroupModel; +import org.htmlparser.beans.StringBean; +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; +import org.w3c.tidy.Tidy; + +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; +import com.sun.net.ssl.HttpsURLConnection; + +/** + * The server side implementation of the RPC service. + */ +@SuppressWarnings("serial") +public class ShareUpdateServiceImpl extends RemoteServiceServlet implements ShareUpdateService { + /** + * + */ + private static final String ADMIN_ROLE = "Administrator"; + /** + * + */ + private static GCUBEClientLog _log = new GCUBEClientLog(ShareUpdateServiceImpl.class); + /** + * The store interface + */ + private DatabookStore store; + /** + * used for debugging in eclipse + */ + private boolean withinPortal = false; + /** + * connect to cassandra at startup + */ + public void init() { + store = new DBCassandraAstyanaxImpl(); + } + + 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 test.user and Running OUTSIDE PORTAL"); + user = "test.user"; + //user = "massimiliano.assante"; + withinPortal = false; + } + else { + withinPortal = true; + } + System.out.println("SessionID = " + sessionID); + return SessionManager.getInstance().getASLSession(sessionID, user); + } + /** + * + */ + public ClientFeed share(String feedText, FeedType feedType, PrivacyLevel pLevel, String vreId, String linkTitle, + String linkDesc, String url, String urlThumbnail, String host) { + + String escapedFeedText = escapeHtml(feedText); + String username = getASLSession().getUsername(); + String email = username+"@isti.cnr.it"; + String fullName = username+" FULL"; + String thumbnailURL = "images/Avatar_default.png"; + + if (withinPortal) { + try { + UserInfo user = getUserFromSession(); + email = user.getEmailaddress(); + fullName = user.getFullName(); + thumbnailURL = user.getAvatarId(); + } catch (Exception e) { + e.printStackTrace(); + } + } + Feed toShare = new Feed(UUID.randomUUID().toString(), feedType, username, new Date(), + "", url, urlThumbnail, transformUrls(escapedFeedText), pLevel, fullName, email, thumbnailURL, linkTitle, linkDesc, host); + + _log.trace("Attempting to save Feed with text: " + escapedFeedText + " Level: " + pLevel); + boolean result = store.saveUserFeed(toShare); + + //need to put the feed into VRES Timeline too + if (pLevel == PrivacyLevel.VRES) { + _log.trace("PrivacyLevel was set to VRES attempting to write onto User's VRES Timelines"); + for (GroupModel vre : getUserVREs(username)) { + String vreScope = getScopeByOrganizationId(vre.getGroupId()); + _log.trace("Attempting to write onto " + vreScope); + try { + store.saveFeedToVRETimeline(toShare.getKey(), vreScope); + } catch (FeedIDNotFoundException e) { + _log.error("Error writing onto VRES Time Line" + vreScope); + } //save the feed + _log.trace("Success writing onto " + vreScope); + } + + } //share on a single VRE Timeline + //receives a VreId(groupId) get the scope from the groupId + else if (pLevel == PrivacyLevel.SINGLE_VRE && vreId != null && vreId.compareTo("") != 0) { + String vreScope = getScopeByOrganizationId(vreId); + _log.trace("Attempting to write onto " + vreScope); + try { + store.saveFeedToVRETimeline(toShare.getKey(), vreScope); + } catch (FeedIDNotFoundException e) { + _log.error("Error writing onto VRES Time Line" + vreScope); + } //save the feed + _log.trace("Success writing onto " + vreScope); + } + if (!result) return null; + + ClientFeed cf = new ClientFeed(toShare.getKey(), toShare.getType().toString(), username, toShare.getUri(), + replaceAmpersand(toShare.getDescription()), fullName, email, thumbnailURL, toShare.getLinkTitle(), toShare.getLinkDescription(), + toShare.getUriThumbnail(), toShare.getLinkHost()); + return cf; + } + + private UserInfo getUserFromSession() { + return (UserInfo) getASLSession().getAttribute(UserInfo.USER_INFO_ATTR); + } + + private void setUserInSession(UserInfo user) { + getASLSession().setAttribute(UserInfo.USER_INFO_ATTR, user); + } + private String replaceAmpersand(String toReplace) { + String toReturn = toReplace.replaceAll("&", "&"); + return toReturn; + } + /** + * utilty method that convert a url ina text in a clickable url by the browser + * and if the user has just pasted a link, converts the link in: shared a link + * @param feedText + * @return the text with the clickable url in it + */ + public String transformUrls(String feedText) { + StringBuilder sb = new StringBuilder(); + // separate input by spaces ( URLs have no spaces ) + String [] parts = feedText.split("\\s"); + // Attempt to convert each item into an URL. + for (int i = 0; i < parts.length; i++) { + if (parts[i].startsWith("http")) { + try { + URL url = new URL(parts[i]); + if (i == 0 && parts.length == 1) //then he shared just a link + return sb.append("shared ").append("a link.").append(" ").toString(); + // If possible then replace with anchor... + sb.append("").append(url).append(" "); + } catch (MalformedURLException e) { + // If there was an URL then it's not valid + _log.error("MalformedURLException returning... "); + return feedText; + } + } else { + sb.append(parts[i]); + sb.append(" "); + } + } + return sb.toString(); + + } + + public UserInfo getUserInfo() { + try { + + String username = getASLSession().getUsername(); + String email = username+"@isti.cnr.it"; + String fullName = username+" FULL"; + String thumbnailURL = "images/Avatar_default.png"; + + if (withinPortal) { + getUserVREs(username); + 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(); + HashMap vreNames = getUserVreNames(username); + UserInfo toReturn = new UserInfo(username, fullName, thumbnailURL, user.getEmailAddress(), accountURL, true, isAdmin(), vreNames); + setUserInSession(toReturn); + return toReturn; + } + else { + _log.info("Returning test USER"); + HashMap fakeVreNames = new HashMap(); + fakeVreNames.put("1","devVRE"); + // fakeVreNames.put("2", "devNext"); + return new UserInfo(getASLSession().getUsername(), fullName, thumbnailURL, email, "fakeAccountUrl", true, false, fakeVreNames); + } + + } catch (Exception e) { + e.printStackTrace(); + } + return new UserInfo(); + } + /** + * return the id as key and the names as value of the vre a user is subscribed to + * @param username + * @return the id as key and the names as value of the vre a user is subscribed to + */ + private HashMap getUserVreNames(String username) { + HashMap toReturn = new HashMap(); + for (GroupModel vre : getUserVREs(username)) { + toReturn.put(vre.getGroupId(), vre.getGroupName()); + } + return toReturn; + } + /** + * 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; + } + /** + * + * @param username + * @return + */ + private ArrayList getUserVREs(String username) { + ArrayList toReturn = new ArrayList(); + User currUser; + try { + GroupManager gm = new LiferayGroupManager(); + currUser = OrganizationsUtil.validateUser(username); + for (Organization org : currUser.getOrganizations()) + if (gm.isVRE(org.getOrganizationId()+"")) { + GroupModel toAdd = gm.getGroup(""+org.getOrganizationId()); + toReturn.add(toAdd); + } + } catch (Exception e) { + _log.error("Failed reading User VREs for : " + username); + e.printStackTrace(); + return toReturn; + } + return toReturn; + } + + private String getScopeByOrganizationId(String vreid) { + GroupManager gm = new LiferayGroupManager(); + try { + return gm.getScope(vreid); + } catch (Exception e) { + _log.error("Could not find a scope for this VREid: " + vreid); + return null; + } + } + /** + * 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(">", ">"); + } + + /** + * utilty method that extract an url ina text + * @param feedText + * @return the text with the clickable url in it + */ + public String extractURL(String feedText) { + // separate input by spaces ( URLs have no spaces ) + String [] parts = feedText.split("\\s"); + // Attempt to convert each item into an URL. + for( String item : parts ) { + if (item.startsWith("http")) { + try { + new URL(item); + return item; + } catch (MalformedURLException e) { + // If there was an URL then it's not valid + _log.error("MalformedURLException returning... "); + return null; + } + } + } + return null; + } + /** + * tries the following in the indicated order for Populating the Link preview + * Open Graph protocol + * Meta "title" and "description" tags + * Best guess from page content (not recommended) + * + * Schema.org microdata <-- This is still a TODO + */ + public LinkPreview checkLink(String linkToCheck) { + LinkPreview toReturn = null; + _log.info("to check " + linkToCheck); + //look for a url in text + linkToCheck = extractURL(linkToCheck); + if (linkToCheck == null) + return null; //no url + + String[] schemes = {"http","https"}; + UrlValidator urlValidator = new UrlValidator(schemes); + if (! urlValidator.isValid(linkToCheck)) { + _log.warn("url is NOT valid, returning nothing"); + return null; + } + _log.debug("url is valid"); + + URL pageURL; + URLConnection siteConnection = null; + try { + pageURL = new URL(linkToCheck); + if (pageURL.getProtocol().equalsIgnoreCase("https")) { + System.setProperty("java.protocol.handler.pkgs", "com.sun.net.ssl.internal.www.protocol"); + java.security.Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider()); + trustAllHTTPSConnections(); + siteConnection = (HttpsURLConnection) pageURL.openConnection(); + } + else + siteConnection = (HttpURLConnection) pageURL.openConnection(); + } catch (MalformedURLException e) { + _log.error("url is not valid"); + return null; + } catch (IOException e) { + _log.error("url is not reachable"); + return null; + } + String title; + String description; + ArrayList imageUrls = new ArrayList(); + //get the host from the url + String host = pageURL.getHost().replaceAll("www.", ""); + + //try openGraph First + OpenGraph ogLink = null; + try { + ogLink = new OpenGraph(linkToCheck, true, siteConnection); + if (ogLink == null || ogLink.getContent("title") == null) { + //there is no OpenGraph for this link + _log.info("No OpenGraph Found, going Best guess from page content") ; + toReturn = getInfoFromHTML(pageURL, linkToCheck, host); + } else { + //there is OpenGraph + title = ogLink.getContent("title"); + description = (ogLink.getContent("description") != null) ? ogLink.getContent("description") : ""; + description = ((description.length() > 256) ? description.substring(0, 256)+"..." : description); + //look for the imagem ask the guesser if not present + if (ogLink.getContent("image") != null) + imageUrls.add(ogLink.getContent("image")); + else { + ArrayList images = getImagesFromHTML(pageURL); + if (! images.isEmpty()) + imageUrls = images; + } + toReturn = new LinkPreview(title, description, linkToCheck, host, imageUrls); + return toReturn; + } + } catch (IOException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + return toReturn; + } + /** + * get all the image urls from an HTML page up to 15 + * @param pageURL the url + * @return a list of image url + * @throws IOException + */ + private ArrayList getImagesFromHTML(URL pageURL) throws IOException { + ArrayList toReturn = new ArrayList(); + InputStream input = pageURL.openStream(); + try { + Document document = new Tidy().parseDOM(input, null); + NodeList imgs = document.getElementsByTagName("img"); + int upTo = (imgs.getLength() > 15) ? 15 : imgs.getLength(); + for (int i = 0; i < upTo; i++) { + System.out.println(i); + toReturn.add(imgs.item(i).getAttributes().getNamedItem("src").getNodeValue()); + } + }catch (NullPointerException e) { + _log.error("Error parsing HTML for images, malformed HTML returning what I found so far ... "); + return toReturn; + } + return toReturn; + } + + /** + * to use when OpenGraph is not available, Tries Metadata first, then Best guess from page content + * @param pageUrl + * @param link + * @param host + * @return a LinPreview object instance filled with the extracted information + * @throws IOException + */ + private LinkPreview getInfoFromHTML(URL pageUrl, String link, String host) throws Exception { + LinkPreview toReturn = null; + String title = ""; + String description = ""; + + InputStream input = pageUrl.openStream(); + Document document = new Tidy().parseDOM(input, null); + NodeList titles = document.getElementsByTagName("title"); + if (titles != null && titles.getLength()>0) { + if (titles.item(0).getFirstChild() == null || titles.item(0).getFirstChild().getNodeValue() == null) { + _log.error("[MANUAL-PARSE] Something wrong with the title element, returning ... "); + return toReturn; + } + title = titles.item(0).getFirstChild().getNodeValue(); + MetaSeeker ms = null; + try { + ms = new MetaSeeker(link); + } catch(Exception e) { + _log.error("[MANUAL-PARSE] Something wrong with the meta seeker returning ... "); + return toReturn; + } + //try the metadata, otherwise ask the guesser + description = (ms.getContent("description") != null && ! ms.getContent("description").isEmpty()) ? ms.getContent("description") : createDescriptionFromContent(link); + + ArrayList images = new ArrayList(); + NodeList imgs = document.getElementsByTagName("img"); + int upTo = (imgs.getLength() > 15) ? 15 : imgs.getLength(); + for (int i = 0; i < upTo; i++) { + String imageUrl = imgs.item(i).getAttributes().getNamedItem("src").getNodeValue(); + if (imageUrl.startsWith("/")) + imageUrl = pageUrl.getProtocol()+"://"+pageUrl.getHost()+imageUrl; + images.add(imageUrl); + } + toReturn = new LinkPreview(title, description, link, host, images); + } + return toReturn; + } + /** + * generate the description parsing the content (Best Guess) + * @param link the link to check + * @return the description guessed + */ + private String createDescriptionFromContent(String link) { + StringBean sb = new StringBean(); + sb.setURL(link); + sb.setLinks(false); + String description = sb.getStrings(); + description = ((description.length() > 256) ? description.substring(0, 256)+"..." : description); + return description; + } + /** + * this method handles the non trusted https connections + */ + private void trustAllHTTPSConnections() { + // Create a trust manager that does not validate certificate chains + TrustManager[] trustAllCerts = new TrustManager[]{ + new X509TrustManager() { + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return null; + } + + public void checkClientTrusted( + java.security.cert.X509Certificate[] certs, String authType) { + } + + public void checkServerTrusted( + java.security.cert.X509Certificate[] certs, String authType) { + } + } + }; + try { + SSLContext sc = SSLContext.getInstance("SSL"); + sc.init(null, trustAllCerts, new java.security.SecureRandom()); + HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); + } catch (Exception e) { + System.out.println("Error" + e); + } + } +} diff --git a/src/main/java/org/gcube/portlets/user/shareupdates/server/metaseeker/MetaElement.java b/src/main/java/org/gcube/portlets/user/shareupdates/server/metaseeker/MetaElement.java new file mode 100644 index 0000000..a07dff3 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/shareupdates/server/metaseeker/MetaElement.java @@ -0,0 +1,40 @@ +package org.gcube.portlets.user.shareupdates.server.metaseeker; + +import java.net.URL; + +/** + * Represents OpenGraph enabled meta data for a specific document + * @author Callum Jones + */ +public class MetaElement +{ + private String property; + private String content; + + /** + * Construct the representation of an element + * @param property The property key + * @param content The content or value of this element + */ + public MetaElement(String property, String content) + { + this.property = property; + this.content = content; + } + + /** + * Fetch the content string of the element + */ + public String getContent() + { + return content; + } + + /** + * Fetch the property of the element + */ + public String getProperty() + { + return property; + } +} \ No newline at end of file diff --git a/src/main/java/org/gcube/portlets/user/shareupdates/server/metaseeker/MetaSeeker.java b/src/main/java/org/gcube/portlets/user/shareupdates/server/metaseeker/MetaSeeker.java new file mode 100644 index 0000000..de0818c --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/shareupdates/server/metaseeker/MetaSeeker.java @@ -0,0 +1,211 @@ +package org.gcube.portlets.user.shareupdates.server.metaseeker; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.ArrayList; +import java.util.Hashtable; + +import org.htmlcleaner.HtmlCleaner; +import org.htmlcleaner.TagNode; + +/** + * @author Massimiliano Assante ISTI-CNR + * @version 1.0 Nov 2012 + * + * This class parses the meta elements in the head of an html page and constructs a hashmap with the content attr value associated to the attr name + * e.g. + * + * + * ex. #getContent("description") returns 'my description' + * + */ +public class MetaSeeker +{ + private String pageUrl; + + private Hashtable> metaAttributes; + private String baseType; + private boolean isImported; // determine if the object is a new incarnation or representation of a web page + private boolean hasChanged; // track if object has been changed + + public final static String[] REQUIRED_META = new String[]{"title", "type", "image", "url" }; + + public final static Hashtable BASE_TYPES = new Hashtable(); + static + { + BASE_TYPES.put("activity", new String[] {"activity", "sport"}); + BASE_TYPES.put("business", new String[] {"bar", "company", "cafe", "hotel", "restaurant"}); + BASE_TYPES.put("group", new String[] {"cause", "sports_league", "sports_team"}); + BASE_TYPES.put("organization", new String[] {"band", "government", "non_profit", "school", "university"}); + BASE_TYPES.put("person", new String[] {"actor", "athlete", "author", "director", "musician", "politician", "profile", "public_figure"}); + BASE_TYPES.put("place", new String[] {"city", "country", "landmark", "state_province"}); + BASE_TYPES.put("product", new String[] {"album", "book", "drink", "food", "game", "movie", "product", "song", "tv_show"}); + BASE_TYPES.put("website", new String[] {"blog", "website", "article"}); + } + + /** + * Create an open graph representation for generating your own Open Graph object + */ + public MetaSeeker() { + metaAttributes = new Hashtable>(); + hasChanged = false; + isImported = false; + } + + /** + * Fetch the metas representation from a web site + * @param url The address to the web page to fetch the meta + * @throws java.io.IOException If a network error occurs, the HTML parser will throw an IO Exception + */ + public MetaSeeker(String url) throws java.io.IOException, Exception { + this(); + isImported = true; + // download the (X)HTML content, but only up to the closing head tag. We do not want to waste resources parsing irrelevant content + URL httpURL = new URL(url); + BufferedReader dis = new BufferedReader(new InputStreamReader(httpURL.openStream())); + String inputLine; + StringBuffer headContents = new StringBuffer(); + + // Loop through each line, looking for the closing head element + while ((inputLine = dis.readLine()) != null) { + if (inputLine.contains("")) { + inputLine = inputLine.substring(0, inputLine.indexOf("") + 7); + inputLine = inputLine.concat(""); + headContents.append(inputLine + "\r\n"); + break; + } + headContents.append(inputLine + "\r\n"); + } + + String headContentsStr = headContents.toString(); + HtmlCleaner cleaner = new HtmlCleaner(); + // parse the string HTML + TagNode pageData = cleaner.clean(headContentsStr); + // open only the meta tags + TagNode[] metaData = pageData.getElementsByName("meta", true); + System.out.println("meta length " + metaData.length); + for (TagNode metaElement : metaData) { + String target = null; + System.out.println("meta " + metaElement.toString()); + if (metaElement.hasAttribute("name")) { + target = "name"; + setProperty(metaElement.getAttributeByName(target), metaElement.getAttributeByName("content")); + } + } + pageUrl = httpURL.toExternalForm(); + } + /** + * Get the basic type of the Open graph page as per the specification + * @return Base type as defined by specification, null otherwise + */ + public String getBaseType() + { + return baseType; + } + + /** + * Get a value of a given Open Graph property + * @param property The Open graph property key + * @return Returns the value of the first property defined, null otherwise + */ + public String getContent(String property) + { + if (metaAttributes.containsKey(property) && metaAttributes.get(property).size() > 0) + return metaAttributes.get(property).get(0).getContent(); + else + return null; + } + + /** + * Get all the defined properties of the Open Graph object + * @return An array of all currently defined properties + */ + public MetaElement[] getProperties() + { + ArrayList allElements = new ArrayList(); + for (ArrayList collection : metaAttributes.values()) + allElements.addAll(collection); + + return (MetaElement[]) allElements.toArray(new MetaElement[allElements.size()]); + } + + /** + * Get all the defined properties of the Open Graph object + * @param property The property to focus on + * @return An array of all currently defined properties + */ + public MetaElement[] getProperties(String property) + { + if (metaAttributes.containsKey(property)) + { + ArrayList target = metaAttributes.get(property); + return (MetaElement[]) target.toArray(new MetaElement[target.size()]); + } + else + return null; + } + + /** + * Get the original URL the Open Graph page was obtained from + * @return The address to the Open Graph object page + */ + public String getOriginalUrl() + { + return pageUrl; + } + + + + + /** + * Set the meta property to a specific value + * @param property The meta where XXXX is the property you wish to set + * @param content The value or contents of the property to be set + */ + public void setProperty(String property, String content) + { + + MetaElement element = new MetaElement(property, content); + if (!metaAttributes.containsKey(property)) + metaAttributes.put(property, new ArrayList()); + + metaAttributes.get(property).add(element); + } + + /** + * Removed a defined property + * @param property The og:XXXX where XXXX is the property you wish to remove + */ + public void removeProperty(String property) + { + metaAttributes.remove(property); + } + + /** + * Obtain the underlying HashTable + * @return The underlying structure as a Hashtable + */ + public Hashtable> exposeTable() { + return metaAttributes; + } + + /** + * Test if the Open Graph object was initially a representation of a web page + * @return True if the object is from a web page, false otherwise + */ + public boolean isFromWeb() + { + return isImported; + } + + /** + * Test if the object has been modified by setters/deleters. + * This is only relevant if this object initially represented a web page + * @return True True if the object has been modified, false otherwise + */ + public boolean hasChanged() + { + return hasChanged; + } +} diff --git a/src/main/java/org/gcube/portlets/user/shareupdates/server/opengraph/MetaElement.java b/src/main/java/org/gcube/portlets/user/shareupdates/server/opengraph/MetaElement.java new file mode 100644 index 0000000..c179f02 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/shareupdates/server/opengraph/MetaElement.java @@ -0,0 +1,51 @@ +package org.gcube.portlets.user.shareupdates.server.opengraph; + +import java.net.URL; + +/** + * Represents OpenGraph enabled meta data for a specific document + * @author Callum Jones + */ +public class MetaElement +{ + private OpenGraphNamespace namespace; //either "og" an NS specific + private String property; + private String content; + + /** + * Construct the representation of an element + * @param namespace The namespace the element belongs to + * @param property The property key + * @param content The content or value of this element + */ + public MetaElement(OpenGraphNamespace namespace, String property, String content) + { + this.namespace = namespace; + this.property = property; + this.content = content; + } + + /** + * Fetch the content string of the element + */ + public String getContent() + { + return content; + } + + /** + * Fetch the OpenGraph namespace + */ + public OpenGraphNamespace getNamespace() + { + return namespace; + } + + /** + * Fetch the property of the element + */ + public String getProperty() + { + return property; + } +} \ No newline at end of file diff --git a/src/main/java/org/gcube/portlets/user/shareupdates/server/opengraph/OpenGraph.java b/src/main/java/org/gcube/portlets/user/shareupdates/server/opengraph/OpenGraph.java new file mode 100644 index 0000000..769b9a7 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/shareupdates/server/opengraph/OpenGraph.java @@ -0,0 +1,378 @@ +package org.gcube.portlets.user.shareupdates.server.opengraph; + +import org.htmlcleaner.HtmlCleaner; +import org.htmlcleaner.TagNode; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A Java object representation of an Open Graph enabled webpage. + * A simplified layer over a Hastable. + * + * @author Callum Jones + */ +public class OpenGraph +{ + private String pageUrl; + private ArrayList pageNamespaces; + private Hashtable> metaAttributes; + private String baseType; + private boolean isImported; // determine if the object is a new incarnation or representation of a web page + private boolean hasChanged; // track if object has been changed + + public final static String[] REQUIRED_META = new String[]{"title", "type", "image", "url" }; + + public final static Hashtable BASE_TYPES = new Hashtable(); + static + { + BASE_TYPES.put("activity", new String[] {"activity", "sport"}); + BASE_TYPES.put("business", new String[] {"bar", "company", "cafe", "hotel", "restaurant"}); + BASE_TYPES.put("group", new String[] {"cause", "sports_league", "sports_team"}); + BASE_TYPES.put("organization", new String[] {"band", "government", "non_profit", "school", "university"}); + BASE_TYPES.put("person", new String[] {"actor", "athlete", "author", "director", "musician", "politician", "profile", "public_figure"}); + BASE_TYPES.put("place", new String[] {"city", "country", "landmark", "state_province"}); + BASE_TYPES.put("product", new String[] {"album", "book", "drink", "food", "game", "movie", "product", "song", "tv_show"}); + BASE_TYPES.put("website", new String[] {"blog", "website", "article"}); + } + + /** + * Create an open graph representation for generating your own Open Graph object + */ + public OpenGraph() + { + pageNamespaces = new ArrayList(); + metaAttributes = new Hashtable>(); + hasChanged = false; + isImported = false; + } + + /** + * Fetch the open graph representation from a web site + * @param url The address to the web page to fetch Open Graph data + * @param ignoreSpecErrors Set this option to true if you don't wish to have an exception throw if the page does not conform to the basic 4 attributes + * @throws java.io.IOException If a network error occurs, the HTML parser will throw an IO Exception + * @throws java.lang.Exception A generic exception is throw if the specific page fails to conform to the basic Open Graph standard as define by the constant REQUIRED_META + */ + public OpenGraph(String url, boolean ignoreSpecErrors, URLConnection siteConnection) throws java.io.IOException, Exception { + this(); + isImported = true; + + + // download the (X)HTML content, but only up to the closing head tag. We do not want to waste resources parsing irrelevant content + Charset charset = getConnectionCharset(siteConnection); + BufferedReader dis = new BufferedReader(new InputStreamReader(siteConnection.getInputStream(), charset)); + String inputLine; + StringBuffer headContents = new StringBuffer(); + + // Loop through each line, looking for the closing head element + while ((inputLine = dis.readLine()) != null) + { + if (inputLine.contains("")) + { + inputLine = inputLine.substring(0, inputLine.indexOf("") + 7); + inputLine = inputLine.concat(""); + headContents.append(inputLine + "\r\n"); + break; + } + headContents.append(inputLine + "\r\n"); + } + + String headContentsStr = headContents.toString(); + HtmlCleaner cleaner = new HtmlCleaner(); + // parse the string HTML + TagNode pageData = cleaner.clean(headContentsStr); + + // read in the declared namespaces + boolean hasOGspec = false; + TagNode headElement = pageData.findElementByName("head", true); + if (headElement.hasAttribute("prefix")) + { + String namespaceData = headElement.getAttributeByName("prefix"); + Pattern pattern = Pattern.compile("(([A-Za-z0-9_]+):\\s+(http:\\/\\/ogp.me\\/ns(\\/\\w+)*#))\\s*"); + Matcher matcher = pattern.matcher(namespaceData); + while (matcher.find()) + { + String prefix = matcher.group(2); + String documentURI = matcher.group(3); + pageNamespaces.add(new OpenGraphNamespace(prefix, documentURI)); + if (prefix.equals("og")) + hasOGspec = true; + } + } + + // some pages do not include the new OG spec + // this fixes compatibility + if (!hasOGspec) + pageNamespaces.add(new OpenGraphNamespace("og", "http:// ogp.me/ns#")); + + // open only the meta tags + TagNode[] metaData = pageData.getElementsByName("meta", true); + for (TagNode metaElement : metaData) + { + for (OpenGraphNamespace namespace : pageNamespaces) + { + String target = null; + if (metaElement.hasAttribute("property")) + target = "property"; + else if (metaElement.hasAttribute("name")) + target = "name"; + + if (target != null && metaElement.getAttributeByName(target).startsWith(namespace.getPrefix() + ":")) + { + setProperty(namespace, metaElement.getAttributeByName(target), metaElement.getAttributeByName("content")); + break; + } + } + } + + /** + * Check that page conforms to Open Graph protocol + */ + if (!ignoreSpecErrors) + { + for (String req : REQUIRED_META) + { + if (!metaAttributes.containsKey(req)) + throw new Exception("Does not conform to Open Graph protocol"); + } + } + + /** + * Has conformed, now determine basic sub type. + */ + baseType = null; + String currentType = getContent("type"); + // read the original page url + URL realURL = siteConnection.getURL(); + pageUrl = realURL.toExternalForm(); + } + + /** + * Gets the charset for specified connection. + * Content Type header is parsed to get the charset name. + * + * @param connection the connection. + * @return the Charset object for response charset name; + * if it's not found then the default charset. + */ + private static Charset getConnectionCharset(URLConnection connection) + { + String contentType = connection.getContentType(); + if (contentType != null && contentType.length() > 0) + { + contentType = contentType.toLowerCase(); + String charsetName = extractCharsetName(contentType); + if (charsetName != null && charsetName.length() > 0) + { + try + { + return Charset.forName(charsetName); + } + catch (Exception e) { + // specified charset is not found, + // skip it to return the default one + } + } + } + + // return the default charset + return Charset.defaultCharset(); + } + + /** + * Extract the charset name form the content type string. + * Content type string is received from Content-Type header. + * + * @param contentType the content type string, must be not null. + * @return the found charset name or null if not found. + */ + private static String extractCharsetName(String contentType) + { + // split onto media types + final String[] mediaTypes = contentType.split(":"); + if (mediaTypes.length > 0) + { + // use only the first one, and split it on parameters + final String[] params = mediaTypes[0].split(";"); + + // find the charset parameter and return it's value + for (String each : params) + { + each = each.trim(); + if (each.startsWith("charset=")) + { + // return the charset name + return each.substring(8).trim(); + } + } + } + + return null; + } + + /** + * Get the basic type of the Open graph page as per the specification + * @return Base type as defined by specification, null otherwise + */ + public String getBaseType() + { + return baseType; + } + + /** + * Get a value of a given Open Graph property + * @param property The Open graph property key + * @return Returns the value of the first property defined, null otherwise + */ + public String getContent(String property) + { + if (metaAttributes.containsKey(property) && metaAttributes.get(property).size() > 0) + return metaAttributes.get(property).get(0).getContent(); + else + return null; + } + + /** + * Get all the defined properties of the Open Graph object + * @return An array of all currently defined properties + */ + public MetaElement[] getProperties() + { + ArrayList allElements = new ArrayList(); + for (ArrayList collection : metaAttributes.values()) + allElements.addAll(collection); + + return (MetaElement[]) allElements.toArray(new MetaElement[allElements.size()]); + } + + /** + * Get all the defined properties of the Open Graph object + * @param property The property to focus on + * @return An array of all currently defined properties + */ + public MetaElement[] getProperties(String property) + { + if (metaAttributes.containsKey(property)) + { + ArrayList target = metaAttributes.get(property); + return (MetaElement[]) target.toArray(new MetaElement[target.size()]); + } + else + return null; + } + + /** + * Get the original URL the Open Graph page was obtained from + * @return The address to the Open Graph object page + */ + public String getOriginalUrl() + { + return pageUrl; + } + + + /** + * Get the HTML representation of the Open Graph data. + * @return An array of meta elements as Strings + */ + public String[] toHTML() + { + // allocate the array + ArrayList returnHTML = new ArrayList(); + + int index = 0; // keep track of the index to insert into + for (ArrayList elements : metaAttributes.values()) + { + for (MetaElement element : elements) + returnHTML.add(""); + } + + // return the array + return (String[]) returnHTML.toArray(); + } + + /** + * Get the XHTML representation of the Open Graph data. + * @return An array of meta elements as Strings + */ + public String[] toXHTML() + { + // allocate the array + ArrayList returnHTML = new ArrayList(); + + int index = 0; // keep track of the index to insert into + for (ArrayList elements : metaAttributes.values()) + { + for (MetaElement element : elements) + returnHTML.add(""); + } + + // return the array + return (String[]) returnHTML.toArray(); + } + + /** + * Set the Open Graph property to a specific value + * @param namespace The OpenGraph namespace the content belongs to + * @param property The og:XXXX where XXXX is the property you wish to set + * @param content The value or contents of the property to be set + */ + public void setProperty(OpenGraphNamespace namespace, String property, String content) + { + if (!pageNamespaces.contains(namespace)) + pageNamespaces.add(namespace); + + property = property.replaceAll(namespace.getPrefix() + ":", ""); + MetaElement element = new MetaElement(namespace, property, content); + if (!metaAttributes.containsKey(property)) + metaAttributes.put(property, new ArrayList()); + + metaAttributes.get(property).add(element); + } + + /** + * Removed a defined property + * @param property The og:XXXX where XXXX is the property you wish to remove + */ + public void removeProperty(String property) + { + metaAttributes.remove(property); + } + + /** + * Obtain the underlying HashTable + * @return The underlying structure as a Hashtable + */ + public Hashtable> exposeTable() { + return metaAttributes; + } + + /** + * Test if the Open Graph object was initially a representation of a web page + * @return True if the object is from a web page, false otherwise + */ + public boolean isFromWeb() + { + return isImported; + } + + /** + * Test if the object has been modified by setters/deleters. + * This is only relevant if this object initially represented a web page + * @return True True if the object has been modified, false otherwise + */ + public boolean hasChanged() + { + return hasChanged; + } +} diff --git a/src/main/java/org/gcube/portlets/user/shareupdates/server/opengraph/OpenGraphNamespace.java b/src/main/java/org/gcube/portlets/user/shareupdates/server/opengraph/OpenGraphNamespace.java new file mode 100644 index 0000000..c8e516e --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/shareupdates/server/opengraph/OpenGraphNamespace.java @@ -0,0 +1,38 @@ +package org.gcube.portlets.user.shareupdates.server.opengraph; + +/** + * Represents an OpenGraph namespace + * @author Callum Jones + */ +public class OpenGraphNamespace +{ + private String prefix; + private String schemaURI; + + /** + * Construct a namespace + * @param prefix The OpenGraph assigned namespace prefix such as og or og_appname + * @param schemaURI The URL for the OpenGraph schema + */ + public OpenGraphNamespace(String prefix, String schemaURI) + { + this.prefix = prefix; + this.schemaURI = schemaURI; + } + + /* + * Fetch the prefix used for the namespace + */ + public String getPrefix() + { + return prefix; + } + + /* + * Fetch the address for the schema reference + */ + public String getSchemaURI() + { + return schemaURI; + } +} \ No newline at end of file diff --git a/src/main/java/org/gcube/portlets/user/shareupdates/server/portlet/ShareUpdatesPortlet.java b/src/main/java/org/gcube/portlets/user/shareupdates/server/portlet/ShareUpdatesPortlet.java new file mode 100644 index 0000000..3bb4b2c --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/shareupdates/server/portlet/ShareUpdatesPortlet.java @@ -0,0 +1,32 @@ + +package org.gcube.portlets.user.shareupdates.server.portlet; + +import javax.portlet.GenericPortlet; +import javax.portlet.ActionRequest; +import javax.portlet.RenderRequest; +import javax.portlet.ActionResponse; +import javax.portlet.RenderResponse; +import javax.portlet.PortletException; +import java.io.IOException; +import javax.portlet.PortletRequestDispatcher; + +import org.gcube.portal.custom.scopemanager.scopehelper.ScopeHelper; + +/** + * WfTemplatesPortlet Portlet Class + * + * @author Massimiliano Assante, ISTI-CNR - massimiliano.assante@isti.cnr.it + * @version May 2011 (0.1) + */ + public class ShareUpdatesPortlet 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/ShareUpdates_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/shareupdates/shared/FieldVerifier.java b/src/main/java/org/gcube/portlets/user/shareupdates/shared/FieldVerifier.java new file mode 100644 index 0000000..36239a2 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/shareupdates/shared/FieldVerifier.java @@ -0,0 +1,42 @@ +package org.gcube.portlets.user.shareupdates.shared; + +/** + *

+ * FieldVerifier validates that the name the user enters is valid. + *

+ *

+ * This class is in the shared packing because we use it in both + * the client code and on the server. On the client, we verify that the name is + * valid before sending an RPC request so the user doesn't have to wait for a + * network round trip to get feedback. On the server, we verify that the name is + * correct to ensure that the input is correct regardless of where the RPC + * originates. + *

+ *

+ * When creating a class that is used on both the client and the server, be sure + * that all code is translatable and does not use native JavaScript. Code that + * is note translatable (such as code that interacts with a database or the file + * system) cannot be compiled into client side JavaScript. Code that uses native + * JavaScript (such as Widgets) cannot be run on the server. + *

+ */ +public class FieldVerifier { + + /** + * Verifies that the specified name is valid for our service. + * + * In this example, we only require that the name is at least four + * characters. In your application, you can use more complex checks to ensure + * that usernames, passwords, email addresses, URLs, and other fields have the + * proper syntax. + * + * @param name the name to validate + * @return true if valid, false if invalid + */ + public static boolean isValidName(String name) { + if (name == null) { + return false; + } + return name.length() > 3; + } +} diff --git a/src/main/java/org/gcube/portlets/user/shareupdates/shared/LinkPreview.java b/src/main/java/org/gcube/portlets/user/shareupdates/shared/LinkPreview.java new file mode 100644 index 0000000..5e99d68 --- /dev/null +++ b/src/main/java/org/gcube/portlets/user/shareupdates/shared/LinkPreview.java @@ -0,0 +1,79 @@ +package org.gcube.portlets.user.shareupdates.shared; + +import java.io.Serializable; +import java.util.ArrayList; + +@SuppressWarnings("serial") +public class LinkPreview implements Serializable { + + private String title; + private String description; + private String url; + private String host; + private ArrayList imageUrls; + + + public LinkPreview() { + super(); + } + + public LinkPreview(String title, String description, String url, + String host, ArrayList imageUrls) { + super(); + this.title = title; + this.description = description; + this.url = url; + this.host = host; + this.imageUrls = imageUrls; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + /** + * + * @return + */ + public String getTitle() { + return title; + } + /** + * + * @param title + */ + public void setTitle(String title) { + this.title = title; + } + public String getDescription() { + return description; + } + public void setDescription(String description) { + this.description = description; + } + public String getUrl() { + return url; + } + public void setUrl(String url) { + this.url = url; + } + public ArrayList getImageUrls() { + return imageUrls; + } + public void setImageUrls(ArrayList imageUrls) { + this.imageUrls = imageUrls; + } + + @Override + public String toString() { + return "LinkPreview [title=" + title + ", description=" + description + + ", url=" + url + ", host=" + host + ", imageUrls=" + + imageUrls + "]"; + } + + +} diff --git a/src/main/resources/org/gcube/portlets/user/shareupdates/ShareUpdates.gwt.xml b/src/main/resources/org/gcube/portlets/user/shareupdates/ShareUpdates.gwt.xml new file mode 100644 index 0000000..d3ff540 --- /dev/null +++ b/src/main/resources/org/gcube/portlets/user/shareupdates/ShareUpdates.gwt.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/webapp/ShareUpdates.css b/src/main/webapp/ShareUpdates.css new file mode 100644 index 0000000..c59d99c --- /dev/null +++ b/src/main/webapp/ShareUpdates.css @@ -0,0 +1,174 @@ +.buttonDiv { + text-align: right; + padding-top: 2px; +} +.member-photo { + display: block; + padding: 2px; + border: 1px solid #E6E6E6; +} + +.link-previewer { + width: 600px; + padding-top: 10px; + padding-bottom: 10px; + font-family: 'lucida grande', tahoma, verdana, arial, sans-serif; + background-clip: border-box; + background-color: #FBFBFB; + background-image: none; + border-color: #D7D7D7; +} + +.hide-description { + margin-top: 5px; +} + +.hide-description span label{ + padding-left: 5px; +} + +.linkpreview-bgcolor { + background-color: #FBFBFB; +} + +.link-title { + font-size: 12px; + font-weight: bold; + line-height: 15px; + width: 465px; +} + +.link-url { + color: #666; + font-size: 10px; +} + +.link-desc { + padding-top: 5px; + color: #333; + font-size: 10px; + width: 465px; +} + +.image-loader { + background: #FFF url(images/avatarLoader.gif) no-repeat; +} + +.su-closeImage { + background: url(images/close.png) 0px 0px no-repeat; + height: 15px; + width: 15px; +} + +.su-closeImage:hover { + background: url(images/close.png) 0px -16px no-repeat; + cursor: pointer; + cursor: hand; +} + +.su-closeImage:active { + background: url(images/close.png) 0px -32px no-repeat; +} + +.small-text { + font-family: 'lucida grande', tahoma, verdana, arial, sans-serif; + font-size: 9px; +} + +.small-text-arrow { + font-family: 'lucida grande', tahoma, verdana, arial, sans-serif; + font-size: 9px; + padding-left: 5px; + padding-right: 5px; +} + +.small-text-arrow:hover { + color: blue; + cursor: pointer; + cursor: hand; +} + +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; +} + +.shareContainer { + width: 600px; + background-clip: border-box; + background-image: none; + background-origin: padding-box; + padding-bottom: 3px; +} + +.shareContainer-in { + padding-left: 5px; +} + +.post-message { + height: 54px; + color: #999; + font-family: 'Lucida Grande', Verdana, 'Bitstream Vera Sans', Arial, + sans-serif; + font-size: 13px; + padding: 4px 2px; + width: 99%; + border-color: #999; + border-width: 1px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + letter-spacing: normal; +} + +.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; +} + +.toolsContainer { + padding-top: 3px; + width: 600px; + background-image: none; + border-top-color: #DADADA; + border-top-style: solid; + border-top-width: 1px; +} + +.wizardListbox { + height: 19px; + width: 110px; + background: #F2F2F2 url(images/white-grad.png) repeat-x scroll left top; + border-color: #BBB; + color: #464646; + font-size: 10px; + border-width: 1px; + border-style: solid; + cursor: pointer; + cursor: hand; + padding-bottom: 3px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; +} + +.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; +} \ No newline at end of file diff --git a/src/main/webapp/ShareUpdates.html b/src/main/webapp/ShareUpdates.html new file mode 100644 index 0000000..92c34e6 --- /dev/null +++ b/src/main/webapp/ShareUpdates.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/src/main/webapp/WEB-INF/jsp/ShareUpdates_view.jsp b/src/main/webapp/WEB-INF/jsp/ShareUpdates_view.jsp new file mode 100644 index 0000000..5b6223a --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/ShareUpdates_view.jsp @@ -0,0 +1,19 @@ +<%@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"%> + + +--%> + + + + +
\ No newline at end of file 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..234cd92 --- /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..6b3b338 --- /dev/null +++ b/src/main/webapp/WEB-INF/liferay-plugin-package.properties @@ -0,0 +1,9 @@ +name=ShareUpdates +module-group-id=liferay +module-incremental-version=1 +tags= +short-description= +change-log= +page-url=http://www.liferay.com +author=Liferay, Inc. +licenses=LGPL \ 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..c9f5d1f --- /dev/null +++ b/src/main/webapp/WEB-INF/liferay-portlet.xml @@ -0,0 +1,29 @@ + + + + + + ShareUpdates + false + false + false + /ShareUpdates.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..e9df94b --- /dev/null +++ b/src/main/webapp/WEB-INF/portlet.xml @@ -0,0 +1,30 @@ + + + + + ShareUpdates + ShareUpdates + org.gcube.portlets.user.shareupdates.server.portlet.ShareUpdatesPortlet + + view-jsp + /view.jsp + + 0 + + text/html + + + Share updates + Share + Share Updates + + + 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..6060b8c --- /dev/null +++ b/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,24 @@ + + + + + + shareupdateServlet + org.gcube.portlets.user.shareupdates.server.ShareUpdateServiceImpl + + + + shareupdateServlet + /shareupdates/shareupdateServlet + + + + + ShareUpdates.html + + + diff --git a/src/main/webapp/images/Avatar_default.png b/src/main/webapp/images/Avatar_default.png new file mode 100644 index 0000000..2a6c844 Binary files /dev/null and b/src/main/webapp/images/Avatar_default.png differ diff --git a/src/main/webapp/images/avatarLoader.gif b/src/main/webapp/images/avatarLoader.gif new file mode 100644 index 0000000..4c4d825 Binary files /dev/null and b/src/main/webapp/images/avatarLoader.gif differ diff --git a/src/main/webapp/images/close.png b/src/main/webapp/images/close.png new file mode 100644 index 0000000..978ee49 Binary files /dev/null and b/src/main/webapp/images/close.png differ diff --git a/src/main/webapp/images/member-photo.jpg b/src/main/webapp/images/member-photo.jpg new file mode 100644 index 0000000..9d601ef Binary files /dev/null and b/src/main/webapp/images/member-photo.jpg differ diff --git a/src/main/webapp/images/white-grad.png b/src/main/webapp/images/white-grad.png new file mode 100644 index 0000000..aaf57aa Binary files /dev/null and b/src/main/webapp/images/white-grad.png differ 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/shareupdates/client/GwtTestShareUpdates.java b/src/test/java/org/gcube/portlets/user/shareupdates/client/GwtTestShareUpdates.java new file mode 100644 index 0000000..33b8091 --- /dev/null +++ b/src/test/java/org/gcube/portlets/user/shareupdates/client/GwtTestShareUpdates.java @@ -0,0 +1,39 @@ +package org.gcube.portlets.user.shareupdates.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 GwtTestShareUpdates extends GWTTestCase { + + /** + * Must refer to a valid module that sources this class. + */ + public String getModuleName() { + return "org.gcube.portlets.user.shareupdates.ShareUpdatesJUnit"; + } + + /** + * 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/shareupdates/ShareUpdatesJUnit.gwt.xml b/src/test/resources/org/gcube/portlets/user/shareupdates/ShareUpdatesJUnit.gwt.xml new file mode 100644 index 0000000..9473f23 --- /dev/null +++ b/src/test/resources/org/gcube/portlets/user/shareupdates/ShareUpdatesJUnit.gwt.xml @@ -0,0 +1,6 @@ + + + + + +