diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..36540ab --- /dev/null +++ b/.classpath @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..07a493b --- /dev/null +++ b/.project @@ -0,0 +1,42 @@ + + + social-networking-library-ws + + + + + + org.eclipse.wst.jsdt.core.javascriptValidator + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + org.eclipse.wst.validation.validationbuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jem.workbench.JavaEMFNature + org.eclipse.wst.common.modulecore.ModuleCoreNature + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + org.eclipse.wst.common.project.facet.core.nature + org.eclipse.wst.jsdt.core.jsNature + + diff --git a/.settings/.jsdtscope b/.settings/.jsdtscope new file mode 100644 index 0000000..b72a6a4 --- /dev/null +++ b/.settings/.jsdtscope @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..839d647 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=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..443e085 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.compliance=1.7 +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.7 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +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..99ec679 --- /dev/null +++ b/.settings/org.eclipse.wst.common.component @@ -0,0 +1,25 @@ + + + + + + + + uses + + + uses + + + uses + + + uses + + + uses + + + + + diff --git a/.settings/org.eclipse.wst.common.project.facet.core.prefs.xml b/.settings/org.eclipse.wst.common.project.facet.core.prefs.xml new file mode 100644 index 0000000..cc81385 --- /dev/null +++ b/.settings/org.eclipse.wst.common.project.facet.core.prefs.xml @@ -0,0 +1,7 @@ + + + + + + + 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..af37384 --- /dev/null +++ b/.settings/org.eclipse.wst.common.project.facet.core.xml @@ -0,0 +1,8 @@ + + + + + + + + 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.eclipse.wst.jsdt.ui.superType.name b/.settings/org.eclipse.wst.jsdt.ui.superType.name new file mode 100644 index 0000000..05bd71b --- /dev/null +++ b/.settings/org.eclipse.wst.jsdt.ui.superType.name @@ -0,0 +1 @@ +Window \ No newline at end of file diff --git a/.settings/org.eclipse.wst.validation.prefs b/.settings/org.eclipse.wst.validation.prefs new file mode 100644 index 0000000..04cad8c --- /dev/null +++ b/.settings/org.eclipse.wst.validation.prefs @@ -0,0 +1,2 @@ +disabled=06target +eclipse.preferences.version=1 diff --git a/distro/LICENSE b/distro/LICENSE new file mode 100644 index 0000000..2d9616a --- /dev/null +++ b/distro/LICENSE @@ -0,0 +1 @@ +${gcube.license} \ No newline at end of file diff --git a/distro/README b/distro/README new file mode 100644 index 0000000..fc6be41 --- /dev/null +++ b/distro/README @@ -0,0 +1,62 @@ +The gCube System - ${name} +-------------------------------------------------- + +${description} + + +${gcube.description} + +${gcube.funding} + + +Version +-------------------------------------------------- + +${version} (${buildDate}) + +Please see the file named "changelog.xml" in this directory for the release notes. + + +Authors +-------------------------------------------------- + +* Costantino Perciante (costantino.perciante@isti.cnr.it), Istituto di Scienza e Tecnologie dell'Informazione "A. Faedo" - CNR, Pisa (Italy). + +Maintainers +----------- + +* Costantino Perciante (costantino.perciante@isti.cnr.it), Istituto di Scienza e Tecnologie dell'Informazione "A. Faedo" - CNR, Pisa (Italy). + +Download information +-------------------------------------------------- + +Source code is available from SVN: + ${scm.url} + +Binaries can be downloaded from the gCube website: + ${gcube.website} + + +Installation +-------------------------------------------------- + +Installation documentation is available on-line in the gCube Wiki: + ${gcube.wikiRoot} + +Documentation +-------------------------------------------------- + +Documentation is available on-line in the gCube Wiki: + ${gcube.wikiRoot} + +Support +-------------------------------------------------- + +Bugs and support requests can be reported in the gCube issue tracking tool: + ${gcube.issueTracking} + + +Licensing +-------------------------------------------------- + +This software is licensed under the terms you may find in the file named "LICENSE" in this directory. \ No newline at end of file diff --git a/distro/changelog.xml b/distro/changelog.xml new file mode 100644 index 0000000..222c1f5 --- /dev/null +++ b/distro/changelog.xml @@ -0,0 +1,63 @@ + + + added get-oauth-profile method for returning Google similar + JSON Profile + + + Fix for #10997 + + + Support for tickets #9333, #9332, #9276 + Added support for caches (ehcache) + + + Fixed bug #8685 + + + Moved as service on top of SmartGears + Exploiting Liferay's Web APIs + + + Custom jackson object mapper is used to have snake case + fields'names + + Improved users version 2 resource with more methods + + + Moved to new Authorization Framework + Added method to send Messages + Added new version for the APIs that accept/return JSON + + Token is discovered before every call is executed by a filter + + + + Support for new portal (gCube 4.0.0) + + + Minor bug fixes (look at support ticket #2971) + + + First Release + + diff --git a/distro/descriptor.xml b/distro/descriptor.xml new file mode 100644 index 0000000..b3f5628 --- /dev/null +++ b/distro/descriptor.xml @@ -0,0 +1,31 @@ + + servicearchive + + tar.gz + + / + + + ${distroDirectory} + / + true + + README + LICENSE + changelog.xml + profile.xml + + 755 + true + + + + + target/${build.finalName}.${project.packaging} + /${artifactId} + + + \ No newline at end of file diff --git a/distro/profile.xml b/distro/profile.xml new file mode 100644 index 0000000..4dda0b9 --- /dev/null +++ b/distro/profile.xml @@ -0,0 +1,25 @@ + + + + Library + + ${description} + Portal + ${artifactId} + 1.0.0 + + + ${artifactId} + ${version} + + ${groupId} + ${artifactId} + ${version} + + + ${build.finalName}.war + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..999ba2e --- /dev/null +++ b/pom.xml @@ -0,0 +1,395 @@ + + + 4.0.0 + + maven-parent + org.gcube.tools + 1.0.0 + + + + org.gcube.portal + social-networking-library-ws + war + 2.2.3-SNAPSHOT + social-networking-library-ws + Rest interface for the social networking library. + + 1.8 + 1.5.13 + 2.25.1 + ${project.basedir}/distro + ${project.build.directory}/${project.build.finalName} + distro + UTF-8 + UTF-8 + + + + scm:svn:http://svn.d4science.research-infrastructures.eu/gcube/trunk/portal/${project.artifactId} + scm:https://svn.d4science.research-infrastructures.eu/gcube/trunk/portal/${project.artifactId} + http://svn.d4science.research-infrastructures.eu/gcube/trunk/portal/${project.artifactId} + + + + + + org.gcube.distribution + maven-smartgears-bom + LATEST + pom + import + + + + + + + net.sf.ehcache + ehcache + 2.10.0 + + + org.gcube.common.portal + portal-manager + [2.0.0-SNAPSHOT,) + compile + + + com.sun.mail + javax.mail + 1.5.2 + compile + + + commons-lang + commons-lang + 2.6 + compile + + + com.liferay.portal + portal-service + 6.2.5 + compile + + + com.google.guava + guava + compile + 18.0 + + + org.slf4j + slf4j-api + provided + + + com.netflix.astyanax + astyanax + 2.0.2 + compile + + + jersey-client + com.sun.jersey + + + jersey-bundle + com.sun.jersey + + + jersey-apache-client4 + com.sun.jersey.contribs + + + + + + + + + com.netflix.astyanax + astyanax-thrift + 2.0.2 + compile + + + com.netflix.astyanax + astyanax-cassandra + 2.0.2 + compile + + + com.netflix.astyanax + astyanax-core + 2.0.2 + compile + + + org.slf4j + log4j-over-slf4j + + + + + org.gcube.socialnetworking + social-util-library + [1.0.1-SNAPSHOT,2.0.0-SNAPSHOT) + compile + + + org.gcube.portal + social-networking-library + [1.0.0-SNAPSHOT,) + compile + + + org.gcube.dvos + usermanagement-core + [2.0.0-SNAPSHOT,) + compile + + + org.gcube.resources.discovery + ic-client + provided + + + org.gcube.core + common-encryption + provided + + + org.gcube.common + authorization-client + provided + + + org.gcube.common + home-library + [2.0.0-SNAPSHOT, 3.0.0-SNAPSHOT) + compile + + + org.gcube.common + home-library-jcr + [2.0.0-SNAPSHOT, 3.0.0-SNAPSHOT) + compile + + + junit + junit + 4.8 + test + + + org.gcube.socialnetworking + social-data-search-client + [1.0.0-SNAPSHOT,2.0.0-SNAPSHOT) + compile + + + + + + + + + + + + + + jackson-dataformat-yaml + com.fasterxml.jackson.dataformat + 2.8.6 + + + + jackson-dataformat-smile + com.fasterxml.jackson.dataformat + 2.8.6 + + + + jackson-dataformat-cbor + com.fasterxml.jackson.dataformat + 2.8.6 + + + + + com.ning + compress-lzf + 1.0.3 + compile + + + org.glassfish.jersey.containers + + jersey-container-servlet-core + ${version.jersey} + + + org.glassfish.jersey.media + jersey-media-json-jackson + ${version.jersey} + + + org.glassfish.jersey.media + jersey-media-json-processing + ${version.jersey} + + + org.glassfish.jersey.media + jersey-media-multipart + ${version.jersey} + + + org.glassfish.jersey.media + jersey-media-sse + ${version.jersey} + + + org.glassfish.jersey.ext + jersey-bean-validation + ${version.jersey} + + + + + + + + + + io.swagger + swagger-jersey2-jaxrs + ${version.swagger} + provided + + + jackson-dataformat-yaml + com.fasterxml.jackson.dataformat + + + + + javax.portlet + portlet-api + [2.0.0-SNAPSHOT,) + compile + + + + org.gcube.core + common-smartgears + provided + + + org.gcube.core + common-smartgears-app + compile + + + + + org.gcube.portal + notifications-common-library + [1.3.0-SNAPSHOT,) + compile + + + org.gcube.applicationsupportlayer + aslsocial + [1.0.0-SNAPSHOT,2.0.0-SNAPSHOT) + compile + + + org.apache.httpcomponents + httpclient + 4.3 + compile + + + + + ${name} + + + org.apache.maven.plugins + maven-war-plugin + 2.1.1 + + + compile + + exploded + + + + + ${webappDirectory} + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.7 + 1.7 + + + + + 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/portal/social/networking/caches/CachesManager.java b/src/main/java/org/gcube/portal/social/networking/caches/CachesManager.java new file mode 100644 index 0000000..1df6a4c --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/caches/CachesManager.java @@ -0,0 +1,34 @@ +package org.gcube.portal.social.networking.caches; + +import net.sf.ehcache.CacheManager; +import net.sf.ehcache.Ehcache; + +/** + * Handle caches via Ehcache + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ +public class CachesManager { + + private static CacheManager cacheManager; + public static final CachesManager singleton = new CachesManager(); + + // the following caches are declared within the ehcache.xml (no default is available) + public static final String SOCIAL_NETWORKING_SITES_CACHE = "social_networking_site_cache"; + public static final String USERS_CACHE = "users_cache"; + public static final String GROUPS_CACHE = "groups_cache"; + + private CachesManager(){ + cacheManager = CacheManager.newInstance(); + } + + public static Ehcache getCache(String name){ + return cacheManager.getEhcache(name); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + cacheManager.shutdown(); + } + +} diff --git a/src/main/java/org/gcube/portal/social/networking/caches/GroupsCache.java b/src/main/java/org/gcube/portal/social/networking/caches/GroupsCache.java new file mode 100644 index 0000000..407ca02 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/caches/GroupsCache.java @@ -0,0 +1,54 @@ +package org.gcube.portal.social.networking.caches; + +import net.sf.ehcache.Ehcache; +import net.sf.ehcache.Element; + +import org.gcube.vomanagement.usermanagement.model.GCubeGroup; +import org.slf4j.LoggerFactory; + +public class GroupsCache { + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(GroupsCache.class); + private static GroupsCache singleton = new GroupsCache(); + + /** + * Private constructor: build the cache + * @return + */ + private GroupsCache(){ + + logger.info("Building cache"); + CachesManager.getCache(CachesManager.GROUPS_CACHE); + + } + + /** + * Get the singleton object + */ + public static GroupsCache getSingleton() { + return singleton; + } + + /** + * Retrieve an entry + * @param id + * @return user associated to the user + */ + public GCubeGroup getGroup(long groupId){ + Ehcache groupsCache = CachesManager.getCache(CachesManager.GROUPS_CACHE); + if(groupsCache.get(groupId) != null) + return (GCubeGroup) groupsCache.get(groupId).getObjectValue(); + else + return null; + } + + /** + * Save an entry into the cache + * @param id + * @param user + */ + public void pushEntry(long id, GCubeGroup group){ + Ehcache groupsCache = CachesManager.getCache(CachesManager.GROUPS_CACHE); + groupsCache.put(new Element(id, group)); + } + +} diff --git a/src/main/java/org/gcube/portal/social/networking/caches/SocialNetworkingSiteFinder.java b/src/main/java/org/gcube/portal/social/networking/caches/SocialNetworkingSiteFinder.java new file mode 100644 index 0000000..cfe1928 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/caches/SocialNetworkingSiteFinder.java @@ -0,0 +1,248 @@ +package org.gcube.portal.social.networking.caches; + +import static org.gcube.resources.discovery.icclient.ICFactory.clientFor; +import static org.gcube.resources.discovery.icclient.ICFactory.queryFor; + +import java.io.InputStream; +import java.util.List; +import java.util.Properties; + +import net.sf.ehcache.Ehcache; +import net.sf.ehcache.Element; + +import org.gcube.applicationsupportlayer.social.shared.SocialNetworkingSite; +import org.gcube.common.portal.GCubePortalConstants; +import org.gcube.common.resources.gcore.ServiceEndpoint; +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.portal.social.networking.liferay.ws.GroupManagerWSBuilder; +import org.gcube.resources.discovery.client.api.DiscoveryClient; +import org.gcube.resources.discovery.client.queries.api.SimpleQuery; +import org.gcube.smartgears.ContextProvider; +import org.gcube.smartgears.context.application.ApplicationContext; +import org.gcube.vomanagement.usermanagement.GroupManager; +import org.gcube.vomanagement.usermanagement.model.GCubeGroup; +import org.gcube.vomanagement.usermanagement.model.VirtualGroup; +import org.slf4j.LoggerFactory; + +/** + * When a notification needs to be sent, this class offers utility to discover (starting from the scope) + * the site information needed to build up the SocialNetworkingSite object (which, for instance, contains the + * portal email). + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ +public class SocialNetworkingSiteFinder { + + // Logger + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(SocialNetworkingSiteFinder.class); + + private static final String EMAIL_SENDER_SITE_CUSTOM_FIELD = "Emailsender"; + private static final String CATEGORY = "Portal"; + + // these properties could be overwritten by the ones read from config.properties + private static String PROD_FALLBACK_GATEWAY = "D4Science.org Gateway"; + private static String DEV_FALLBACK_GATEWAY = "gCube Dev4 Snapshot Gateway"; + private static String PREPROD_FALLBACK_GATEWAY = "gCube Preprod (dev) Gateway"; + + /** + * Singleton object + */ + private static SocialNetworkingSiteFinder singleton = new SocialNetworkingSiteFinder(); + + /** + * Build the singleton instance + */ + private SocialNetworkingSiteFinder(){ + + // read fallback properties + try{ + logger.info("Trying to read config.properties"); + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + InputStream input = classLoader.getResourceAsStream("config.properties"); + Properties properties = new Properties(); + properties.load(input); + PROD_FALLBACK_GATEWAY = properties.getProperty("PROD_FALLBACK_GATEWAY"); + DEV_FALLBACK_GATEWAY = properties.getProperty("DEV_FALLBACK_GATEWAY"); + PREPROD_FALLBACK_GATEWAY = properties.getProperty("PREPROD_FALLBACK_GATEWAY"); + }catch(Exception e){ + logger.warn("Failed to read config.properties...", e); + } + + } + + /** + * Retrieve the singleton instance + */ + public static SocialNetworkingSiteFinder getInstance(){ + + return singleton; + + } + + /** + * Retrieve the SocialNetworkingSite given the scope + * @param scope + * @return + */ + public static SocialNetworkingSite getSocialNetworkingSiteFromScope(String scope){ + + Ehcache socialSitesCache = CachesManager.getCache(CachesManager.SOCIAL_NETWORKING_SITES_CACHE); + + if(scope == null || scope.isEmpty()) + throw new IllegalArgumentException("Scope cannot be null/empty"); + + if(socialSitesCache.get(scope) != null) + return (SocialNetworkingSite) socialSitesCache.get(scope).getObjectValue(); + else{ + SocialNetworkingSite site = discoverSite(scope); + if(site != null) + socialSitesCache.put(new Element(scope, site)); + return site; + } + + } + + /** + * Discover the site for this scope + * @param scope + * @return + */ + private static SocialNetworkingSite discoverSite(String scope) { + + try{ + + logger.info("Requested site for scope " + scope); + GroupManager groupManager = GroupManagerWSBuilder.getInstance().getGroupManager(); + long groupId = groupManager.getGroupIdFromInfrastructureScope(scope); + + GCubeGroup matchingGateway = null; + + if(groupManager.isVRE(groupId)){ + + // get the Virtual groups for the groupid related to the scope + + List virtualGroupsOfGroup = groupManager.getVirtualGroups(groupId); + + if(virtualGroupsOfGroup == null || virtualGroupsOfGroup.isEmpty()) + throw new Exception("It seems that the VRE is not linked to any VirtualGroups"); + + // get the gateways + List gateways = groupManager.getGateways(); + + if(gateways == null || gateways.isEmpty()) + throw new Exception("It seems there is no gateway here!"); + + logger.info("Retrieved Gateways are " + gateways); + + // now, retrieve the virtual groups for each gateway and stop when a VG matches with one of the group + // then, it is the gateway of interest + ext_loop: for (GCubeGroup gateway : gateways) { + List gatewayVirtualGroups = groupManager.getVirtualGroups(gateway.getGroupId()); + if(gatewayVirtualGroups != null && !gatewayVirtualGroups.isEmpty()){ + for (VirtualGroup gatewayVirtualGroup : gatewayVirtualGroups) { + if(virtualGroupsOfGroup.contains(gatewayVirtualGroup)){ + logger.info("Matching gateway for scope " + scope + " is " + gateway); + matchingGateway = gateway; + break ext_loop; + } + } + } + } + + }else{ + + List gateways = groupManager.getGateways(); + + // vo and root vo cases are treated separately: in production environment services.d4science.org is used, instead + // in dev next.d4science.org is used TODO better way... + ApplicationContext ctx = ContextProvider.get(); // get this info from SmartGears + String rootContext = "/"+ctx.container().configuration().infrastructure(); + String matchingGatewayName = null; + if(isDevOrPreprod(rootContext)){ + matchingGatewayName = DEV_FALLBACK_GATEWAY; + }else{ + matchingGatewayName = PROD_FALLBACK_GATEWAY; + } + + // find the matching one among the gateways + for (GCubeGroup gateway : gateways) { + if(gateway.getGroupName().equals(matchingGatewayName)){ + matchingGateway = gateway; + break; + } + } + + if(matchingGateway == null && isDevOrPreprod(rootContext)){ + + logger.warn("Checking if it is the preprod environment"); + matchingGatewayName = PREPROD_FALLBACK_GATEWAY; + // find the matching one among the gateways + for (GCubeGroup gateway : gateways) { + if(gateway.getGroupName().equals(matchingGatewayName)){ + matchingGateway = gateway; + break; + } + } + + } + + } + + if(matchingGateway == null){ + logger.warn("There is no gateway for such scope. Returning null"); + return null; + }else{ + String siteName = matchingGateway.getGroupName(); + String emailSender = (String)groupManager.readCustomAttr(matchingGateway.getGroupId(), EMAIL_SENDER_SITE_CUSTOM_FIELD); + String siteLandingPagePath = GCubePortalConstants.PREFIX_GROUP_URL + matchingGateway.getFriendlyURL(); + String siteUrl = discoverHostOfServiceEndpoint(siteName); + SocialNetworkingSite site = new SocialNetworkingSite(siteName, emailSender, siteUrl, siteLandingPagePath); + logger.info("Site is " + site); + return site; + } + + }catch(Exception e){ + logger.error("Failed to determine the SocialNetworkingSite for scope " + scope, e); + } + + return null; + } + + private static boolean isDevOrPreprod(String rootContext) { + return rootContext.equals("/gcube"); + } + + /** + * Retrieve endpoint host from IS for this gateway + * @return the host for the gateway + * @throws Exception + */ + private static String discoverHostOfServiceEndpoint(String gatewayName){ + + String currentScope = ScopeProvider.instance.get(); + ApplicationContext ctx = ContextProvider.get(); // get this info from SmartGears + ScopeProvider.instance.set("/"+ctx.container().configuration().infrastructure()); + String host = null; + try{ + + SimpleQuery query = queryFor(ServiceEndpoint.class); + query.addCondition("$resource/Profile/Name/text() eq '"+ gatewayName +"'"); + query.addCondition("$resource/Profile/Category/text() eq '"+ CATEGORY +"'"); + DiscoveryClient client = clientFor(ServiceEndpoint.class); + List toReturn = client.submit(query); + for (ServiceEndpoint serviceEndpoint : toReturn) { + host = "https://" + serviceEndpoint.profile().runtime().hostedOn(); + logger.info("Gateway host is " + host); + break; + } + + }catch(Exception e){ + logger.error("Error while retrieving host for the gateway " + gatewayName); + }finally{ + ScopeProvider.instance.set(currentScope); + } + + return host; + + } + +} diff --git a/src/main/java/org/gcube/portal/social/networking/caches/UsersCache.java b/src/main/java/org/gcube/portal/social/networking/caches/UsersCache.java new file mode 100644 index 0000000..c399965 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/caches/UsersCache.java @@ -0,0 +1,83 @@ +package org.gcube.portal.social.networking.caches; + +import java.util.List; + +import net.sf.ehcache.Ehcache; +import net.sf.ehcache.Element; + +import org.gcube.portal.social.networking.liferay.ws.GroupManagerWSBuilder; +import org.gcube.portal.social.networking.liferay.ws.UserManagerWSBuilder; +import org.gcube.smartgears.ContextProvider; +import org.gcube.smartgears.context.application.ApplicationContext; +import org.gcube.vomanagement.usermanagement.GroupManager; +import org.gcube.vomanagement.usermanagement.UserManager; +import org.gcube.vomanagement.usermanagement.model.GCubeUser; +import org.slf4j.LoggerFactory; + +/** + * This cache will store GCubeUser of the users of the infrastructure as couples {user-id, user screename} + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ +public class UsersCache{ + + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(UsersCache.class); + private static UsersCache singleton = new UsersCache(); + + /** + * Private constructor: build the cache + * @return + */ + private UsersCache(){ + + // create a thread to build the cache + new Thread(){ + public void run() { + try{ + logger.info("Fetching users and putting them into cache"); + Ehcache usersCache = CachesManager.getCache(CachesManager.USERS_CACHE); + GroupManager groupManager = GroupManagerWSBuilder.getInstance().getGroupManager(); + UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager(); + ApplicationContext ctx = ContextProvider.get(); // get this info from SmartGears + List users = userManager.listUsersByGroup(groupManager.getGroupIdFromInfrastructureScope("/"+ctx.container().configuration().infrastructure())); + for (GCubeUser gCubeUser : users) { + usersCache.put(new Element(gCubeUser.getUserId(), gCubeUser)); + } + }catch(Exception e){ + logger.error("Unable to retrieve user's usernames. Other users will be discovered later on", e); + } + } + }.start(); + + } + + /** + * Get the singleton object + */ + public static UsersCache getSingleton() { + return singleton; + } + + /** + * Retrieve an entry + * @param id + * @return user associated to the user + */ + public GCubeUser getUser(long userId){ + Ehcache usersCache = CachesManager.getCache(CachesManager.USERS_CACHE); + if(usersCache.get(userId) != null) + return (GCubeUser) usersCache.get(userId).getObjectValue(); + else + return null; + } + + /** + * Save an entry into the cache + * @param id + * @param user + */ + public void pushEntry(long id, GCubeUser user){ + Ehcache usersCache = CachesManager.getCache(CachesManager.USERS_CACHE); + usersCache.put(new Element(id, user)); + } + +} diff --git a/src/main/java/org/gcube/portal/social/networking/liferay/ws/GroupManagerWSBuilder.java b/src/main/java/org/gcube/portal/social/networking/liferay/ws/GroupManagerWSBuilder.java new file mode 100644 index 0000000..a83b36e --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/liferay/ws/GroupManagerWSBuilder.java @@ -0,0 +1,50 @@ +package org.gcube.portal.social.networking.liferay.ws; + +import org.gcube.vomanagement.usermanagement.GroupManager; +import org.gcube.vomanagement.usermanagement.impl.ws.LiferayWSGroupManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class that builds a (singleton) GroupManagerWS object. + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ +public class GroupManagerWSBuilder { + + private static final Logger logger = LoggerFactory.getLogger(GroupManagerWSBuilder.class); + private static GroupManagerWSBuilder singleton = new GroupManagerWSBuilder(); + private static GroupManager groupManagerWs; + + private GroupManagerWSBuilder(){ + + logger.info("Building GroupManager please wait"); + + try{ + groupManagerWs = new LiferayWSGroupManager( + LiferayJSONWsCredentials.getSingleton().getUser(), + LiferayJSONWsCredentials.getSingleton().getPassword(), + LiferayJSONWsCredentials.getSingleton().getHost(), + LiferayJSONWsCredentials.getSingleton().getSchema(), + LiferayJSONWsCredentials.getSingleton().getPort()); + }catch(Exception e){ + logger.error("Failed to build the GroupManager. ", e); + return; + } + + logger.info("GroupManager instance built"); + + } + + /** + * Get the user manager instance + * @return + */ + public GroupManager getGroupManager(){ + return groupManagerWs; + } + + public static GroupManagerWSBuilder getInstance(){ + return singleton; + } + +} diff --git a/src/main/java/org/gcube/portal/social/networking/liferay/ws/LiferayJSONWsCredentials.java b/src/main/java/org/gcube/portal/social/networking/liferay/ws/LiferayJSONWsCredentials.java new file mode 100644 index 0000000..6543a30 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/liferay/ws/LiferayJSONWsCredentials.java @@ -0,0 +1,175 @@ +package org.gcube.portal.social.networking.liferay.ws; + +import static org.gcube.resources.discovery.icclient.ICFactory.clientFor; +import static org.gcube.resources.discovery.icclient.ICFactory.queryFor; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.gcube.common.encryption.StringEncrypter; +import org.gcube.common.resources.gcore.ServiceEndpoint; +import org.gcube.common.resources.gcore.ServiceEndpoint.AccessPoint; +import org.gcube.common.resources.gcore.ServiceEndpoint.Property; +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.resources.discovery.client.api.DiscoveryClient; +import org.gcube.resources.discovery.client.queries.api.SimpleQuery; +import org.gcube.smartgears.ContextProvider; +import org.gcube.smartgears.context.application.ApplicationContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + + +/** + * This is a singleton bean instantiated at service start up. It contains + * the credentials of the user who is allowed to perform calls to Liferay. + * Its credentials are looked up from the infrastructure. + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ +public class LiferayJSONWsCredentials { + + private static final Logger logger = LoggerFactory.getLogger(LiferayJSONWsCredentials.class); + + // the singleton obj + private static LiferayJSONWsCredentials singleton = new LiferayJSONWsCredentials(); + + // properties that it contains + private String host; + private String schema; + private String user; + private String password; + private int port; + + // The token of the user used to send notifications/messages when an application token is provided. (will be read from web.xml) + private String notifierUserToken; + + // Service endpoint properties + private final static String RUNTIME_RESOURCE_NAME = "D4Science Infrastructure Gateway"; + private final static String CATEGORY = "Portal"; + + /** + * Private constructor + */ + private LiferayJSONWsCredentials() { + logger.info("Building LiferayJSONWsCredentials object"); + loadNotifierToken(); + lookupPropertiesFromIs(); + logger.info("LiferayJSONWsCredentials object built"); + } + + /** + * Load the token of the notifier user + */ + private void loadNotifierToken() { + try{ + notifierUserToken = ServletContextClass.getNotifierToken(); + logger.debug("Token read " + notifierUserToken.substring(0, 5)+ "*********************"); + }catch(Exception e){ + logger.error("Failed to read notifier user token!", e); + } + } + + /** + * Read the properties from the infrastructure + */ + private void lookupPropertiesFromIs() { + + logger.info("Starting creating LiferayJSONWsCredentials"); + + String oldContext = ScopeProvider.instance.get(); + ApplicationContext ctx = ContextProvider.get(); // get this info from SmartGears + ScopeProvider.instance.set("/"+ctx.container().configuration().infrastructure()); + logger.info("Discovering liferay user's credentials in context " + ctx.container().configuration().infrastructure()); + + try{ + List resources = getConfigurationFromIS(); + if (resources.size() == 0){ + logger.error("There is no Runtime Resource having name " + RUNTIME_RESOURCE_NAME +" and Category " + CATEGORY + " in this scope."); + throw new Exception("There is no Runtime Resource having name " + RUNTIME_RESOURCE_NAME +" and Category " + CATEGORY + " in this scope."); + } + else { + for (ServiceEndpoint res : resources) { + Iterator accessPointIterator = res.profile().accessPoints().iterator(); + while (accessPointIterator.hasNext()) { + ServiceEndpoint.AccessPoint accessPoint = (ServiceEndpoint.AccessPoint) accessPointIterator + .next(); + + if(accessPoint.name().equals("JSONWSUser")){ + // get base path + Map properties = accessPoint.propertyMap(); + host = accessPoint.address(); + schema = (String)properties.get("schema").value(); + user = StringEncrypter.getEncrypter().decrypt((String)properties.get("username").value()); + password = StringEncrypter.getEncrypter().decrypt((String)properties.get("password").value()); + port = Integer.valueOf(properties.get("port").value()); + + // break + break; + } + } + } + } + }catch(Exception e){ + logger.error("Unable to retrieve such service endpoint information!", e); + return; + }finally{ + if(oldContext != null) + ScopeProvider.instance.set(oldContext); + } + + logger.info("Bean built " + toString()); + } + + /** + * Retrieve endpoints information from IS for DB + * @return list of endpoints for ckan database + * @throws Exception + */ + private List getConfigurationFromIS() throws Exception{ + + SimpleQuery query = queryFor(ServiceEndpoint.class); + query.addCondition("$resource/Profile/Name/text() eq '"+ RUNTIME_RESOURCE_NAME +"'"); + query.addCondition("$resource/Profile/Category/text() eq '"+ CATEGORY +"'"); + DiscoveryClient client = clientFor(ServiceEndpoint.class); + List toReturn = client.submit(query); + return toReturn; + + } + + public static LiferayJSONWsCredentials getSingleton() { + return singleton; + } + + public String getHost() { + return host; + } + + public String getSchema() { + return schema; + } + + public String getUser() { + return user; + } + + public String getPassword() { + return password; + } + + public int getPort() { + return port; + } + + public String getNotifierUserToken() { + return notifierUserToken; + } + + @Override + public String toString() { + return "LiferayJSONWsCredentials [host=" + host + ", schema=" + schema + + ", user=" + user + ", password=" + password + ", port=" + + port + ", notifierUserToken=" + notifierUserToken + "]"; + } + +} diff --git a/src/main/java/org/gcube/portal/social/networking/liferay/ws/RoleManagerWSBuilder.java b/src/main/java/org/gcube/portal/social/networking/liferay/ws/RoleManagerWSBuilder.java new file mode 100644 index 0000000..b5b230e --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/liferay/ws/RoleManagerWSBuilder.java @@ -0,0 +1,50 @@ +package org.gcube.portal.social.networking.liferay.ws; + +import org.gcube.vomanagement.usermanagement.RoleManager; +import org.gcube.vomanagement.usermanagement.impl.ws.LiferayWSRoleManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class that builds a (singleton) UserManagerWS object. + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ +public class RoleManagerWSBuilder { + + private static final Logger logger = LoggerFactory.getLogger(UserManagerWSBuilder.class); + private static RoleManagerWSBuilder singleton = new RoleManagerWSBuilder(); + private RoleManager roleManagerWs; + + private RoleManagerWSBuilder(){ + + logger.info("Building UserManager please wait"); + + try{ + roleManagerWs = new LiferayWSRoleManager( + LiferayJSONWsCredentials.getSingleton().getUser(), + LiferayJSONWsCredentials.getSingleton().getPassword(), + LiferayJSONWsCredentials.getSingleton().getHost(), + LiferayJSONWsCredentials.getSingleton().getSchema(), + LiferayJSONWsCredentials.getSingleton().getPort()); + }catch(Exception e){ + logger.error("Failed to build the UserManager. ", e); + return; + } + + logger.info("UserManager instance built"); + + } + + /** + * Get the role manager instance + * @return + */ + public RoleManager getRoleManager(){ + return roleManagerWs; + } + + public static RoleManagerWSBuilder getInstance(){ + return singleton; + } + +} diff --git a/src/main/java/org/gcube/portal/social/networking/liferay/ws/ServletContextClass.java b/src/main/java/org/gcube/portal/social/networking/liferay/ws/ServletContextClass.java new file mode 100644 index 0000000..5dcca04 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/liferay/ws/ServletContextClass.java @@ -0,0 +1,39 @@ +package org.gcube.portal.social.networking.liferay.ws; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +import org.gcube.portal.social.networking.caches.UsersCache; + +/** + * Loaded at start up. This class performs some init - to be done once - operations. + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ +public class ServletContextClass implements ServletContextListener +{ + + private static String notifierToken; + + public void contextInitialized(ServletContextEvent arg0) { + + // get the token and save it + notifierToken = arg0.getServletContext().getInitParameter("NOTIFIER_TOKEN"); + + // start the thread to retrieve infrastructure users (which is, build up the singleton) + UsersCache.getSingleton(); + + } + + @Override + public void contextDestroyed(ServletContextEvent arg0){ + // on shutdown + } + + /** + * Returns the token of the Liferay's User. + * @return + */ + public static String getNotifierToken() { + return notifierToken; + } +} \ No newline at end of file diff --git a/src/main/java/org/gcube/portal/social/networking/liferay/ws/UserManagerWSBuilder.java b/src/main/java/org/gcube/portal/social/networking/liferay/ws/UserManagerWSBuilder.java new file mode 100644 index 0000000..d6016b2 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/liferay/ws/UserManagerWSBuilder.java @@ -0,0 +1,50 @@ +package org.gcube.portal.social.networking.liferay.ws; + +import org.gcube.vomanagement.usermanagement.UserManager; +import org.gcube.vomanagement.usermanagement.impl.ws.LiferayWSUserManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class that builds a (singleton) UserManagerWS object. + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ +public class UserManagerWSBuilder { + + private static final Logger logger = LoggerFactory.getLogger(UserManagerWSBuilder.class); + private static UserManagerWSBuilder singleton = new UserManagerWSBuilder(); + private UserManager userManagerWs; + + private UserManagerWSBuilder(){ + + logger.info("Building UserManager please wait"); + + try{ + userManagerWs = new LiferayWSUserManager( + LiferayJSONWsCredentials.getSingleton().getUser(), + LiferayJSONWsCredentials.getSingleton().getPassword(), + LiferayJSONWsCredentials.getSingleton().getHost(), + LiferayJSONWsCredentials.getSingleton().getSchema(), + LiferayJSONWsCredentials.getSingleton().getPort()); + }catch(Exception e){ + logger.error("Failed to build the UserManager. ", e); + return; + } + + logger.info("UserManager instance built"); + + } + + /** + * Get the user manager instance + * @return + */ + public UserManager getUserManager(){ + return userManagerWs; + } + + public static UserManagerWSBuilder getInstance(){ + return singleton; + } + +} diff --git a/src/main/java/org/gcube/portal/social/networking/swagger/config/Bootstrap.java b/src/main/java/org/gcube/portal/social/networking/swagger/config/Bootstrap.java new file mode 100644 index 0000000..d33e196 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/swagger/config/Bootstrap.java @@ -0,0 +1,31 @@ +package org.gcube.portal.social.networking.swagger.config; + +import io.swagger.jaxrs.config.BeanConfig; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; + +/** + * Configuration servlet for swagger. + * @author Costantino Perciante at ISTI-CNR + */ +@SuppressWarnings("serial") +public class Bootstrap extends HttpServlet{ + + public static final String GCUBE_TOKEN_IN_QUERY_DEF = "gcube-token-query"; + public static final String GCUBE_TOKEN_IN_HEADER_DEF = "gcube-token-header"; + + @Override + public void init(ServletConfig config) throws ServletException { + + super.init(config); + BeanConfig beanConfig = new BeanConfig(); + beanConfig.setResourcePackage("org.gcube.portal.social.networking.ws"); + beanConfig.setPrettyPrint(true); // print pretty json + beanConfig.setHost("socialnetworking1.d4science.org"); + beanConfig.setBasePath("social-networking-library-ws/rest"); + beanConfig.setScan(true); + + } +} \ No newline at end of file diff --git a/src/main/java/org/gcube/portal/social/networking/swagger/config/SwaggerConstants.java b/src/main/java/org/gcube/portal/social/networking/swagger/config/SwaggerConstants.java new file mode 100644 index 0000000..63fc6b6 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/swagger/config/SwaggerConstants.java @@ -0,0 +1,25 @@ +package org.gcube.portal.social.networking.swagger.config; + +/** + * Swagger constants + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ +public class SwaggerConstants { + + public static final String SECURITY_SCHEMA = "gcube-token"; + public static final String HTTP_SCHEMA = "http"; + public static final String HTTPS_SCHEMA = "https"; + + // VALUES + public static final String USERS = "users"; + public static final String TOKENS = "tokens"; + public static final String POSTS = "posts"; + public static final String PEOPLE = "people"; + public static final String NOTIFICATIONS = "notifications"; + public static final String MESSAGES = "messages"; + public static final String HASHTAGS = "hashtags"; + public static final String FULLTEXT = "full-text-search"; + public static final String COMMENTS = "comments"; + public static final String VRES = "vres"; + +} \ No newline at end of file diff --git a/src/main/java/org/gcube/portal/social/networking/ws/SNLApiConfig.java b/src/main/java/org/gcube/portal/social/networking/ws/SNLApiConfig.java new file mode 100644 index 0000000..664c935 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/SNLApiConfig.java @@ -0,0 +1,47 @@ +package org.gcube.portal.social.networking.ws; + +import org.gcube.portal.social.networking.swagger.config.Bootstrap; + +import io.swagger.annotations.ApiKeyAuthDefinition; +import io.swagger.annotations.ApiKeyAuthDefinition.ApiKeyLocation; +import io.swagger.annotations.Contact; +import io.swagger.annotations.Extension; +import io.swagger.annotations.ExtensionProperty; +import io.swagger.annotations.ExternalDocs; +import io.swagger.annotations.Info; +import io.swagger.annotations.SecurityDefinition; +import io.swagger.annotations.SwaggerDefinition; + + +@SwaggerDefinition( + info = @Info( + description = "This is the official documentation of the second version (v. 2.0) of the Social-Networking RESTful interface.", + version = "V 2.0", + title = "Social Networking RESTful Service", + contact = @Contact( + name = "Costantino Perciante", + email ="costantino.perciante@isti.cnr.it" + ), + extensions = { + @Extension(name = "extra-contact", properties = { + @ExtensionProperty(name = "name", value = "Massimiliano Assante"), + @ExtensionProperty(name = "email", value = "massimiliano.assante@isti.cnr.it") + }), + @Extension(name = "development-host", properties = { + @ExtensionProperty(name = "url", value = "https://socialnetworking-d-d4s.d4science.org"), + }) + } + ), + externalDocs=@ExternalDocs(url="https://wiki.gcube-system.org/gcube/Social_Networking_Service", value="Wiki page at gCube"), + host="socialnetworking1.d4science.org", + basePath="social-networking-library-ws/rest", + securityDefinition=@SecurityDefinition( + apiKeyAuthDefinitions={ + @ApiKeyAuthDefinition(key=Bootstrap.GCUBE_TOKEN_IN_QUERY_DEF, description="A token bound to a user (or app identifier) and a context", in=ApiKeyLocation.HEADER, name="gcube-token"), + @ApiKeyAuthDefinition(key=Bootstrap.GCUBE_TOKEN_IN_HEADER_DEF, description="A token bound to a user (or app identifier) and a context", in=ApiKeyLocation.QUERY, name="gcube-token") + }), + schemes = {SwaggerDefinition.Scheme.HTTP, SwaggerDefinition.Scheme.HTTPS} + ) +public interface SNLApiConfig { + +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/WelcomeService.java b/src/main/java/org/gcube/portal/social/networking/ws/WelcomeService.java new file mode 100644 index 0000000..48f7c50 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/WelcomeService.java @@ -0,0 +1,19 @@ +package org.gcube.portal.social.networking.ws; + +import javax.inject.Singleton; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +@Path("/") +@Singleton +public class WelcomeService { + + @GET + @Produces(MediaType.TEXT_HTML) + public Response sayHtmlHello() { + return Response.ok("

The social networking web service is up and running!

").build(); + } +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/ex/ApplicationException.java b/src/main/java/org/gcube/portal/social/networking/ws/ex/ApplicationException.java new file mode 100644 index 0000000..6360754 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/ex/ApplicationException.java @@ -0,0 +1,29 @@ +package org.gcube.portal.social.networking.ws.ex; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +import org.gcube.portal.social.networking.ws.outputs.ResponseBean; +import org.slf4j.LoggerFactory; + + +/** + * Exception thrown when @Valid fail + * @author Costantino Perciante at ISTI-CNR + */ +@Provider +public class ApplicationException implements ExceptionMapper { + + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(ApplicationException.class); + + public Response toResponse(Exception e) { + logger.warn("ApplicationException invoked for exception " + e); + return Response + .status(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()) + .type(MediaType.APPLICATION_JSON) + .entity(new ResponseBean(false, e.getMessage(), null)) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/org/gcube/portal/social/networking/ws/ex/ValidationException.java b/src/main/java/org/gcube/portal/social/networking/ws/ex/ValidationException.java new file mode 100644 index 0000000..1d81a24 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/ex/ValidationException.java @@ -0,0 +1,36 @@ +package org.gcube.portal.social.networking.ws.ex; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +import org.gcube.portal.social.networking.ws.outputs.ResponseBean; +import org.slf4j.LoggerFactory; + +/** + * Exception thrown on fail + * @author Costantino Perciante at ISTI-CNR + */ +@Provider +public class ValidationException implements ExceptionMapper { + + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(ValidationException.class); + @Override + public Response toResponse(javax.validation.ValidationException e) { + final StringBuilder strBuilder = new StringBuilder(); + for (ConstraintViolation cv : ((ConstraintViolationException) e).getConstraintViolations()) { + strBuilder.append(cv.getMessage()); + break; + } + + logger.warn("ValidationException invoked, returning " + strBuilder.toString()); + return Response + .status(Response.Status.BAD_REQUEST.getStatusCode()) + .type(MediaType.APPLICATION_JSON) + .entity(new ResponseBean(false, strBuilder.toString(), null)) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/org/gcube/portal/social/networking/ws/inputs/ApplicationId.java b/src/main/java/org/gcube/portal/social/networking/ws/inputs/ApplicationId.java new file mode 100644 index 0000000..58495d9 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/inputs/ApplicationId.java @@ -0,0 +1,51 @@ +package org.gcube.portal.social.networking.ws.inputs; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import com.fasterxml.jackson.annotation.JsonProperty; + + +/** + * Application id object + * @author Costantino Perciante at ISTI-CNR + */ +@ApiModel(description="An object containing the app_id field", value="Application") +public class ApplicationId { + + @JsonProperty("app_id") + @NotNull(message="app_id cannot be null") + @Size(message="app_id cannot be empty", min=1) + @ApiModelProperty( + example="appX", + name="app_id", + required=true, + value="The application identifier" + ) + private String appId; + + public ApplicationId() { + super(); + } + + public ApplicationId(String appId) { + super(); + this.appId = appId; + } + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + @Override + public String toString() { + return "ApplicationId [appId=" + appId + "]"; + } +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/inputs/JobNotificationBean.java b/src/main/java/org/gcube/portal/social/networking/ws/inputs/JobNotificationBean.java new file mode 100644 index 0000000..39a55ca --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/inputs/JobNotificationBean.java @@ -0,0 +1,164 @@ +package org.gcube.portal.social.networking.ws.inputs; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +import javax.validation.constraints.NotNull; + +import org.gcube.portal.databook.shared.JobStatusType; +import org.gcube.portal.databook.shared.RunningJob; +import org.gcube.portal.social.networking.ws.providers.JobStatusTypeDeserializer; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + + +/** + * The job notification bean class. + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ +@JsonIgnoreProperties(ignoreUnknown = true) // ignore in serialization/deserialization +@ApiModel(description="A job notification object", value="JobNotification") +public class JobNotificationBean { + + @JsonProperty("recipient") + @NotNull(message="recipient cannot be missing") + @ApiModelProperty(value="The recipient of the notification", + required=true, + example="andrea.rossi", + name="recipient") + private String recipient; + + @JsonProperty("job_id") + @ApiModelProperty(value="The job's identifier, i.e. very likely a UUID", + required=true, + example="88055f18-558a-4246-942d-a43012528c92", + name="job_id") + @NotNull(message="job_id cannot be missing") + private String jobId; + + @JsonProperty("job_name") + @ApiModelProperty(value="The job's name", + required=true, + example="SocialDataIndexer Plugin", + name="job_name") + @NotNull(message="job_name cannot be missing") + private String jobName; + + @JsonProperty("service_name") + @ApiModelProperty(value="The name of the service running the job", + required=true, + example="SmartExecutor", + name="service_name") + @NotNull(message="service_name cannot be missing") + private String serviceName; + + @JsonProperty("status") + @ApiModelProperty(value="The status of the job", + required=true, + example="FAILED", + name="status", + allowableValues = "CANCELLED,CANCELLING,DELETED,DELETING,EXECUTING,FAILED,NEW,SUBMITTED,SUCCEEDED,TIMED_OUT,WAITING" + ) + @JsonDeserialize(using=JobStatusTypeDeserializer.class) + @NotNull(message="status cannot be missing") + private JobStatusType status; + + @JsonProperty("status_message") + @ApiModelProperty(value="A message reporting the reason of the current status", + required=false, + example="An error occurred while ...", + name="status_message") + private String statusMessage; + + public JobNotificationBean() { + super(); + } + + /** + * @param recipient + * @param jobId + * @param jobName + * @param serviceName + * @param status + * @param statusMessage + */ + public JobNotificationBean(String recipient, String jobId, String jobName, + String serviceName, JobStatusType status, String statusMessage) { + super(); + this.recipient = recipient; + this.jobId = jobId; + this.jobName = jobName; + this.serviceName = serviceName; + this.status = status; + this.statusMessage = statusMessage; + } + + public String getStatusMessage() { + return statusMessage; + } + + public void setStatusMessage(String statusMessage) { + this.statusMessage = statusMessage; + } + + public String getRecipient() { + return recipient; + } + + public void setRecipient(String recipient) { + this.recipient = recipient; + } + + public String getServiceName() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public String getJobId() { + return jobId; + } + + public void setJobId(String jobId) { + this.jobId = jobId; + } + + public String getJobName() { + return jobName; + } + + public void setJobName(String jobName) { + this.jobName = jobName; + } + + public JobStatusType getStatus() { + return status; + } + + public void setStatus(JobStatusType status) { + this.status = status; + } + + public RunningJob getRunningJob(){ + + return new RunningJob(jobId, jobName, status, statusMessage, serviceName); + + } + + @Override + public String toString() { + return "JobNotificationBean [" + + (recipient != null ? "recipient=" + recipient + ", " : "") + + (jobId != null ? "jobId=" + jobId + ", " : "") + + (jobName != null ? "jobName=" + jobName + ", " : "") + + (serviceName != null ? "serviceName=" + serviceName + ", " + : "") + + (status != null ? "status=" + status + ", " : "") + + (statusMessage != null ? "statusMessage=" + statusMessage + : "") + "]"; + } +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/inputs/MessageInputBean.java b/src/main/java/org/gcube/portal/social/networking/ws/inputs/MessageInputBean.java new file mode 100644 index 0000000..bf2792b --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/inputs/MessageInputBean.java @@ -0,0 +1,114 @@ +package org.gcube.portal.social.networking.ws.inputs; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +import java.io.Serializable; +import java.util.ArrayList; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Generic input bean for methods that allow to write messages + * @author Costantino Perciante at ISTI-CNR + */ +@JsonIgnoreProperties(ignoreUnknown = true) // ignore in serialization/deserialization +@ApiModel(description="A message object", value="Message") +public class MessageInputBean implements Serializable { + + private static final long serialVersionUID = -1317811686036127456L; + + /*@JsonProperty("sender") + @ApiModelProperty( + example="andrea.rossi", + required=false, + hidden=true, // do not show this information + value="The sender of the message. If not specified is the gcube-token's owner") + private String sender;*/ + + @JsonProperty("body") + @NotNull(message="body cannot be missing") + @Size(min=1, message="body cannot be empty") + @ApiModelProperty( + example="This is the body of the mail ...", + required= true, + value="The body of the message") + private String body; + + @JsonProperty("subject") + @NotNull(message="subject cannot be missing") + @Size(min=1, message="subject cannot be empty") + @ApiModelProperty( + example="This is the subject of the mail ...", + required= true, + value="The subject of the message") + private String subject; + + @JsonProperty("recipients") + @NotNull(message="recipients cannot be missing") + @Size(min=1, message="at least a recipient is needed") + @Valid // validate recursively + @ApiModelProperty( + required= true, + value="The recipients of this message. At least one is needed") + private ArrayList recipients; + + public MessageInputBean() { + super(); + } + + public MessageInputBean(String sender, String body, String subject, + ArrayList recipients) { + super(); + //this.sender = sender; + this.body = body; + this.subject = subject; + this.recipients = recipients; + } + + // public String getSender() { + // return sender; + // } + // public void setSender(String sender) { + // this.sender = sender; + // } + public String getBody() { + return body; + } + public void setBody(String body) { + this.body = body; + } + public String getSubject() { + return subject; + } + public void setSubject(String subject) { + this.subject = subject; + } + public ArrayList getRecipients() { + return recipients; + } + public void setRecipients(ArrayList recipients) { + this.recipients = recipients; + } + + @Override + public String toString() { + return "MessageInputBean [" + + (body != null ? "body=" + body + ", " : "") + + (subject != null ? "subject=" + subject + ", " : "") + + (recipients != null ? "recipients=" + recipients : "") + "]"; + } + + // @Override + // public String toString() { + // return "MessageInputBean [sender=" + sender + ", body=" + body + // + ", subject=" + subject + ", recipients=" + recipients + "]"; + // } + + +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/inputs/PostInputBean.java b/src/main/java/org/gcube/portal/social/networking/ws/inputs/PostInputBean.java new file mode 100644 index 0000000..4e9f76c --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/inputs/PostInputBean.java @@ -0,0 +1,171 @@ +package org.gcube.portal.social.networking.ws.inputs; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +import java.io.Serializable; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +/** + * Generic input bean for methods that allow to write posts + * @author Costantino Perciante at ISTI-CNR + */ +@JsonIgnoreProperties(ignoreUnknown = true) // ignore in serialization/deserialization +@ApiModel(description="A post object", value="Post") +public class PostInputBean implements Serializable{ + + private static final long serialVersionUID = 5274608088828232980L; + + @JsonProperty("text") + @NotNull(message="text cannot be null") + @Size(min=1, message="text cannot be empty") + @ApiModelProperty( + example="Dear vre members, ...", + required=true, + value="The text of the post") + private String text; + + @JsonProperty("preview_title") + @ApiModelProperty( + required=false, + value="A preview title for the preview", + name="preview_title") + private String previewtitle; + + @JsonProperty("preview_description") + @ApiModelProperty( + required=false, + value="A preview description for the preview", + name="preview_description") + private String previewdescription; + + @JsonProperty("preview_host") + @ApiModelProperty( + required=false, + value="A preview host for the preview", + name="preview_host") + private String previewhost; + + @JsonProperty("preview_url") + @ApiModelProperty( + required=false, + value="A preview url for the preview", + name="preview_url") + private String previewurl; + + @JsonProperty("image_url") + @ApiModelProperty( + required=false, + value="An image url for the preview", + name="image_url") + private String httpimageurl; + + @JsonProperty("enable_notification") + @ApiModelProperty( + required=false, + value="If true send a notification to the other vre members about this post", + name="enable_notification") + private boolean enablenotification; + + @JsonProperty("params") + @ApiModelProperty( + required=false, + value="Other parameters for the application's posts") + private String params; + + public PostInputBean() { + super(); + } + + public PostInputBean(String text, String previewtitle, + String previewdescription, String previewhost, String previewurl, + String httpimageurl, boolean enablenotification, String params) { + super(); + this.text = text; + this.previewtitle = previewtitle; + this.previewdescription = previewdescription; + this.previewhost = previewhost; + this.previewurl = previewurl; + this.httpimageurl = httpimageurl; + this.enablenotification = enablenotification; + this.params = params; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public String getPreviewtitle() { + return previewtitle; + } + + public void setPreviewtitle(String previewtitle) { + this.previewtitle = previewtitle; + } + + public String getPreviewdescription() { + return previewdescription; + } + + public void setPreviewdescription(String previewdescription) { + this.previewdescription = previewdescription; + } + + public String getPreviewhost() { + return previewhost; + } + + public void setPreviewhost(String previewhost) { + this.previewhost = previewhost; + } + + public String getPreviewurl() { + return previewurl; + } + + public void setPreviewurl(String previewurl) { + this.previewurl = previewurl; + } + + public String getHttpimageurl() { + return httpimageurl; + } + + public void setHttpimageurl(String httpimageurl) { + this.httpimageurl = httpimageurl; + } + + public boolean isEnablenotification() { + return enablenotification; + } + + public void setEnablenotification(boolean enablenotification) { + this.enablenotification = enablenotification; + } + + public String getParams() { + return params; + } + + public void setParams(String params) { + this.params = params; + } + + @Override + public String toString() { + return "PostInputBean [text=" + text + ", previewtitle=" + previewtitle + + ", previewdescription=" + previewdescription + + ", previewhost=" + previewhost + ", previewurl=" + previewurl + + ", httpimageurl=" + httpimageurl + ", enablenotification=" + + enablenotification + ", params=" + params + "]"; + } + +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/inputs/Recipient.java b/src/main/java/org/gcube/portal/social/networking/ws/inputs/Recipient.java new file mode 100644 index 0000000..e375a7b --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/inputs/Recipient.java @@ -0,0 +1,52 @@ +package org.gcube.portal.social.networking.ws.inputs; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +import java.io.Serializable; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Recipient message bean + * @author Costantino Perciante at ISTI-CNR + * (costantino.perciante@isti.cnr.it) + * + */ +@JsonIgnoreProperties(ignoreUnknown = true) // ignore in serialization/deserialization +@ApiModel(description="A recipient object") +public class Recipient implements Serializable{ + + private static final long serialVersionUID = 1071412144446514138L; + + @JsonProperty("id") + @NotNull(message="recipient id must not be null") + @Size(min=1, message="recipient id must not be empty") + @ApiModelProperty( + example="andrea.rossi", + required=true, + value="The id of the recipient") + private String id; + + public Recipient() { + super(); + } + public Recipient(String id) { + super(); + this.id = id; + } + public String getId() { + return id; + } + public void setId(String id) { + this.id = id; + } + @Override + public String toString() { + return "Recipient [id=" + id + "]"; + } +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/methods/v1/Messages.java b/src/main/java/org/gcube/portal/social/networking/ws/methods/v1/Messages.java new file mode 100644 index 0000000..c1099d1 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/methods/v1/Messages.java @@ -0,0 +1,146 @@ +package org.gcube.portal.social.networking.ws.methods.v1; + +import java.util.ArrayList; +import java.util.List; + +import javax.ws.rs.FormParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.gcube.applicationsupportlayer.social.ApplicationNotificationsManager; +import org.gcube.applicationsupportlayer.social.NotificationsManager; +import org.gcube.applicationsupportlayer.social.shared.SocialNetworkingSite; +import org.gcube.applicationsupportlayer.social.shared.SocialNetworkingUser; +import org.gcube.common.authorization.library.provider.AuthorizationProvider; +import org.gcube.common.authorization.library.utils.Caller; +import org.gcube.common.homelibrary.home.HomeLibrary; +import org.gcube.common.homelibrary.home.workspace.Workspace; +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.portal.notifications.bean.GenericItemBean; +import org.gcube.portal.notifications.thread.MessageNotificationsThread; +import org.gcube.portal.social.networking.caches.SocialNetworkingSiteFinder; +import org.gcube.portal.social.networking.liferay.ws.UserManagerWSBuilder; +import org.gcube.portal.social.networking.ws.utils.ErrorMessages; +import org.gcube.vomanagement.usermanagement.UserManager; +import org.gcube.vomanagement.usermanagement.model.GCubeUser; +import org.slf4j.LoggerFactory; + +/** + * Messages services REST interface + * @author Costantino Perciante at ISTI-CNR + * (costantino.perciante@isti.cnr.it) + */ +@Path("/messages") +@Deprecated +public class Messages { + + // Logger + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Messages.class); + + // recipients separator + private static final String RECIPIENTS_ID_SEPARATOR = ","; + + // user manager + private UserManager uManager = null; + + /** + * Build usermanager + * @throws Exception + */ + public Messages() throws Exception { + uManager = UserManagerWSBuilder.getInstance().getUserManager(); + } + + @POST + @Path("writeMessageToUsers/") + @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN}) + /** + * Try to send a message to recipientsIds. The sender is the owner of the gcube-token if not otherwise stated. + * @param body + * @param subject + * @return ok on success, error otherwise + */ + public Response writeMessageToUsers( + @FormParam("sender") String sender, // the optional sender, if missing the sender will be the token's owner. + @FormParam("body") String body, + @FormParam("subject") String subject, + @FormParam("recipients") String recipientsIds) { + + if(body == null || body.isEmpty() || subject == null || subject.isEmpty() || recipientsIds == null || recipientsIds.isEmpty()){ + logger.error("Missing/wrong request parameters"); + return Response.status(Status.BAD_REQUEST).entity(ErrorMessages.MISSING_PARAMETERS).build(); + } + + Caller caller = AuthorizationProvider.instance.get(); + String senderId = caller.getClient().getId(); + String scope = ScopeProvider.instance.get(); + + // check on sender id + if(sender == null || sender.isEmpty()) + logger.info("Sender is going to be the token's owner [" + senderId + "]"); + else{ + logger.info("Sender is going to be " + sender); + senderId = sender; + } + + // get the recipients ids (simple check, trim) + List recipientsListFiltered = new ArrayList(); + String[] splittedRecipientsIds = recipientsIds.split(RECIPIENTS_ID_SEPARATOR); + List recipientsBeans = new ArrayList(); + for (String recipientId : splittedRecipientsIds) { + try{ + String tempId = recipientId.trim(); + if(tempId.isEmpty()) + continue; + GCubeUser userRecipient = uManager.getUserByUsername(tempId); + GenericItemBean beanUser = new GenericItemBean(userRecipient.getUsername(), userRecipient.getUsername(), userRecipient.getFullname(), userRecipient.getUserAvatarURL()); + recipientsBeans.add(beanUser); + recipientsListFiltered.add(tempId); + }catch(Exception e){ + logger.error("Unable to retrieve recipient information for recipient with id " + recipientId, e); + } + } + + if(recipientsListFiltered.isEmpty()){ + logger.error("Missing/wrong request parameters"); + return Response.status(Status.BAD_REQUEST).entity(ErrorMessages.BAD_REQUEST).build(); + } + try{ + + logger.info("Trying to send message with body " + body + " subject " + subject + " to user " + recipientsIds + " from " + senderId); + + // sender info + GCubeUser senderUser = uManager.getUserByUsername(senderId); + Workspace workspace = HomeLibrary.getUserWorkspace(senderId); + + // send message + logger.debug("Sending message to " + recipientsListFiltered); + String messageId = workspace.getWorkspaceMessageManager() + .sendMessageToPortalLogins(subject, body, + new ArrayList(), recipientsListFiltered); + + // send notification + logger.debug("Message sent to " + recipientsIds + ". Sending message notification to: " + recipientsIds); + SocialNetworkingSite site = SocialNetworkingSiteFinder.getSocialNetworkingSiteFromScope(scope); + SocialNetworkingUser user = new SocialNetworkingUser( + senderUser.getUsername(), senderUser.getEmail(), + senderUser.getFullname(), senderUser.getUserAvatarURL()); + + logger.info("SocialNetworkingUser is " + user); + + NotificationsManager nm = new ApplicationNotificationsManager(UserManagerWSBuilder.getInstance().getUserManager(), site, scope, user); + new Thread(new MessageNotificationsThread(recipientsBeans, messageId, subject, body, nm)).start(); + + + }catch(Exception e){ + logger.error("Unable to send message.", e); + return Response.status(Status.INTERNAL_SERVER_ERROR).build(); + } + + return Response.status(Status.CREATED).build(); + } +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/methods/v1/Notifications.java b/src/main/java/org/gcube/portal/social/networking/ws/methods/v1/Notifications.java new file mode 100644 index 0000000..637b206 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/methods/v1/Notifications.java @@ -0,0 +1,67 @@ +package org.gcube.portal.social.networking.ws.methods.v1; + +import java.util.List; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.gcube.common.authorization.library.provider.AuthorizationProvider; +import org.gcube.common.authorization.library.utils.Caller; +import org.gcube.portal.databook.shared.Notification; +import org.gcube.portal.social.networking.ws.utils.CassandraConnection; +import org.gcube.portal.social.networking.ws.utils.ErrorMessages; +import org.slf4j.LoggerFactory; + +/** + * REST interface for the social networking library (notifications). + * @author Costantino Perciante at ISTI-CNR + */ +@Path("/notifications") +@Deprecated +public class Notifications { + + // Logger + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Notifications.class); + + @GET + @Path("getRangeNotificationsByUser/") + @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN}) + /** + * Retrieves notifications from from up to a given quantity for a given user (to whom the token belongs) + * @param from + * @param quantity + * @param token + * @return + */ + public Response getRangeNotificationsByUser( + @QueryParam("from") int from, + @QueryParam("quantity") int quantity) { + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + + logger.info("Retrieving " + quantity + " notifications of user = " + username + " from " + from); + + if(from <= 0 || quantity <= 0){ + logger.error("Missing/wrong request parameters"); + return Response.status(Status.BAD_REQUEST).entity(ErrorMessages.BAD_REQUEST).build(); + } + + List notifications = null; + try{ + notifications = CassandraConnection.getInstance().getDatabookStore().getRangeNotificationsByUser(username, from, quantity); + }catch(Exception e){ + logger.error("Unable to retrieve such notifications.", e); + return Response.status(Status.INTERNAL_SERVER_ERROR).build(); + } + + logger.info("List of notifications retrieved"); + return Response.status(Status.OK).entity(notifications).build(); + } + +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/methods/v1/Posts.java b/src/main/java/org/gcube/portal/social/networking/ws/methods/v1/Posts.java new file mode 100644 index 0000000..7794376 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/methods/v1/Posts.java @@ -0,0 +1,436 @@ +package org.gcube.portal.social.networking.ws.methods.v1; + + +import java.util.Iterator; +import java.util.List; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.gcube.common.authorization.library.provider.AuthorizationProvider; +import org.gcube.common.authorization.library.utils.Caller; +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.portal.databook.shared.ApplicationProfile; +import org.gcube.portal.databook.shared.Feed; +import org.gcube.portal.databook.shared.FeedType; +import org.gcube.portal.social.networking.liferay.ws.GroupManagerWSBuilder; +import org.gcube.portal.social.networking.ws.utils.CassandraConnection; +import org.gcube.portal.social.networking.ws.utils.ErrorMessages; +import org.gcube.portal.social.networking.ws.utils.Filters; +import org.gcube.portal.social.networking.ws.utils.SocialUtils; +import org.gcube.vomanagement.usermanagement.GroupManager; +import org.slf4j.LoggerFactory; + +/** + * REST interface for the social networking library (feeds). + * @author Costantino Perciante at ISTI-CNR + */ +@Path("/posts") +@Deprecated +public class Posts { + + // Logger + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Posts.class); + + @GET + @Path("getRecentPostsByUserAndDate/") + @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN}) + /** + * Retrieves the most recent posts of the token's owner (starting from timeInMillis) + * @param timeInMillis + * @return Response (OK, BAD REQUEST, ...) + */ + public Response getRecentPostsByUserAndDate( + @QueryParam("time") long timeInMillis) { + + if(timeInMillis < 0){ + logger.error("Missing/wrong request parameters"); + return Response.status(Status.BAD_REQUEST).entity(ErrorMessages.MISSING_PARAMETERS).build(); + } + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + String context = ScopeProvider.instance.get(); + List feeds = null; + + try{ + logger.info("Retrieving feeds for user id " + username + " and reference time " + timeInMillis); + feeds = CassandraConnection.getInstance().getDatabookStore().getRecentFeedsByUserAndDate(username, timeInMillis); + Filters.filterFeedsPerContext(feeds, context); + }catch(Exception e){ + logger.error("Unable to retrieve such feeds.", e); + return Response.status(Status.NOT_FOUND).build(); + } + + logger.info("List retrieved"); + return Response.status(Status.OK).entity(feeds).build(); + } + + @GET + @Path("getAllPostsByUser/") + @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN}) + /** + * Retrieves all posts belong to token's owner + * @return Response (OK, BAD REQUEST, ...) + */ + public Response getAllPostsByUser() { + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + String context = ScopeProvider.instance.get(); + + List feeds = null; + try{ + logger.info("Retrieving feeds for user with id " + username); + feeds = CassandraConnection.getInstance().getDatabookStore().getAllFeedsByUser(username); + Filters.filterFeedsPerContext(feeds, context); + }catch(Exception e){ + logger.error("Unable to retrieve such feeds.", e); + return Response.status(Status.NOT_FOUND).build(); + } + + logger.info("List retrieved"); + return Response.status(Status.OK).entity(feeds).build(); + } + + @GET + @Path("getRecentPostsByUser/") + @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN}) + /** + * Retrieves the last quantity posts belonging to token's owner + * @param quantity + * @param token + * @return Response (OK, BAD REQUEST, ...) + */ + public Response getRecentPostsByUser( + @DefaultValue("10") @QueryParam("quantity") int quantity) { + + if(quantity < 0){ + logger.error("Missing/wrong request parameters"); + return Response.status(Status.BAD_REQUEST).entity(ErrorMessages.MISSING_PARAMETERS).build(); + } + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + String context = ScopeProvider.instance.get(); + List feeds = null; + + // if quantity is zero, just return an empty list + if(quantity == 0){ + return Response.status(Status.OK).entity(feeds).build(); + } + + try{ + logger.info("Retrieving last " + quantity + " feeds made by user " + username); + feeds = CassandraConnection.getInstance().getDatabookStore().getRecentFeedsByUser(username, -1); + Filters.filterFeedsPerContext(feeds, context); + feeds = feeds.subList(0, quantity); + }catch(Exception e){ + logger.error("Unable to retrieve such feeds.", e); + return Response.status(Status.NOT_FOUND).build(); + } + + logger.info("List retrieved"); + return Response.status(Status.OK).entity(feeds).build(); + } + + @POST + @Consumes("application/x-www-form-urlencoded") + @Path("writePostUser") + @Produces(MediaType.TEXT_PLAIN) + /** + * Allows a user to write post + * @param feedText + * @param previewTitle + * @param previewDescription + * @param previewHost + * @param previewUrl + * @param httpImageUrl + * @param enableNotification + * @param token + * @return + */ + public Response writePostUser( + @FormParam("text") String feedText, + @FormParam("previewtitle")String previewTitle, + @FormParam("previewdescription") String previewDescription, + @FormParam("previewhost") String previewHost, + @FormParam("previewurl") String previewUrl, + @FormParam("httpimageurl") String httpImageUrl, + @DefaultValue("false") @FormParam("enablenotification") String enableNotification + ){ + + logger.info("Request of writing a feed coming from user"); + + // at least the feedText is necessary to write a feed (the token is needed anyway) + if(feedText == null || feedText.isEmpty()){ + + logger.error("Missing request parameters"); + return Response.status(Status.BAD_REQUEST).entity(ErrorMessages.MISSING_PARAMETERS).build(); + + } + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + String context = ScopeProvider.instance.get(); + + try{ + // check it is a VRE + GroupManager groupManager = GroupManagerWSBuilder.getInstance().getGroupManager(); + long groupId = groupManager.getGroupIdFromInfrastructureScope(context); + boolean isVRE = groupManager.isVRE(groupId); + if(!isVRE){ + logger.error("A post cannot be written into a context that is not a VRE"); + return Response.status(Status.BAD_REQUEST).entity(ErrorMessages.POST_OUTSIDE_VRE).build(); + } + + // convert enablenotification parameter + boolean notifyGroup = enableNotification.equals("true"); + if(notifyGroup) + logger.info("Enable notification for this user post."); + else + logger.info("Disable notification for this user post."); + + + // try to share + logger.debug("Trying to share user feed..."); + Feed res = SocialUtils.shareUserUpdate( + username, + feedText, + ScopeProvider.instance.get(), + previewTitle, + previewDescription, + previewHost, + previewUrl, + httpImageUrl, + notifyGroup + ); + + if(res != null){ + logger.info("Feed correctly written by user " + username); + return Response.status(Status.CREATED).build(); + } + + }catch(Exception e){ + logger.error("Feed not written by user " + username, e); + } + logger.info("Feed not written by user " + username); + return Response.status(Status.INTERNAL_SERVER_ERROR).build(); + + } + + @GET + @Path("getAllPostsByApp/") + @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN}) + /** + * Retrieves all application's posts. + * @param token + * @return + */ + public Response getAllPostsByApp() { + + Caller caller = AuthorizationProvider.instance.get(); + String appId = caller.getClient().getId(); + String context = ScopeProvider.instance.get(); + // check if the token actually matches an application + ApplicationProfile appProfile = SocialUtils.getProfileFromInfrastrucure(appId, ScopeProvider.instance.get()); + if(appProfile == null){ + logger.error("The given token is not belonging to an application!!!"); + return Response.status(Status.FORBIDDEN).entity(ErrorMessages.NOT_APP_TOKEN).build(); + } + List feeds = null; + try{ + logger.info("Retrieving feeds for app with id " + appId); + feeds = CassandraConnection.getInstance().getDatabookStore().getAllFeedsByApp(appId); + Filters.filterFeedsPerContext(feeds, context); + }catch(Exception e){ + logger.error("Unable to retrieve such feeds.", e); + return Response.status(Status.NOT_FOUND).build(); + } + + logger.info("List retrieved"); + return Response.status(Status.OK).entity(feeds).build(); + } + + @POST + @Consumes("application/x-www-form-urlencoded") + @Path("writePostApplication") + @Produces(MediaType.TEXT_PLAIN) + /** + * Allows an application to write a post. + * @param feedText + * @param uriParams + * @param previewTitle + * @param previewDescription + * @param httpImageUrl + * @param enableNotification + * @param token + * @return + */ + public Response writePostApp( + @FormParam("text") String feedText, + @FormParam("params") String uriParams, + @FormParam("previewtitle")String previewTitle, + @FormParam("previewdescription") String previewDescription, + @FormParam("httpimageurl") String httpImageUrl, + @DefaultValue("false") @FormParam("enablenotification") String enableNotification + ){ + + logger.info("Request of writing a feed coming from an application."); + + // at least the feedText is necessary to write a feed (the token is needed anyway) + if(feedText == null || feedText.isEmpty()){ + logger.error("Missing request parameters"); + return Response.status(Status.BAD_REQUEST).entity(ErrorMessages.MISSING_PARAMETERS).build(); + } + + Caller caller = AuthorizationProvider.instance.get(); + String appId = caller.getClient().getId(); + String context = ScopeProvider.instance.get(); + try{ + // check it is a VRE + GroupManager groupManager = GroupManagerWSBuilder.getInstance().getGroupManager(); + long groupId = groupManager.getGroupIdFromInfrastructureScope(context); + boolean isVRE = groupManager.isVRE(groupId); + if(!isVRE){ + logger.error("A post cannot be written into a context that is not a VRE"); + return Response.status(Status.BAD_REQUEST).entity(ErrorMessages.POST_OUTSIDE_VRE).build(); + } + + // check if the token actually matches an application profile + ApplicationProfile appProfile = SocialUtils.getProfileFromInfrastrucure(appId, ScopeProvider.instance.get()); + if(appProfile == null){ + logger.error("The given token doesn't belong to an application!!!"); + return Response.status(Status.FORBIDDEN).entity(ErrorMessages.NOT_APP_TOKEN).build(); + } + + // convert enablenotification parameter + boolean notifyGroup = enableNotification.equals("true"); + if(notifyGroup) + logger.info("Enable notification for this application post."); + else + logger.info("Disable notification for this application post."); + + // write feed + notification if it is the case + Feed written = SocialUtils.shareApplicationUpdate( + feedText, + uriParams, + previewTitle, + previewDescription, + httpImageUrl, + appProfile, + caller, + notifyGroup + ); + if(written != null){ + return Response.status(Status.CREATED).build(); + } + + }catch(Exception e){ + logger.error("Error while writing a post", e); + } + + logger.info("Feed not written by application " + appId); + return Response.status(Status.INTERNAL_SERVER_ERROR).build(); + } + + @GET + @Path("getAllPostsByVRE/") + @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN}) + /** + * Retrieve all posts for this vre + * @param token a user token associated with a given vre(scope) + * @return + */ + public Response getAllPostsByVRE() { + + String scope = ScopeProvider.instance.get(); + + logger.info("Retrieving all posts coming from vre = " + scope); + + List feeds = null; + try{ + feeds = CassandraConnection.getInstance().getDatabookStore().getAllFeedsByVRE(scope); + Iterator it = feeds.iterator(); + + // remove disabled feeds + while (it.hasNext()) { + Feed f = it.next(); + + if(f.getType() == FeedType.DISABLED) + it.remove(); + + } + }catch(Exception e){ + logger.error("Unable to retrieve feeds for vre = " + scope, e); + return Response.status(Status.INTERNAL_SERVER_ERROR).build(); + } + + logger.info("List of feeds of vre = " + scope + " retrieved"); + return Response.status(Status.OK).entity(feeds).build(); + } + + @GET + @Path("getAllLikedPostIdsByUser/") + @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN}) + /** + * Retrieves all liked posts ids relates to token's owner + * @param token + * @return + */ + public Response getAllLikedPostIdsByUser() { + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + + logger.info("Retrieving all liked feeds IDS for user with id " + username); + + List retrievedLikedFeeds = null; + try{ + retrievedLikedFeeds = CassandraConnection.getInstance().getDatabookStore().getAllLikedFeedIdsByUser(username); + }catch(Exception e){ + logger.error("Unable to read such ids of liked feeds.", e); + return Response.status(Status.INTERNAL_SERVER_ERROR).build(); + } + + logger.info("Ids of liked feeds by " + username + " retrieved"); + return Response.status(Status.OK).entity(retrievedLikedFeeds).build(); + } + + @GET + @Path("getAllLikedPostsByUser/") + @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN}) + /** + * Returns all liked feeds of token's owner + * @param limit + * @param token + * @return + */ + public Response getAllLikedPostsByUser(@DefaultValue("10") @QueryParam("limit") int limit) { + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + + logger.info("Retrieving " + limit + " liked feeds for user with id " + username); + + List retrievedLikedFeeds = null; + try{ + retrievedLikedFeeds = CassandraConnection.getInstance().getDatabookStore().getAllLikedFeedsByUser(username, limit); + }catch(Exception e){ + logger.error("Unable to read such liked feeds.", e); + return Response.status(Status.INTERNAL_SERVER_ERROR).build(); + } + + logger.info("Liked feeds by " + username + " retrieved"); + return Response.status(Status.OK).entity(retrievedLikedFeeds).build(); + } +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/methods/v1/Tokens.java b/src/main/java/org/gcube/portal/social/networking/ws/methods/v1/Tokens.java new file mode 100644 index 0000000..008a1ed --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/methods/v1/Tokens.java @@ -0,0 +1,78 @@ +package org.gcube.portal.social.networking.ws.methods.v1; + +import static org.gcube.common.authorization.client.Constants.authorizationService; + +import java.util.ArrayList; +import java.util.List; + +import javax.ws.rs.FormParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.portal.databook.shared.ApplicationProfile; +import org.gcube.portal.social.networking.ws.utils.ErrorMessages; +import org.gcube.portal.social.networking.ws.utils.SocialUtils; +import org.slf4j.LoggerFactory; + + +/** + * REST interface for the social networking library (tokens). + * @author Costantino Perciante at ISTI-CNR + */ +@Path("/tokens") +@Deprecated +public class Tokens { + + private final static String DEFAULT_ROLE = "OrganizationMember"; + + // Logger + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Tokens.class); + + @POST + @Path("generateApplicationToken/") + @Produces(MediaType.TEXT_PLAIN) + /** + * Allows the owner of the token token to generate a new token for the couple (appId, vre) + * where vre is the same associated to the original user token. + * @param appId + * @param token + * @return + */ + public Response generateApplicationToken(@FormParam("appid") String appId){ + + logger.info("Incoming request for app token generation. Appid id is " + appId); + + //user scope + String userScope = ScopeProvider.instance.get(); + + // check if an application profile exists for this appId/scope (the discovery of the service will be made on the root) + ApplicationProfile appProfile = SocialUtils.getProfileFromInfrastrucure(appId, userScope); + + if(appProfile == null){ + logger.error("There is no application profile for this application id and scope!"); + return Response.status(Status.FORBIDDEN).entity(ErrorMessages.NO_APP_PROFILE_FOUND).build(); + } + + logger.info("Generating token for the application with id " + appId); + + List roles = new ArrayList<>(); + roles.add(DEFAULT_ROLE); + + String appToken = null; + try { + // each token is related to an identifier and the context + appToken = authorizationService().generateExternalServiceToken(appId); + } catch (Exception e) { + logger.error("Unable to generate token for app " + appId + " and scope " + userScope); + return Response.status(Status.BAD_REQUEST).entity(ErrorMessages.TOKEN_GENERATION_APP_FAILED).build(); + } + + return Response.status(Status.CREATED).entity(appToken).build(); + } + +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/methods/v1/Users.java b/src/main/java/org/gcube/portal/social/networking/ws/methods/v1/Users.java new file mode 100644 index 0000000..c402760 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/methods/v1/Users.java @@ -0,0 +1,163 @@ +package org.gcube.portal.social.networking.ws.methods.v1; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.gcube.common.authorization.library.provider.AuthorizationProvider; +import org.gcube.common.authorization.library.utils.Caller; +import org.gcube.portal.social.networking.liferay.ws.UserManagerWSBuilder; +import org.gcube.portal.social.networking.ws.utils.ErrorMessages; +import org.gcube.portal.social.networking.ws.utils.TokensUtils; +import org.gcube.vomanagement.usermanagement.model.GCubeUser; +import org.slf4j.LoggerFactory; + +/** + * REST interface for the social networking library (users). + * @author Costantino Perciante at ISTI-CNR + */ +@Path("/users") +@Deprecated +public class Users { + + // Logger + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Users.class); + + private static final String NOT_USER_TOKEN_CONTEXT_USED = "User's information can only be retrieved through a user token (not qualified)"; + + @GET + @Path("readCustomAttr/") + @Produces(MediaType.TEXT_PLAIN) + /** + * A wrapper for the user management library 's readCustomAttr method + * @return Response (OK, BAD REQUEST, ...) + */ + public Response readCustomAttr( + @QueryParam("attribute") String attributeKey + ) { + + if(attributeKey == null || attributeKey.isEmpty()){ + logger.error("Missing/wrong request parameters"); + return Response.status(Status.BAD_REQUEST).entity(ErrorMessages.MISSING_PARAMETERS).build(); + } + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + String toReturn; + if(!TokensUtils.isUserTokenDefault(caller)){ + logger.warn(NOT_USER_TOKEN_CONTEXT_USED); + return Response.status(Status.FORBIDDEN).entity("User's information can only be retrieved through a user token").build(); + }else{ + try{ + GCubeUser user = UserManagerWSBuilder.getInstance().getUserManager().getUserByUsername(username); + toReturn = (String)UserManagerWSBuilder.getInstance().getUserManager().readCustomAttr(user.getUserId(), attributeKey); + }catch(Exception e){ + logger.error("Unable to retrieve attribute for user.", e); + return Response.status(Status.NOT_FOUND).build(); + } + } + logger.info("Attribute " + attributeKey + " retrieved for user " + username); + return Response.status(Status.OK).entity(toReturn).build(); + } + + // @PUT + // @Path("updateCustomAttr") + // @Produces(MediaType.TEXT_PLAIN) + // /** + // * A wrapper for the user management library 's saveCustomAttr method + // * @return + // */ + // public Response updateCustomAttr( + // @FormParam("attribute") String attributeKey, + // @FormParam("value") String newValue + // ){ + // + // if(attributeKey == null || attributeKey.isEmpty() || newValue == null){ + // + // logger.error("Missing/wrong request parameters"); + // return Response.status(Status.BAD_REQUEST).entity(ErrorMessages.missingParameters).build(); + // + // } + // + // Caller caller = AuthorizationProvider.instance.get(); + // String username = caller.getClient().getId(); + // + // try{ + // + // GCubeUser user = userManager.getUserByUsername(username); + // userManager.saveCustomAttr(user.getUserId(), attributeKey, newValue); + // + // }catch(Exception e){ + // + // logger.error("Unable to set attribute for user.", e); + // return Response.status(Status.NOT_MODIFIED).build(); + // + // } + // + // return Response.status(Status.OK).build(); + // + // } + + @GET + @Path("getUserFullname") + @Produces(MediaType.TEXT_PLAIN) + /** + * Retrieve user's fullname + * @return + */ + public Response getUserUsername(){ + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + String toReturn = null; + if(!TokensUtils.isUserTokenDefault(caller)){ + logger.warn(NOT_USER_TOKEN_CONTEXT_USED); + return Response.status(Status.FORBIDDEN).entity("User's information can only be retrieved through a user token").build(); + }else{ + try{ + GCubeUser user = UserManagerWSBuilder.getInstance().getUserManager().getUserByUsername(username); + toReturn = user.getFullname(); + logger.info("Found fullname " + toReturn + " for user " + username); + }catch(Exception e){ + logger.error("Unable to retrieve attribute for user.", e); + return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.toString()).build(); + } + } + return Response.status(Status.OK).entity(toReturn).build(); + } + + @GET + @Path("getUserEmail") + @Produces(MediaType.TEXT_PLAIN) + /** + * Retrieve user's email + * @return + */ + public Response getUserEmail(){ + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + String toReturn = null; + if(!TokensUtils.isUserTokenDefault(caller)){ + logger.warn(NOT_USER_TOKEN_CONTEXT_USED); + return Response.status(Status.FORBIDDEN).entity("User's information can only be retrieved through a user token").build(); + }else{ + try{ + + GCubeUser user = UserManagerWSBuilder.getInstance().getUserManager().getUserByUsername(username); + toReturn = user.getEmail(); + logger.info("Found email " + toReturn + " for user " + username); + + }catch(Exception e){ + + logger.error("Unable to retrieve attribute for user.", e); + return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.toString()).build(); + } + } + return Response.status(Status.OK).entity(toReturn).build(); + } +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/Comments.java b/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/Comments.java new file mode 100644 index 0000000..b21c06b --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/Comments.java @@ -0,0 +1,125 @@ +package org.gcube.portal.social.networking.ws.methods.v2; + + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import io.swagger.annotations.Authorization; + +import java.util.List; + +import javax.validation.ValidationException; +import javax.validation.constraints.Min; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.gcube.common.authorization.library.provider.AuthorizationProvider; +import org.gcube.common.authorization.library.utils.Caller; +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.portal.databook.shared.Comment; +import org.gcube.portal.social.networking.swagger.config.Bootstrap; +import org.gcube.portal.social.networking.swagger.config.SwaggerConstants; +import org.gcube.portal.social.networking.ws.outputs.ResponseBean; +import org.gcube.portal.social.networking.ws.utils.CassandraConnection; +import org.gcube.portal.social.networking.ws.utils.ErrorMessages; +import org.gcube.portal.social.networking.ws.utils.Filters; +import org.slf4j.LoggerFactory; + +/** + * REST interface for the social networking library (comments). + * @author Costantino Perciante at ISTI-CNR + */ +@Path("2/comments") +@Api(value=SwaggerConstants.COMMENTS, authorizations={@Authorization(value = Bootstrap.GCUBE_TOKEN_IN_QUERY_DEF), @Authorization(value = Bootstrap.GCUBE_TOKEN_IN_HEADER_DEF)}) +/** + * Resource endpoint for Comment (version 2) + * @author Costantino Perciante at ISTI-CNR + * (costantino.perciante@isti.cnr.it) + */ +public class Comments { + + // Logger + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Comments.class); + + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("get-comments-user") + @ApiOperation(value = "Retrieve user's comments", response=ResponseBean.class, nickname="get-comments-user", notes="Retrieve the list of comments belonging to the owner of the token in the related context.") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "The list of comments is put into the 'result' field", response = ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + public Response getCommentsUser() { + + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + Caller caller = AuthorizationProvider.instance.get(); + String context = ScopeProvider.instance.get(); + String username = caller.getClient().getId(); + List comments = null; + + try{ + logger.info("Retrieving comments for user id " + username); + comments = CassandraConnection.getInstance().getDatabookStore().getRecentCommentsByUserAndDate(username, 0); + Filters.filterCommentsPerContext(comments, context); + responseBean.setResult(comments); + responseBean.setSuccess(true); + }catch(Exception e){ + logger.error("Unable to retrieve such comments.", e); + responseBean.setMessage(e.getMessage()); + responseBean.setSuccess(false); + status = Status.INTERNAL_SERVER_ERROR; + } + + return Response.status(status).entity(responseBean).build(); + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("get-comments-user-by-time") + @ApiOperation(value = "Retrieve user's comments and filter by date", notes="Retrieve comments of the gcube-token's owner in the context bound to the token itself and filter them by date", + response=ResponseBean.class, nickname="get-comments-user-by-time") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "The list of comments is put into the 'result' field", response = ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + public Response getCommentsUserByTime( + @QueryParam("time") + @Min(value = 0, message="time cannot be negative") + @ApiParam( + allowableValues="range[0, infinity]", + required=true, + name="time", + value="the base time for filtering operation") + long timeInMillis + ) throws ValidationException{ + + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + Caller caller = AuthorizationProvider.instance.get(); + String context = ScopeProvider.instance.get(); + String username = caller.getClient().getId(); + List comments = null; + + try{ + logger.info("Retrieving comments for user id " + username); + comments = CassandraConnection.getInstance().getDatabookStore().getRecentCommentsByUserAndDate(username, timeInMillis); + Filters.filterCommentsPerContext(comments, context); + responseBean.setResult(comments); + responseBean.setMessage(""); + responseBean.setSuccess(true); + }catch(Exception e){ + logger.error("Unable to retrieve such comments.", e); + responseBean.setMessage(e.getMessage()); + responseBean.setSuccess(false); + status = Status.INTERNAL_SERVER_ERROR; + } + + return Response.status(status).entity(responseBean).build(); + } +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/FullTextSearch.java b/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/FullTextSearch.java new file mode 100644 index 0000000..23bb40f --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/FullTextSearch.java @@ -0,0 +1,169 @@ +package org.gcube.portal.social.networking.ws.methods.v2; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import io.swagger.annotations.Authorization; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.ValidationException; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.gcube.common.authorization.library.provider.AuthorizationProvider; +import org.gcube.common.authorization.library.utils.Caller; +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.portal.databook.server.DatabookStore; +import org.gcube.portal.databook.shared.EnhancedFeed; +import org.gcube.portal.databook.shared.Feed; +import org.gcube.portal.social.networking.liferay.ws.GroupManagerWSBuilder; +import org.gcube.portal.social.networking.liferay.ws.UserManagerWSBuilder; +import org.gcube.portal.social.networking.swagger.config.Bootstrap; +import org.gcube.portal.social.networking.swagger.config.SwaggerConstants; +import org.gcube.portal.social.networking.ws.outputs.ResponseBean; +import org.gcube.portal.social.networking.ws.utils.CassandraConnection; +import org.gcube.portal.social.networking.ws.utils.ElasticSearchConnection; +import org.gcube.portal.social.networking.ws.utils.ErrorMessages; +import org.gcube.portal.social.networking.ws.utils.Filters; +import org.gcube.portal.social.networking.ws.utils.TokensUtils; +import org.gcube.vomanagement.usermanagement.GroupManager; +import org.gcube.vomanagement.usermanagement.UserManager; +import org.gcube.vomanagement.usermanagement.model.GCubeGroup; +import org.gcube.vomanagement.usermanagement.model.GCubeUser; +import org.slf4j.LoggerFactory; + +/** + * REST interface for the social networking library (post and its comments). + * @author Costantino Perciante at ISTI-CNR + */ +@Path("2/full-text-search") +@Api(value=SwaggerConstants.FULLTEXT, authorizations={@Authorization(value = Bootstrap.GCUBE_TOKEN_IN_QUERY_DEF), @Authorization(value = Bootstrap.GCUBE_TOKEN_IN_HEADER_DEF)}) +public class FullTextSearch { + + // Logger + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(FullTextSearch.class); + + @GET + @Path("search-by-query") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Retrieve posts/comments that match the given query", + notes="The posts/comments returned belong to the context bound to the gcube-token", + response=ResponseBean.class, nickname="search-by-query") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Successful retrieval of posts/comments that match the query, reported in the 'result' field of the returned object", response = ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + public Response searchByQuery( + @Context HttpServletRequest httpServletRequest, + @QueryParam("query") @NotNull(message="query cannot be null") @Size(min=1, message="query cannot be empty") + @ApiParam(required=true, defaultValue="none", name="query", value="A string to search for") + String query, + + @DefaultValue("0") @QueryParam("from") @Min(value=0, message="from cannot be negative") + @ApiParam(allowableValues="range[0, infinity]", + required=false, name="from", value="the index of the base result to be returned") + int from, + + @DefaultValue("10") @QueryParam("quantity") @Min(value=0, message="quantity cannot be negative") + @ApiParam(allowableValues="range[1, infinity]", + required=false, name="quantity", value="defines how many results are most are to be returned") + int quantity + ) throws ValidationException{ + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + String context = ScopeProvider.instance.get(); + ResponseBean responseBean = new ResponseBean(); + Status status = Status.BAD_REQUEST; + responseBean.setMessage("This method can be only invoked by using a user token."); + + if(!TokensUtils.isUserToken(caller)) + return Response.status(status).entity(responseBean).build(); + + status = Status.OK; + responseBean.setMessage(null); + + GroupManager groupManager = GroupManagerWSBuilder.getInstance().getGroupManager(); + UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager(); + + try{ + // Retrieve user's vres in which we must search + Set vres = new HashSet(); + + // get the group id from the current context + long currentGroupId = groupManager.getGroupIdFromInfrastructureScope(context); + GCubeUser currUser = userManager.getUserByUsername(username); + List userContexts = groupManager.listGroupsByUser(currUser.getUserId()); + + if (groupManager.isRootVO(currentGroupId)) { + for (GCubeGroup group : groupManager.listGroupsByUser(currUser.getUserId())) { + if (groupManager.isVRE(group.getGroupId()) && userContexts.contains(group)) { + vres.add(groupManager.getInfrastructureScope(group.getGroupId())); + } + } + } + else if(groupManager.isVO(currentGroupId)){ + for (GCubeGroup group : groupManager.listGroupsByUser(currUser.getUserId())) { + if (groupManager.isVRE(group.getGroupId()) && group.getParentGroupId() == currentGroupId && userContexts.contains(group)) { + vres.add(groupManager.getInfrastructureScope(group.getGroupId())); + } + } + } + else { + vres.add(context); + } + + // query elastic search + List enhancedFeeds = ElasticSearchConnection.getSingleton().getElasticSearchClient().search(query, vres, from, quantity); + Filters.hideSensitiveInformation(enhancedFeeds, caller.getClient().getId()); + DatabookStore store = CassandraConnection.getInstance().getDatabookStore(); + + // retrieve the ids of liked feeds by the user + List likedFeeds = store.getAllLikedFeedIdsByUser(username); + + // update fields "liked" and "isuser" + for (EnhancedFeed enhancedFeed : enhancedFeeds) { + if(isUsers(enhancedFeed.getFeed(), username)) + enhancedFeed.setUsers(true); + if(likedFeeds.contains(enhancedFeed.getFeed().getKey())) + enhancedFeed.setLiked(true); + } + responseBean.setResult((ArrayList) enhancedFeeds); + responseBean.setSuccess(true); + }catch(Exception e){ + logger.error("Something went wrong while searching", e); + responseBean.setMessage(e.getMessage()); + responseBean.setSuccess(false); + status = Status.INTERNAL_SERVER_ERROR; + + } + return Response.status(status).entity(responseBean).build(); + } + + /** + * tell if a feed belongs to the current user or not + * @param tocheck + * @param username + * @return true if this feed is of the current user + */ + private static final boolean isUsers(Feed tocheck, String username) { + return (tocheck.getEntityId().equals(username)); + } + +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/HashTags.java b/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/HashTags.java new file mode 100644 index 0000000..d8e21d4 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/HashTags.java @@ -0,0 +1,79 @@ +package org.gcube.portal.social.networking.ws.methods.v2; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import io.swagger.annotations.Authorization; + +import java.util.Map; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.gcube.common.authorization.library.provider.AuthorizationProvider; +import org.gcube.common.authorization.library.utils.Caller; +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.portal.databook.server.DatabookStore; +import org.gcube.portal.social.networking.liferay.ws.GroupManagerWSBuilder; +import org.gcube.portal.social.networking.swagger.config.Bootstrap; +import org.gcube.portal.social.networking.swagger.config.SwaggerConstants; +import org.gcube.portal.social.networking.ws.outputs.ResponseBean; +import org.gcube.portal.social.networking.ws.utils.CassandraConnection; +import org.gcube.portal.social.networking.ws.utils.ErrorMessages; +import org.slf4j.LoggerFactory; + +/** + * REST interface for the social networking library (hash tags). + * @author Costantino Perciante at ISTI-CNR + */ +@Path("2/hashtags") +@Api(value=SwaggerConstants.HASHTAGS, authorizations={@Authorization(value = Bootstrap.GCUBE_TOKEN_IN_QUERY_DEF), @Authorization(value = Bootstrap.GCUBE_TOKEN_IN_HEADER_DEF)}) +public class HashTags { + + // Logger + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(HashTags.class); + + @GET + @Path("get-hashtags-and-occurrences/") + @Produces({MediaType.APPLICATION_JSON}) + @ApiOperation(value = "Retrieve hashtags", nickname="get-hashtags-and-occurrences", + notes="Retrieve hashtags in the context bound to the gcube-token", response=ResponseBean.class) + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Hashtags and occurrences retrieved, reported in the 'result' field of the returned object", response = ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + public Response getHashTagsAndOccurrences(){ + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + String context = ScopeProvider.instance.get(); + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + + logger.info("User " + username + " has requested hashtags of context " + context); + + try{ + DatabookStore datastore = CassandraConnection.getInstance().getDatabookStore(); + // TODO handle the case of VO and ROOT + boolean isVRE = GroupManagerWSBuilder.getInstance().getGroupManager().isVRE(GroupManagerWSBuilder.getInstance().getGroupManager().getGroupIdFromInfrastructureScope(context)); + if(isVRE){ + Map map = datastore.getVREHashtagsWithOccurrence(context); + responseBean.setResult(map); + responseBean.setSuccess(true); + }else{ + responseBean.setMessage("Please provide a VRE token. VO and ROOT VO cases are not yet managed."); + responseBean.setResult(false); + } + }catch(Exception e){ + logger.error("Failed to retrieve hashtags", e); + status = Status.INTERNAL_SERVER_ERROR; + } + + return Response.status(status).entity(responseBean).build(); + } + +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/Messages.java b/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/Messages.java new file mode 100644 index 0000000..6883699 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/Messages.java @@ -0,0 +1,233 @@ +package org.gcube.portal.social.networking.ws.methods.v2; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import io.swagger.annotations.Authorization; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; +import javax.validation.ValidationException; +import javax.validation.constraints.NotNull; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.gcube.applicationsupportlayer.social.ApplicationNotificationsManager; +import org.gcube.applicationsupportlayer.social.NotificationsManager; +import org.gcube.applicationsupportlayer.social.shared.SocialNetworkingSite; +import org.gcube.applicationsupportlayer.social.shared.SocialNetworkingUser; +import org.gcube.common.authorization.library.provider.AuthorizationProvider; +import org.gcube.common.authorization.library.provider.SecurityTokenProvider; +import org.gcube.common.authorization.library.utils.Caller; +import org.gcube.common.homelibrary.home.HomeLibrary; +import org.gcube.common.homelibrary.home.workspace.Workspace; +import org.gcube.common.homelibrary.home.workspace.sharing.WorkspaceMessage; +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.portal.notifications.bean.GenericItemBean; +import org.gcube.portal.notifications.thread.MessageNotificationsThread; +import org.gcube.portal.social.networking.caches.SocialNetworkingSiteFinder; +import org.gcube.portal.social.networking.liferay.ws.LiferayJSONWsCredentials; +import org.gcube.portal.social.networking.liferay.ws.UserManagerWSBuilder; +import org.gcube.portal.social.networking.swagger.config.Bootstrap; +import org.gcube.portal.social.networking.swagger.config.SwaggerConstants; +import org.gcube.portal.social.networking.ws.inputs.MessageInputBean; +import org.gcube.portal.social.networking.ws.inputs.Recipient; +import org.gcube.portal.social.networking.ws.outputs.ResponseBean; +import org.gcube.portal.social.networking.ws.utils.ErrorMessages; +import org.gcube.portal.social.networking.ws.utils.TokensUtils; +import org.gcube.vomanagement.usermanagement.exception.UserManagementSystemException; +import org.gcube.vomanagement.usermanagement.exception.UserRetrievalFault; +import org.gcube.vomanagement.usermanagement.model.GCubeUser; +import org.slf4j.LoggerFactory; + +/** + * Messages services REST interface + * @author Costantino Perciante at ISTI-CNR + * (costantino.perciante@isti.cnr.it) + */ +@Path("2/messages") +@Api(value=SwaggerConstants.MESSAGES, authorizations={@Authorization(value = Bootstrap.GCUBE_TOKEN_IN_QUERY_DEF), @Authorization(value = Bootstrap.GCUBE_TOKEN_IN_HEADER_DEF)}) +public class Messages { + + // Logger + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Messages.class); + + @POST + @Path("write-message/") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Write a message to another user", notes="Write a message to another user. The sender is the token's owner by default", + response=ResponseBean.class, nickname="write-message") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Successful write a message. Its id is reported in the 'result' field of the returned object", response = ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + public Response writeMessage( + @NotNull(message="Message to send is missing") + @Valid + @ApiParam(name="input", required=true, allowMultiple=false, value="The message to write") + MessageInputBean input, + @Context HttpServletRequest httpServletRequest) throws ValidationException, UserManagementSystemException, UserRetrievalFault{ + + logger.debug("Incoming message bean is " + input); + + Caller caller = AuthorizationProvider.instance.get(); + + // check if the token belongs to an application token. In this case use J.A.R.V.I.S (the username used to communicate with Liferay) + String senderId = null; + if(!TokensUtils.isUserToken(caller)){ + GCubeUser jarvis = UserManagerWSBuilder.getInstance().getUserManager().getUserByEmail(LiferayJSONWsCredentials.getSingleton().getUser()); + SecurityTokenProvider.instance.set(LiferayJSONWsCredentials.getSingleton().getNotifierUserToken()); + senderId = jarvis.getUsername(); + }else{ + senderId = caller.getClient().getId(); + } + String scope = ScopeProvider.instance.get(); + ResponseBean responseBean = new ResponseBean(); + Status status = Status.CREATED; + String body = input.getBody(); + String subject = input.getSubject(); + List recipientsIds = input.getRecipients(); // "recipients":[{"recipient":"id recipient"}, ......] + logger.info("Sender is going to be the token's owner [" + senderId + "]"); + + // get the recipients ids (simple check, trim) + List recipientsListFiltered = new ArrayList(); + List recipientsBeans = new ArrayList(); + for (Recipient recipientId : recipientsIds) { + try{ + String tempId = recipientId.getId().trim(); + if(tempId.isEmpty()) + continue; + GCubeUser userRecipient = UserManagerWSBuilder.getInstance().getUserManager().getUserByUsername(tempId); + if(userRecipient == null) + userRecipient = UserManagerWSBuilder.getInstance().getUserManager().getUserByEmail(tempId); + if(userRecipient != null){ + GenericItemBean beanUser = new GenericItemBean(userRecipient.getUsername(), userRecipient.getUsername(), userRecipient.getFullname(), userRecipient.getUserAvatarURL()); + recipientsBeans.add(beanUser); + recipientsListFiltered.add(userRecipient.getUsername()); + } + }catch(Exception e){ + logger.error("Unable to retrieve recipient information for recipient with id " + recipientId, e); + } + } + + if(recipientsListFiltered.isEmpty()){ + logger.error("Missing/wrong request parameters"); + status = Status.BAD_REQUEST; + responseBean.setMessage(ErrorMessages.MISSING_PARAMETERS); + return Response.status(status).entity(responseBean).build(); + } + + try{ + + logger.debug("Trying to send message with body " + body + " subject " + subject + " to users " + recipientsIds + " from " + senderId); + + // sender info + GCubeUser senderUser = UserManagerWSBuilder.getInstance().getUserManager().getUserByUsername(senderId); + Workspace workspace = HomeLibrary.getUserWorkspace(senderId); + + logger.debug("Workspace is " + workspace.getRoot()); + + // send message + logger.debug("Sending message to " + recipientsListFiltered); + String messageId = workspace.getWorkspaceMessageManager() + .sendMessageToPortalLogins(subject, body, + new ArrayList(), recipientsListFiltered); + + // send notification + logger.debug("Message sent to " + recipientsIds + ". Sending message notification to: " + recipientsIds); + SocialNetworkingSite site = SocialNetworkingSiteFinder.getSocialNetworkingSiteFromScope(scope); + SocialNetworkingUser user = new SocialNetworkingUser( + senderUser.getUsername(), senderUser.getEmail(), + senderUser.getFullname(), senderUser.getUserAvatarURL()); + + NotificationsManager nm = new ApplicationNotificationsManager(UserManagerWSBuilder.getInstance().getUserManager(), site, ScopeProvider.instance.get(), user); + new Thread(new MessageNotificationsThread(recipientsBeans, messageId, subject, body, nm)).start(); + responseBean.setSuccess(true); + responseBean.setResult(messageId); + + }catch(Exception e){ + logger.error("Unable to send message.", e); + status = Status.INTERNAL_SERVER_ERROR; + responseBean.setMessage(e.toString()); + } + return Response.status(status).entity(responseBean).build(); + } + + @GET + @Path("get-sent-messages") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Retrieve the list of sent messages", notes="Retrieve the list of sent messages. The user is the token's owner by default", + response=ResponseBean.class, nickname="get-sent-messages") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Successful read of the sent messages, reported in the 'result' field of the returned object", response = ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + public Response getSentMessages(){ + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + + logger.info("Request for retrieving sent messages by " + username); + + try{ + Workspace workspace = HomeLibrary.getUserWorkspace(username); + List sentMessages = workspace.getWorkspaceMessageManager().getSentMessages(); + Collections.reverse(sentMessages); + responseBean.setSuccess(true); + logger.debug("Result is " + sentMessages); + responseBean.setResult(sentMessages); + }catch(Exception e){ + logger.error("Unable to retrieve sent messages", e); + responseBean.setMessage(e.getMessage()); + status = Status.INTERNAL_SERVER_ERROR; + } + + return Response.status(status).entity(responseBean).build(); + } + + @GET + @Path("get-received-messages") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Retrieve the list of received messages", notes="Retrieve the list of received messages. The user is the token's owner by default", + response=ResponseBean.class, nickname="get-received-messages") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Successful read of the received messages, reported in the 'result' field of the returned object", response = ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + public Response getReceivedMessages(){ + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + + logger.info("Request for retrieving received messages by " + username); + try{ + Workspace workspace = HomeLibrary.getUserWorkspace(username); + List getMessages = workspace.getWorkspaceMessageManager().getReceivedMessages(); + Collections.reverse(getMessages); + responseBean.setSuccess(true); + responseBean.setResult(getMessages); + + }catch(Exception e){ + logger.error("Unable to retrieve sent messages", e); + responseBean.setMessage(e.getMessage()); + status = Status.INTERNAL_SERVER_ERROR; + } + + return Response.status(status).entity(responseBean).build(); + } +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/Notifications.java b/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/Notifications.java new file mode 100644 index 0000000..4881c12 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/Notifications.java @@ -0,0 +1,154 @@ +package org.gcube.portal.social.networking.ws.methods.v2; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import io.swagger.annotations.Authorization; + +import java.util.Arrays; +import java.util.List; + +import javax.validation.Valid; +import javax.validation.ValidationException; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; +import javax.ws.rs.Consumes; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.gcube.applicationsupportlayer.social.ApplicationNotificationsManager; +import org.gcube.applicationsupportlayer.social.NotificationsManager; +import org.gcube.applicationsupportlayer.social.shared.SocialNetworkingSite; +import org.gcube.applicationsupportlayer.social.shared.SocialNetworkingUser; +import org.gcube.common.authorization.library.provider.AuthorizationProvider; +import org.gcube.common.authorization.library.utils.Caller; +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.portal.databook.shared.Notification; +import org.gcube.portal.notifications.bean.GenericItemBean; +import org.gcube.portal.notifications.thread.JobStatusNotificationThread; +import org.gcube.portal.social.networking.caches.SocialNetworkingSiteFinder; +import org.gcube.portal.social.networking.liferay.ws.LiferayJSONWsCredentials; +import org.gcube.portal.social.networking.liferay.ws.UserManagerWSBuilder; +import org.gcube.portal.social.networking.swagger.config.Bootstrap; +import org.gcube.portal.social.networking.swagger.config.SwaggerConstants; +import org.gcube.portal.social.networking.ws.inputs.JobNotificationBean; +import org.gcube.portal.social.networking.ws.outputs.ResponseBean; +import org.gcube.portal.social.networking.ws.utils.CassandraConnection; +import org.gcube.portal.social.networking.ws.utils.ErrorMessages; +import org.gcube.vomanagement.usermanagement.model.GCubeUser; +import org.slf4j.LoggerFactory; + +/** + * REST interface for the social networking library (notifications). + * @author Costantino Perciante at ISTI-CNR + */ +@Path("2/notifications") +@Api(value=SwaggerConstants.NOTIFICATIONS, authorizations={@Authorization(value = Bootstrap.GCUBE_TOKEN_IN_QUERY_DEF), @Authorization(value = Bootstrap.GCUBE_TOKEN_IN_HEADER_DEF)}) +public class Notifications { + + // Logger + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Notifications.class); + + @GET + @Path("get-range-notifications/") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Retrieve user's notifications", notes="Retrieve notifications of the gcube-token's owner", + response=ResponseBean.class, nickname="get-range-notifications") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Notifications retrieved and reported in the 'result' field of the returned object", response = ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + public Response getRangeNotifications( + @DefaultValue("1") @QueryParam("from") @Min(value=1, message="from must be greater or equal to 1") + @ApiParam(allowableValues="range[0, infinity]", + required=false, allowMultiple=false, value="The base index notification") + int from, + + @DefaultValue("10") @QueryParam("quantity") @Min(value=0, message="quantity must be greater or equal to 0") + @ApiParam(allowableValues="range[1, infinity]", + required=false, allowMultiple=false, value="Retrieve notifications up to this quantity") + int quantity + ) throws ValidationException{ + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + + logger.debug("Retrieving " + quantity + " notifications of user = " + username + " from " + from); + + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + + List notifications = null; + try{ + notifications = CassandraConnection.getInstance().getDatabookStore().getRangeNotificationsByUser(username, from, quantity); + responseBean.setResult(notifications); + responseBean.setSuccess(true); + logger.debug("List of notifications retrieved"); + }catch(Exception e){ + logger.error("Unable to retrieve such notifications.", e); + responseBean.setMessage(e.getMessage()); + responseBean.setSuccess(false); + status = Status.INTERNAL_SERVER_ERROR; + } + + return Response.status(status).entity(responseBean).build(); + } + + @POST + @Path("notify-job-status/") + @ApiOperation(value = "Send a JOB Notification", notes="Send a JOB notification to a given recipient", + response=ResponseBean.class, nickname="notify-job-status") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Notification is sent correctly", response = ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response notifyJobStatus( + @NotNull(message="input is missing") + @ApiParam(name="job", required=true, allowMultiple=false, value="The job bean") + @Valid JobNotificationBean job) throws ValidationException{ + + Caller caller = AuthorizationProvider.instance.get(); + String context = ScopeProvider.instance.get(); + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + + String appQualifier = caller.getClient().getId(); + logger.info("Received request from app " + appQualifier + " to notify job status described by bean " + job); + + try{ + + String recipient = job.getRecipient(); + GCubeUser userRecipient = UserManagerWSBuilder.getInstance().getUserManager().getUserByUsername(recipient); + GenericItemBean recipientBean = new GenericItemBean(userRecipient.getUsername(), userRecipient.getUsername(), userRecipient.getFullname(), userRecipient.getUserAvatarURL()); + + // notifications are sent by using the user allowed to use liferay's json apis + SocialNetworkingSite site = SocialNetworkingSiteFinder.getSocialNetworkingSiteFromScope(context); + GCubeUser senderUser = UserManagerWSBuilder.getInstance().getUserManager().getUserByEmail(LiferayJSONWsCredentials.getSingleton().getUser()); + SocialNetworkingUser user = new SocialNetworkingUser(senderUser.getUsername(), senderUser.getEmail(), senderUser.getFullname(), senderUser.getUserAvatarURL()); + NotificationsManager nm = new ApplicationNotificationsManager(UserManagerWSBuilder.getInstance().getUserManager(), site, context, user); + + new Thread(new JobStatusNotificationThread(job.getRunningJob(), Arrays.asList(recipientBean), nm)).start(); + responseBean.setSuccess(true); + responseBean.setResult("Notification thread started"); + + }catch(Exception e){ + logger.error("Unable to send job notification", e); + responseBean.setSuccess(false); + responseBean.setMessage(e.getMessage()); + status = Status.INTERNAL_SERVER_ERROR; + } + + + return Response.status(status).entity(responseBean).build(); + } + +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/People.java b/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/People.java new file mode 100644 index 0000000..77ab0b8 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/People.java @@ -0,0 +1,102 @@ +package org.gcube.portal.social.networking.ws.methods.v2; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import io.swagger.annotations.Authorization; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.gcube.common.authorization.library.provider.AuthorizationProvider; +import org.gcube.common.authorization.library.utils.Caller; +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.portal.social.networking.liferay.ws.GroupManagerWSBuilder; +import org.gcube.portal.social.networking.liferay.ws.RoleManagerWSBuilder; +import org.gcube.portal.social.networking.liferay.ws.UserManagerWSBuilder; +import org.gcube.portal.social.networking.swagger.config.Bootstrap; +import org.gcube.portal.social.networking.swagger.config.SwaggerConstants; +import org.gcube.portal.social.networking.ws.outputs.ResponseBean; +import org.gcube.portal.social.networking.ws.utils.ErrorMessages; +import org.gcube.portal.social.networking.ws.utils.TokensUtils; +import org.gcube.vomanagement.usermanagement.GroupManager; +import org.gcube.vomanagement.usermanagement.RoleManager; +import org.gcube.vomanagement.usermanagement.UserManager; +import org.gcube.vomanagement.usermanagement.model.GCubeRole; +import org.gcube.vomanagement.usermanagement.model.GCubeUser; +import org.slf4j.LoggerFactory; + + +/** + * REST interface for the social networking library (people). Used by OAUTH 2.0 apps/users. + * @author Costantino Perciante at ISTI-CNR + */ +@Path("2/people") +@Api(value=SwaggerConstants.PEOPLE, authorizations={@Authorization(value = Bootstrap.GCUBE_TOKEN_IN_QUERY_DEF), @Authorization(value = Bootstrap.GCUBE_TOKEN_IN_HEADER_DEF)}) +public class People { + + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(People.class); + + @GET + @Path("profile") + @ApiOperation(value = "Retrieve user's profile", notes="Retrieve the user's profile. The user in this case is the one bound to the token which can be of any kind (qualified, default)", + response=ResponseBean.class, nickname="profile") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Successful retrieval of user's profile, reported in the 'result' field of the returned object", response = ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + @Produces(MediaType.APPLICATION_JSON) + public Response getProfile(){ + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + GCubeUser user = null; + String scope = ScopeProvider.instance.get(); + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + + if(!TokensUtils.isUserToken(caller)){ + status = Status.FORBIDDEN; + responseBean.setMessage("User's information can only be retrieved through a user token"); + logger.warn("Trying to access users method via a token different than 'user-token' is not allowed"); + }else{ + + try{ + UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager(); + RoleManager roleManager = RoleManagerWSBuilder.getInstance().getRoleManager(); + GroupManager groupManager = GroupManagerWSBuilder.getInstance().getGroupManager(); + user = userManager.getUserByUsername(username); + + Map toReturn = new HashMap(); + toReturn.put("username", user.getUsername()); + toReturn.put("avatar", user.getUserAvatarURL()); + toReturn.put("fullname", user.getFullname()); + List roles = roleManager.listRolesByUserAndGroup(user.getUserId(), groupManager.getGroupIdFromInfrastructureScope(scope)); + List rolesNames = new ArrayList(); + for (GCubeRole gCubeRole : roles) { + rolesNames.add(gCubeRole.getRoleName()); + } + toReturn.put("roles", rolesNames); + + responseBean.setResult(toReturn); + responseBean.setSuccess(true); + }catch(Exception e){ + logger.error("Unable to retrieve user's profile", e); + responseBean.setMessage(e.getMessage()); + status = Status.INTERNAL_SERVER_ERROR; + } + } + + return Response.status(status).entity(responseBean).build(); + } + +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/Posts.java b/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/Posts.java new file mode 100644 index 0000000..8cccf06 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/Posts.java @@ -0,0 +1,509 @@ +package org.gcube.portal.social.networking.ws.methods.v2; + + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import io.swagger.annotations.Authorization; + +import java.util.ArrayList; +import java.util.List; + +import javax.validation.Valid; +import javax.validation.ValidationException; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; +import javax.ws.rs.Consumes; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.gcube.common.authorization.library.provider.AuthorizationProvider; +import org.gcube.common.authorization.library.utils.Caller; +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.portal.databook.server.DatabookStore; +import org.gcube.portal.databook.shared.ApplicationProfile; +import org.gcube.portal.databook.shared.Feed; +import org.gcube.portal.social.networking.swagger.config.Bootstrap; +import org.gcube.portal.social.networking.swagger.config.SwaggerConstants; +import org.gcube.portal.social.networking.ws.inputs.PostInputBean; +import org.gcube.portal.social.networking.ws.outputs.ResponseBean; +import org.gcube.portal.social.networking.ws.utils.CassandraConnection; +import org.gcube.portal.social.networking.ws.utils.ErrorMessages; +import org.gcube.portal.social.networking.ws.utils.Filters; +import org.gcube.portal.social.networking.ws.utils.SocialUtils; +import org.slf4j.LoggerFactory; + +/** + * REST interface for the social networking library (feeds). + * @author Costantino Perciante at ISTI-CNR + */ +@Path("2/posts") +@Api(value=SwaggerConstants.POSTS, authorizations={@Authorization(value = Bootstrap.GCUBE_TOKEN_IN_QUERY_DEF), @Authorization(value = Bootstrap.GCUBE_TOKEN_IN_HEADER_DEF)}) +public class Posts { + + // Logger + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Posts.class); + + @GET + @Path("get-posts-user-since/") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Retrieve user's posts", notes="Retrieve posts of the gcube-token's owner, and allow to filter them by time", + response=ResponseBean.class, nickname="get-posts-user-since") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Successful retrieval of posts, reported in the 'result' field of the returned object", response = ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + public Response getRecentPostsByUserAndDate( + @QueryParam("time") @Min(value = 0, message="time cannot be negative") + @ApiParam(allowableValues="range[0, infinity]", name="time", + required=true, allowMultiple=false, value="The reference time since when retrieving posts") + long timeInMillis + ) throws ValidationException{ + + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + Caller caller = AuthorizationProvider.instance.get(); + String context = ScopeProvider.instance.get(); + String username = caller.getClient().getId(); + List feeds = null; + + try{ + logger.info("Retrieving feeds for user id " + username + " and reference time " + timeInMillis); + feeds = CassandraConnection.getInstance().getDatabookStore().getRecentFeedsByUserAndDate(username, timeInMillis); + Filters.filterFeedsPerContext(feeds, context); + Filters.hideSensitiveInformation(feeds, caller.getClient().getId()); + responseBean.setResult(feeds); + responseBean.setMessage(""); + responseBean.setSuccess(true); + + }catch(Exception e){ + logger.error("Unable to retrieve such feeds.", e); + responseBean.setMessage(e.getMessage()); + responseBean.setSuccess(false); + status = Status.INTERNAL_SERVER_ERROR; + } + + return Response.status(status).entity(responseBean).build(); + } + + @GET + @Path("get-posts-user/") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Retrieve all user's posts", notes="Retrieve all posts of the gcube-token's owner", + response=ResponseBean.class, nickname="get-posts-user") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Successful retrieval of posts, reported in the 'result' field of the returned object", response = ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + public Response getAllPostsByUser() { + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + String context = ScopeProvider.instance.get(); + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + List feeds = null; + try{ + logger.debug("Retrieving feeds for user with id " + username); + feeds = CassandraConnection.getInstance().getDatabookStore().getAllFeedsByUser(username); + Filters.filterFeedsPerContext(feeds, context); + Filters.hideSensitiveInformation(feeds, caller.getClient().getId()); + responseBean.setResult(feeds); + responseBean.setMessage(""); + responseBean.setSuccess(true); + }catch(Exception e){ + logger.error("Unable to retrieve such feeds.", e); + responseBean.setMessage(e.getMessage()); + responseBean.setSuccess(false); + status = Status.INTERNAL_SERVER_ERROR; + } + + return Response.status(status).entity(responseBean).build(); + } + + @GET + @Path("get-posts-user-quantity/") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Retrieve a given quantity of latest user's posts", notes="Retrieve a given quantity of latest posts of the gcube-token's owner", + response=ResponseBean.class, nickname="get-posts-user-quantity") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Successful retrieval of posts, reported in the 'result' field of the returned object", response = ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + public Response getQuantityPostsByUser( + @DefaultValue("10") + @QueryParam("quantity") + @Min(value=0, message="quantity cannot be negative") + @ApiParam(allowableValues="range[0, infinity]", name="quantity", + required=false, allowMultiple=false, value="How many posts needs to be retrieved at most") + int quantity) throws ValidationException{ + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + String context = ScopeProvider.instance.get(); + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + + List feeds = new ArrayList(); + + // if quantity is zero, just return an empty list + if(quantity == 0){ + responseBean.setSuccess(true); + return Response.status(status).entity(responseBean).build(); + } + try{ + logger.debug("Retrieving last " + quantity + " feeds made by user " + username); + feeds = CassandraConnection.getInstance().getDatabookStore().getRecentFeedsByUser(username, -1); + Filters.filterFeedsPerContext(feeds, context); + feeds = feeds.subList(0, quantity); + Filters.hideSensitiveInformation(feeds, caller.getClient().getId()); + responseBean.setResult(feeds); + responseBean.setSuccess(true); + }catch(Exception e){ + logger.error("Unable to retrieve such feeds.", e); + responseBean.setMessage(e.getMessage()); + responseBean.setSuccess(false); + status = Status.INTERNAL_SERVER_ERROR; + } + return Response.status(status).entity(responseBean).build(); + } + + @POST + @Path("write-post-user") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Create a new user post", notes="Create a new user post having as owner the gcube-token's owner", + response=ResponseBean.class, nickname="write-post-user") + @ApiResponses(value = { + @ApiResponse(code = 201, message = "Successfull created, the new post is reported in the 'result' field of the returned object", response = ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + public Response writePostUser( + @NotNull(message="Post to write is missing") + @Valid + @ApiParam(name="post", + required=true, allowMultiple=false, value="The post to be written") + PostInputBean post) throws ValidationException{ + + logger.debug("Request of writing a feed coming from user " + post); + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + String context = ScopeProvider.instance.get(); + ResponseBean responseBean = new ResponseBean(); + Status status = Status.CREATED; + + // parse + String feedText = post.getText(); + String previewTitle = post.getPreviewtitle(); + String previewDescription = post.getPreviewdescription(); + String previewHost = post.getPreviewhost(); + String previewUrl = post.getPreviewurl(); + String httpImageUrl = post.getHttpimageurl(); + boolean enableNotification = post.isEnablenotification(); + + // convert enablenotification parameter + if(enableNotification) + logger.info("Enable notification for this user post."); + else + logger.info("Disable notification for this user post."); + + + // try to share + logger.debug("Trying to share user feed..."); + Feed res = SocialUtils.shareUserUpdate( + username, + feedText, + context, + previewTitle, + previewDescription, + previewHost, + previewUrl, + httpImageUrl, + enableNotification + ); + + if(res != null){ + logger.debug("Feed correctly written by user " + username); + responseBean.setResult(res); + responseBean.setSuccess(true); + return Response.status(status).entity(responseBean).build(); + + } + + logger.error("Unable to write post."); + responseBean.setMessage("Unable to write post"); + responseBean.setSuccess(false); + status = Status.INTERNAL_SERVER_ERROR; + return Response.status(status).entity(responseBean).build(); + + } + + @GET + @Path("get-posts-app/") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Retrieve the application's posts", notes="Retrieve the application's posts belonging to the gcube-token's owner (i.e., an application)", + response=ResponseBean.class, nickname="get-posts-app") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Successfull retrieved posts, they are reported in the 'result' field of the returned object", response = ResponseBean.class), + @ApiResponse(code = 403, message = "There is no application profile with such token", response=ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + public Response getAllPostsByApp() { + + Caller caller = AuthorizationProvider.instance.get(); + String appId = caller.getClient().getId(); + String context = ScopeProvider.instance.get(); + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + + // check if the token actually matches an application + ApplicationProfile appProfile = SocialUtils.getProfileFromInfrastrucure(appId, context); + if(appProfile == null){ + + logger.error("The given token is not belonging to an application!!!"); + status = Status.FORBIDDEN; + responseBean.setSuccess(false); + responseBean.setMessage(ErrorMessages.NOT_APP_TOKEN); + return Response.status(status).entity(responseBean).build(); + + } + try{ + + logger.debug("Retrieving feeds for app with id " + appId); + List feeds = CassandraConnection.getInstance().getDatabookStore().getAllFeedsByApp(appId); + Filters.filterFeedsPerContext(feeds, context); + responseBean.setSuccess(true); + responseBean.setResult(feeds); + + }catch(Exception e){ + + logger.error("Unable to retrieve such feeds.", e); + responseBean.setMessage(e.getMessage()); + responseBean.setSuccess(false); + status = Status.INTERNAL_SERVER_ERROR; + + } + + return Response.status(status).entity(responseBean).build(); + } + + @POST + @Path("write-post-app") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Create a new application post", notes="Create a new application post having as owner-application the gcube-token's owner", + response=ResponseBean.class, nickname="write-post-app") + @ApiResponses(value = { + @ApiResponse(code = 201, message = "Successfull created, the new post is reported in the 'result' field of the returned object", response = ResponseBean.class), + @ApiResponse(code = 403, message = "There is no application profile with such token", response=ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + public Response writePostApp( + @NotNull(message="Post to write is null") + @Valid + @ApiParam(name="post", + required=true, allowMultiple=false, value="The post to be written") + PostInputBean post){ + + Caller caller = AuthorizationProvider.instance.get(); + String appId = caller.getClient().getId(); + String context = ScopeProvider.instance.get(); + ResponseBean responseBean = new ResponseBean(); + Status status = Status.CREATED; + + logger.debug("Request of writing a feed coming from an application."); + + // check if the token actually matches an application profile + ApplicationProfile appProfile = SocialUtils.getProfileFromInfrastrucure(appId, context); + + if(appProfile == null){ + logger.error("The given token doesn't belong to an application!!!"); + responseBean.setSuccess(false); + responseBean.setMessage(ErrorMessages.NOT_APP_TOKEN); + status = Status.FORBIDDEN; + return Response.status(status).entity(responseBean).build(); + } + + // parse + String feedText = post.getText(); + String previewTitle = post.getPreviewtitle(); + String previewDescription = post.getPreviewdescription(); + String httpImageUrl = post.getHttpimageurl(); + boolean enableNotification = post.isEnablenotification(); + String params = post.getParams(); + + // convert enablenotification parameter + if(enableNotification) + logger.debug("Enable notification for this application post."); + else + logger.debug("Disable notification for this application post."); + + // write feed + notification if it is the case + Feed written = SocialUtils.shareApplicationUpdate( + feedText, + params, + previewTitle, + previewDescription, + httpImageUrl, + appProfile, + caller, + enableNotification + ); + + if(written != null){ + responseBean.setResult(written); + responseBean.setSuccess(true); + return Response.status(status).entity(responseBean).build(); + } + + logger.error("Unable to write post."); + responseBean.setMessage("Unable to write post"); + responseBean.setSuccess(false); + status = Status.INTERNAL_SERVER_ERROR; + return Response.status(status).entity(responseBean).build(); + } + + @GET + @Path("get-posts-vre/") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Retrieve vre's posts", notes="Retrieve all the posts in the context bound to the gcube-token", + response=ResponseBean.class, nickname="get-posts-vre") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Successfull retrieved posts, they are reported in the 'result' field of the returned object", response = ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + public Response getAllPostsByVRE() { + + String scope = ScopeProvider.instance.get(); + Caller caller = AuthorizationProvider.instance.get(); + logger.debug("Retrieving all posts coming from vre = " + scope); + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + try{ + List feeds = CassandraConnection.getInstance().getDatabookStore().getAllFeedsByVRE(scope); + Filters.hideSensitiveInformation(feeds, caller.getClient().getId()); + responseBean.setResult(feeds); + responseBean.setSuccess(true); + }catch(Exception e){ + logger.error("Unable to retrieve feeds for vre = " + scope, e); + status = Status.INTERNAL_SERVER_ERROR; + responseBean.setMessage(e.toString()); + responseBean.setSuccess(false); + } + + return Response.status(status).entity(responseBean).build(); + } + + @GET + @Path("get-posts-by-hashtag/") + @Produces({MediaType.APPLICATION_JSON}) + @ApiOperation(value = "Retrieve posts containing the hashtag", notes="Retrieve posts containing the hashtag in the context bound to the gcube-token", + response=ResponseBean.class, nickname="get-posts-by-hashtag") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Successfull retrieved posts, they are reported in the 'result' field of the returned object", response = ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + public Response getPostsByHashTags( + @QueryParam("hashtag") + @NotNull(message="hashtag cannot be missing") + @ApiParam(name="hashtag", + required=true, allowMultiple=false, value="The hashtag to be contained within the posts") + String hashtag) throws ValidationException { + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + String context = ScopeProvider.instance.get(); + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + logger.info("User " + username + " has requested posts containing hashtag " + hashtag + " in context " + context); + try{ + DatabookStore datastore = CassandraConnection.getInstance().getDatabookStore(); + List feeds = datastore.getVREFeedsByHashtag(context, hashtag); + Filters.hideSensitiveInformation(feeds, caller.getClient().getId()); + responseBean.setResult(feeds); + responseBean.setSuccess(true); + + }catch(Exception e){ + logger.error("Failed to retrieve hashtags", e); + status = Status.INTERNAL_SERVER_ERROR; + } + return Response.status(status).entity(responseBean).build(); + } + + @GET + @Path("get-id-liked-posts/") + @Produces({MediaType.APPLICATION_JSON}) + @ApiOperation(value = "Retrieve ids (UUID) of the liked by the user", notes="Retrieve ids (UUID) of the liked by the user in the context bound to the gcube-token", + response=ResponseBean.class, nickname="get-id-liked-posts") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Successfull retrieved ids, they are reported in the 'result' field of the returned object", response = ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + public Response getAllLikedPostIdsByUser() { + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + String context = ScopeProvider.instance.get(); + logger.debug("Retrieving all liked feeds for user with id " + username + " in context " + context); + + List retrievedLikedFeedsIds = null; + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + try{ + + DatabookStore datastore = CassandraConnection.getInstance().getDatabookStore(); + retrievedLikedFeedsIds = datastore.getAllLikedFeedIdsByUser(username); + Filters.filterFeedsPerContextById(retrievedLikedFeedsIds, context); + responseBean.setResult(retrievedLikedFeedsIds); + responseBean.setSuccess(true); + logger.debug("Ids of liked feeds by " + username + " retrieved"); + }catch(Exception e){ + logger.error("Unable to read such ids of liked feeds.", e); + responseBean.setMessage(e.getMessage()); + responseBean.setSuccess(false); + status = Status.INTERNAL_SERVER_ERROR; + } + + return Response.status(status).entity(responseBean).build(); + } + + @GET + @Path("get-liked-posts/") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Retrieve posts liked by the user", notes="Retrieve posts liked by the user (up to a given quantity) in the context bound to the gcube-token", + response=ResponseBean.class, nickname="get-liked-posts") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Successfull retrieved posts. They are reported in the 'result' field of the returned object", response = ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + public Response getAllLikedPostsByUser( + @DefaultValue("10") + @QueryParam("limit") + @Min(message="limit cannot be negative", value = 0) + @ApiParam(name="limit", + required=false, allowMultiple=false, value="The maximum number of posts to be retrieved") + int limit) throws ValidationException{ + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + String context = ScopeProvider.instance.get(); + List retrievedLikedFeeds = null; + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + + try{ + logger.debug("Retrieving " + limit + " liked feeds for user with id " + username + " in context " + context); + retrievedLikedFeeds = CassandraConnection.getInstance().getDatabookStore().getAllLikedFeedsByUser(username, limit); + Filters.filterFeedsPerContext(retrievedLikedFeeds, context); + Filters.hideSensitiveInformation(retrievedLikedFeeds, caller.getClient().getId()); + responseBean.setResult(retrievedLikedFeeds); + responseBean.setSuccess(true); + logger.debug("Liked feeds by " + username + " retrieved"); + }catch(Exception e){ + logger.error("Unable to read such liked feeds.", e); + responseBean.setMessage(e.getMessage()); + responseBean.setSuccess(false); + status = Status.INTERNAL_SERVER_ERROR; + } + return Response.status(status).entity(responseBean).build(); + } +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/Tokens.java b/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/Tokens.java new file mode 100644 index 0000000..ed721c6 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/Tokens.java @@ -0,0 +1,91 @@ +package org.gcube.portal.social.networking.ws.methods.v2; + +import static org.gcube.common.authorization.client.Constants.authorizationService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import io.swagger.annotations.Authorization; + +import javax.validation.Valid; +import javax.validation.ValidationException; +import javax.validation.constraints.NotNull; +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.portal.databook.shared.ApplicationProfile; +import org.gcube.portal.social.networking.swagger.config.Bootstrap; +import org.gcube.portal.social.networking.swagger.config.SwaggerConstants; +import org.gcube.portal.social.networking.ws.inputs.ApplicationId; +import org.gcube.portal.social.networking.ws.outputs.ResponseBean; +import org.gcube.portal.social.networking.ws.utils.ErrorMessages; +import org.gcube.portal.social.networking.ws.utils.SocialUtils; +import org.slf4j.LoggerFactory; + + +/** + * REST interface for the social networking library (tokens). + * @author Costantino Perciante at ISTI-CNR + */ +@Path("2/tokens") +@Api(value=SwaggerConstants.TOKENS, authorizations={@Authorization(value = Bootstrap.GCUBE_TOKEN_IN_QUERY_DEF), @Authorization(value = Bootstrap.GCUBE_TOKEN_IN_HEADER_DEF)}) +public class Tokens { + + // Logger + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Tokens.class); + + @POST + @Path("generate-application-token/") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Generate an application token", notes="Generate an application token for the application with id app_id", + response=ResponseBean.class, nickname="generate-application-token") + @ApiResponses(value = { + @ApiResponse(code = 201, message = "Successful creation of the token, reported in the 'result' field of the returned object", response = ResponseBean.class), + @ApiResponse(code = 403, message = "There is no application profile with such id", response=ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + public Response generateApplicationToken( + @NotNull(message="Missing input parameter") + @Valid + @ApiParam(name="input", required=true, allowMultiple=false, value="The bean containing the app_id field") + ApplicationId input) throws ValidationException{ + + logger.debug("Incoming request for app token generation."); + String context = ScopeProvider.instance.get(); + ResponseBean responseBean = new ResponseBean(); + Status status = Status.CREATED; + String appId = input.getAppId(); + try { + // check if the token actually matches an application profile + ApplicationProfile appProfile = SocialUtils.getProfileFromInfrastrucure(appId, context); + if(appProfile == null){ + logger.error("The given id doesn't belong to an application!!!"); + responseBean.setSuccess(false); + responseBean.setMessage(ErrorMessages.NOT_APP_ID); + status = Status.FORBIDDEN; + return Response.status(status).entity(responseBean).build(); + } + logger.info("Generating token for the application with id " + appId); + // each token is related to an identifier and the context + String appToken = authorizationService().generateExternalServiceToken(appId); + responseBean.setSuccess(true); + responseBean.setResult(appToken); + } catch (Exception e) { + logger.error("Unable to generate token for app " + appId + " and scope " + context); + status = Status.INTERNAL_SERVER_ERROR; + responseBean.setSuccess(false); + responseBean.setMessage(ErrorMessages.TOKEN_GENERATION_APP_FAILED); + return Response.status(status).entity(responseBean).build(); + } + + return Response.status(status).entity(responseBean).build(); + } + +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/Users.java b/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/Users.java new file mode 100644 index 0000000..7d705a7 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/Users.java @@ -0,0 +1,577 @@ +package org.gcube.portal.social.networking.ws.methods.v2; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import javax.validation.ValidationException; +import javax.validation.constraints.NotNull; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.gcube.common.authorization.library.provider.AuthorizationProvider; +import org.gcube.common.authorization.library.utils.Caller; +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.common.scope.impl.ScopeBean; +import org.gcube.common.scope.impl.ScopeBean.Type; +import org.gcube.portal.social.networking.caches.UsersCache; +import org.gcube.portal.social.networking.liferay.ws.GroupManagerWSBuilder; +import org.gcube.portal.social.networking.liferay.ws.RoleManagerWSBuilder; +import org.gcube.portal.social.networking.liferay.ws.UserManagerWSBuilder; +import org.gcube.portal.social.networking.swagger.config.Bootstrap; +import org.gcube.portal.social.networking.swagger.config.SwaggerConstants; +import org.gcube.portal.social.networking.ws.outputs.ResponseBean; +import org.gcube.portal.social.networking.ws.utils.ErrorMessages; +import org.gcube.portal.social.networking.ws.utils.TokensUtils; +import org.gcube.portal.social.networking.ws.utils.UserProfileExtendedWithVerifiedEmail; +import org.gcube.vomanagement.usermanagement.GroupManager; +import org.gcube.vomanagement.usermanagement.RoleManager; +import org.gcube.vomanagement.usermanagement.UserManager; +import org.gcube.vomanagement.usermanagement.model.GCubeRole; +import org.gcube.vomanagement.usermanagement.model.GCubeUser; +import org.slf4j.LoggerFactory; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import io.swagger.annotations.Authorization; + +/** + * REST interface for the social networking library (users). + * @author Costantino Perciante at ISTI-CNR + */ +@Path("2/users") +@Api(value=SwaggerConstants.USERS, authorizations={@Authorization(value = Bootstrap.GCUBE_TOKEN_IN_QUERY_DEF), @Authorization(value = Bootstrap.GCUBE_TOKEN_IN_HEADER_DEF)}) +public class Users { + + // Logger + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Users.class); + private static final String NOT_USER_TOKEN_CONTEXT_USED = "User's information can only be retrieved through a user token (not qualified)"; + private static final List GLOBAL_ROLES_ALLOWED_BY_LOCAL_CALL_METHOD = Arrays.asList("DataMiner-Manager","Quota-Manager"); + + @GET + @Path("get-custom-attribute/") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Read a user's custom attribute", notes="Read a user's custom attribute. The user is the one owning the token", + response=ResponseBean.class, nickname="get-custom-attribute") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Successful read of the attribute, reported in the 'result' field of the returned object", response = ResponseBean.class), + @ApiResponse(code = 404, message = "Such an attribute doesn't exist", response = ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + public Response readCustomAttr( + @QueryParam("attribute") + @NotNull(message="attribute name is missing") + @ApiParam(name="attribute", required=true, allowMultiple=false, value="The key of the attribute to be read") + String attributeKey + ) throws ValidationException { + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + if(!TokensUtils.isUserTokenDefault(caller)){ + status = Status.FORBIDDEN; + responseBean.setMessage(NOT_USER_TOKEN_CONTEXT_USED); + logger.warn("Trying to access users method via a token different than USER is not allowed"); + }else{ + UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager(); + try{ + + GCubeUser user = userManager.getUserByUsername(username); + String toReturn = (String)userManager.readCustomAttr(user.getUserId(), attributeKey); + responseBean.setSuccess(true); + responseBean.setResult(toReturn); + + }catch(Exception e){ + + logger.error("Unable to retrieve attribute for user.", e); + responseBean.setMessage(e.toString()); + responseBean.setSuccess(false); + status = Status.NOT_FOUND; + + } + } + return Response.status(status).entity(responseBean).build(); + } + + // @PUT + // @Path("update-custom-attribute") + // @Consumes(MediaType.APPLICATION_JSON) + // @Produces(MediaType.APPLICATION_JSON) + // @ApiOperation(value = "Update a user's custom attribute", notes="Update a user's custom attribute. The user is the one owning the token", + // response=ResponseBean.class, nickname="update-custom-attribute") + // @ApiResponses(value = { + // @ApiResponse(code = 200, message = "Successful update of the attribute, the new value is reported in the 'result' field of the returned object", response = ResponseBean.class), + // @ApiResponse(code = 400, message = "Key or value for the new attribute missing", response=ResponseBean.class), + // @ApiResponse(code = 500, message = ErrorMessages.errorMessageApiResult, response=ResponseBean.class), + // @ApiResponse(code = 304, message = "Attribute not modified", response=ResponseBean.class)}) + // public Response updateCustomAttr( + // @NotNull(message="input is missing") + // @ApiParam(name="input", required=true, allowMultiple=false, value="The object having an attribute key and a value key for the new value") + // String inputJsonObj + // ) throws ValidationException{ + // + // Caller caller = AuthorizationProvider.instance.get(); + // String username = caller.getClient().getId(); + // ResponseBean responseBean = new ResponseBean(); + // Status status = Status.OK; + // + // try{ + // + // // Jackson parser + // ObjectMapper mapper = new ObjectMapper(); + // JsonNode actualObj = mapper.readTree(inputJsonObj); + // String attributeKey = actualObj.get("attribute").asText(); + // String newValue = actualObj.get("value").asText(); + // + // if(attributeKey == null || attributeKey.isEmpty() || newValue == null){ + // + // logger.error("Missing/wrong request parameters"); + // status = Status.BAD_REQUEST; + // responseBean.setMessage(ErrorMessages.missingParameters); + // return Response.status(status).entity(responseBean).build(); + // + // } + // + // GCubeUser user = userManager.getUserByUsername(username); + // userManager.saveCustomAttr(user.getUserId(), attributeKey, newValue); + // responseBean.setSuccess(true); + // responseBean.setResult(newValue); + // + // }catch(Exception e){ + // + // logger.error("Unable to set attribute for user.", e); + // status = Status.NOT_MODIFIED; + // responseBean.setMessage(e.toString()); + // + // } + // + // return Response.status(status).entity(responseBean).build(); + // } + + @GET + @Path("get-fullname") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Read the user's fullname", notes="Read the user's fullname. The user is the one owning the token", + response=ResponseBean.class, nickname="get-fullname") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "The user's fullname is reported in the 'result' field of the returned object", response = ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + public Response getUserFullname(){ + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + String fullName = null; + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + + if(!TokensUtils.isUserTokenDefault(caller)){ + status = Status.FORBIDDEN; + responseBean.setMessage(NOT_USER_TOKEN_CONTEXT_USED); + logger.warn("Trying to access users method via a token different than USER is not allowed"); + }else{ + UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager(); + try{ + + GCubeUser user = userManager.getUserByUsername(username); + fullName = user.getFullname(); + logger.info("Found fullname " + fullName + " for user " + username); + responseBean.setResult(fullName); + responseBean.setSuccess(true); + + }catch(Exception e){ + + logger.error("Unable to retrieve attribute for user.", e); + status = Status.INTERNAL_SERVER_ERROR; + + } + } + return Response.status(status).entity(responseBean).build(); + } + + @GET + @Path("get-email") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Read the user's email address", notes="Read the user's email address. The user is the one owning the token", + response=ResponseBean.class, nickname="get-email") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "The user's email value is reported in the 'result' field of the returned object", response = ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + public Response getUserEmail(){ + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + String email = null; + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + if(!TokensUtils.isUserTokenDefault(caller)){ + status = Status.FORBIDDEN; + responseBean.setMessage(NOT_USER_TOKEN_CONTEXT_USED); + logger.warn("Trying to access users method via a token different than USER is not allowed"); + }else{ + try{ + UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager(); + GCubeUser user = userManager.getUserByUsername(username); + email = user.getEmail(); + logger.info("Found email " + email + " for user " + username); + responseBean.setResult(email); + responseBean.setSuccess(true); + + }catch(Exception e){ + + logger.error("Unable to retrieve attribute for user.", e); + status = Status.INTERNAL_SERVER_ERROR; + + } + } + return Response.status(status).entity(responseBean).build(); + } + + @GET + @Path("get-profile") + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation(value = "Read the user's profile", notes="Read the user's profile. The user is the one owning the token", + response=ResponseBean.class, nickname="get-profile") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "The user's profile is reported in the 'result' field of the returned object", response = ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + public Response getUserProfile(){ + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + GCubeUser user = null; + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + + if(!TokensUtils.isUserTokenDefault(caller)){ + status = Status.FORBIDDEN; + responseBean.setMessage(NOT_USER_TOKEN_CONTEXT_USED); + logger.warn("Trying to access users method via a token different than USER is not allowed"); + }else{ + try{ + UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager(); + user = userManager.getUserByUsername(username); + responseBean.setResult(user); + responseBean.setSuccess(true); + }catch(Exception e){ + logger.error("Unable to retrieve user's profile", e); + responseBean.setMessage(e.getMessage()); + status = Status.INTERNAL_SERVER_ERROR; + } + } + return Response.status(status).entity(responseBean).build(); + } + + private static final Function GCUBE_TO_EXTENDED_PROFILE_MAP_WITH_VERIFIED_EMAIL + = new Function() { + + @Override + public UserProfileExtendedWithVerifiedEmail apply(GCubeUser t) { + + if(t == null) + return null; + + UserProfileExtendedWithVerifiedEmail profile = new UserProfileExtendedWithVerifiedEmail(t.getUsername(), null, t.getUserAvatarURL(), t.getFullname()); + profile.setEmail(t.getEmail()); + profile.setFirstName(t.getFirstName()); + profile.setJobTitle(t.getJobTitle()); + profile.setLastName(t.getLastName()); + profile.setLocationIndustry(t.getLocation_industry()); + profile.setMale(t.isMale()); + profile.setMiddleName(t.getMiddleName()); + profile.setVerifiedEmail(true); + return profile; + } + }; + + @GET + @Path("get-oauth-profile") + @Produces(MediaType.APPLICATION_JSON) + public Response getUserOAuthProfile(){ + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + String scope = ScopeProvider.instance.get(); + GCubeUser user = null; + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + UserProfileExtendedWithVerifiedEmail userWithEmailVerified = null; + if(! (TokensUtils.isUserTokenDefault(caller) || TokensUtils.isUserTokenQualified(caller))){ + status = Status.FORBIDDEN; + responseBean.setMessage(NOT_USER_TOKEN_CONTEXT_USED); + logger.warn("Trying to access users method via a token different than USER is not allowed"); + }else{ + try{ + UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager(); + RoleManager roleManager = RoleManagerWSBuilder.getInstance().getRoleManager(); + GroupManager groupManager = GroupManagerWSBuilder.getInstance().getGroupManager(); + user = userManager.getUserByUsername(username); + userWithEmailVerified = GCUBE_TO_EXTENDED_PROFILE_MAP_WITH_VERIFIED_EMAIL.apply(user); + List roles = roleManager.listRolesByUserAndGroup(user.getUserId(), groupManager.getGroupIdFromInfrastructureScope(scope)); + List rolesNames = new ArrayList(); + for (GCubeRole gCubeRole : roles) { + rolesNames.add(gCubeRole.getRoleName()); + } + userWithEmailVerified.setRoles(rolesNames); + + //responseBean.setResult(userWithEmailVerified); + responseBean.setSuccess(true); + }catch(Exception e){ + logger.error("Unable to retrieve user's profile", e); + responseBean.setMessage(e.getMessage()); + status = Status.INTERNAL_SERVER_ERROR; + } + } + logger.debug("returning: "+userWithEmailVerified.toString()); + return Response.status(status).entity(userWithEmailVerified).build(); + } + + + @GET + @Path("get-all-usernames") + @ApiOperation(value = "Get the list of usernames belonging to a given context", notes="Retrieve the list of usernames for the user belonging to the context linked to the provided token.", + response=ResponseBean.class, nickname="get-all-usernames") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "The list of usernames is put into the 'result' field of the returned object", response = ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + @Produces(MediaType.APPLICATION_JSON) + public Response getAllUserNames(){ + + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + + List usernames = new ArrayList(); + try{ + + GroupManager groupManager = GroupManagerWSBuilder.getInstance().getGroupManager(); + UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager(); + long groupId = groupManager.getGroupIdFromInfrastructureScope(ScopeProvider.instance.get()); + + // first retrieve ids + List userIds = userManager.getUserIdsByGroup(groupId); + + // check info in cache when available + UsersCache cache = UsersCache.getSingleton(); + + for (Long userId : userIds) { + if(cache.getUser(userId) == null){ + GCubeUser user = userManager.getUserById(userId); + if(user != null){ + usernames.add(user.getUsername()); + cache.pushEntry(userId, user); + } + }else + usernames.add(cache.getUser(userId).getUsername()); + } + + responseBean.setResult(usernames); + responseBean.setSuccess(true); + }catch(Exception e){ + logger.error("Unable to retrieve user's usernames", e); + responseBean.setMessage(e.getMessage()); + status = Status.INTERNAL_SERVER_ERROR; + } + + return Response.status(status).entity(responseBean).build(); + } + + @GET + @Path("get-all-fullnames-and-usernames") + @ApiOperation(value = "Get the map of couples username/fullname of the users belonging to a given context", notes="Get the map of couples username/fullname of the users belonging to the context linked to the provided token.", + response=ResponseBean.class, nickname="get-all-fullnames-and-usernames") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "The map is put into the 'result' field of the returned object", response = ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + @Produces(MediaType.APPLICATION_JSON) + public Response getFullnamesAndUsernames(){ + + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + + Map usernamesAndFullnames = new HashMap(); + try{ + + GroupManager groupManager = GroupManagerWSBuilder.getInstance().getGroupManager(); + UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager(); + long groupId = groupManager.getGroupIdFromInfrastructureScope(ScopeProvider.instance.get()); + + // first retrieve ids + List userIds = userManager.getUserIdsByGroup(groupId); + + // check info in cache when available + UsersCache cache = UsersCache.getSingleton(); + + for (Long userId : userIds) { + if(cache.getUser(userId) == null){ + GCubeUser user = userManager.getUserById(userId); + if(user != null){ + usernamesAndFullnames.put(user.getUsername(), user.getFullname()); + cache.pushEntry(userId, user); + } + }else + usernamesAndFullnames.put(cache.getUser(userId).getUsername(), cache.getUser(userId).getFullname()); + } + + responseBean.setResult(usernamesAndFullnames); + responseBean.setSuccess(true); + }catch(Exception e){ + logger.error("Unable to retrieve user's usernames", e); + responseBean.setMessage(e.getMessage()); + status = Status.INTERNAL_SERVER_ERROR; + } + + return Response.status(status).entity(responseBean).build(); + } + + @GET + @Path("get-usernames-by-global-role") + @ApiOperation(value = "Get the list of users having a given global-role", notes="Get the list of users having a given global-role, e.g. 'Administrator'.", + response=ResponseBean.class, nickname="get-usernames-by-global-role") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "The list is put into the 'result' field of the returned object", response = ResponseBean.class), + @ApiResponse(code = 500, message = ErrorMessages.ERROR_IN_API_RESULT, response=ResponseBean.class)}) + @Produces(MediaType.APPLICATION_JSON) + public Response getUsernamesByGlobalRole( + @ApiParam(value = "role-name: the name of the role to be checked (e.g. Administrator)", required = true) + @QueryParam("role-name") String roleName){ + + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + + // this method can only be called from IS scope (except for roles in GLOBAL_ROLES_ALLOWED_BY_LOCAL) + ScopeBean scopeInfo = new ScopeBean(ScopeProvider.instance.get()); + + if(!scopeInfo.is(Type.INFRASTRUCTURE)){ + status = Status.BAD_REQUEST; + responseBean.setMessage("This method can only be called with an infrastructure token"); + }else{ + + List usernames = new ArrayList(); + try{ + RoleManager roleManager = RoleManagerWSBuilder.getInstance().getRoleManager(); + long globalRoleId = roleManager.getRoleIdByName(roleName); + if(globalRoleId > 0){ + UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager(); + List users = userManager.listUsersByGlobalRole(globalRoleId); + if(users != null){ + for (GCubeUser gCubeUser : users) { + usernames.add(gCubeUser.getUsername()); + } + } + responseBean.setResult(usernames); + responseBean.setSuccess(true); + }else{ + responseBean.setMessage("No global role exists whit such a name"); + status = Status.BAD_REQUEST; + } + }catch(Exception e){ + logger.error("Unable to retrieve user's usernames", e); + responseBean.setMessage(e.getMessage()); + status = Status.INTERNAL_SERVER_ERROR; + } + } + return Response.status(status).entity(responseBean).build(); + } + + @GET + @Path("get-usernames-by-role") + @Produces(MediaType.APPLICATION_JSON) + public Response getUsernamesByRole( + @QueryParam("role-name") String roleName){ + + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + String context = ScopeProvider.instance.get(); + List usernames = new ArrayList(); + try{ + GroupManager groupManager = GroupManagerWSBuilder.getInstance().getGroupManager(); + RoleManager roleManager = RoleManagerWSBuilder.getInstance().getRoleManager(); + long roleId = roleManager.getRoleIdByName(roleName); + if(roleId > 0){ + UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager(); + List users = null; + long groupId = groupManager.getGroupIdFromInfrastructureScope(context); + // first check if for any reason this is a global role, then (if result is null or exception arises) check for site role + // Global role's users are retrieved much faster + try{ + if(GLOBAL_ROLES_ALLOWED_BY_LOCAL_CALL_METHOD.contains(roleName)){ + // TODO inconsistent value can be returned + users = userManager.listUsersByGlobalRole(roleId); + } + }catch(Exception globalExp){ + logger.warn("Failed while checking for global role... trying with local one", globalExp); + } + + if(users == null || users.isEmpty()){ + logger.debug("User list is still null/empty, checking for local information"); + users = userManager.listUsersByGroupAndRole(groupId, roleId); + } + + if(users != null){ + for (GCubeUser gCubeUser : users) { + usernames.add(gCubeUser.getUsername()); + } + } + responseBean.setResult(usernames); + responseBean.setSuccess(true); + }else{ + responseBean.setMessage("No role exists whit such a name"); + status = Status.BAD_REQUEST; + } + }catch(Exception e){ + logger.error("Unable to retrieve user's usernames", e); + responseBean.setMessage(e.getMessage()); + status = Status.INTERNAL_SERVER_ERROR; + } + + return Response.status(status).entity(responseBean).build(); + } + + @GET + @Path("user-exists") + @Produces(MediaType.APPLICATION_JSON) + public Response existUser(@QueryParam("username") String username){ + + ResponseBean responseBean = new ResponseBean(); + String messageOnError = "This method can be invoked only by using an application token bound to the root context"; + Status status = Status.BAD_REQUEST; + responseBean.setMessage(messageOnError); + responseBean.setSuccess(false); + Caller caller = AuthorizationProvider.instance.get(); + + if(!TokensUtils.isApplicationToken(caller)) + return Response.status(status).entity(responseBean).build(); + + ScopeBean scopeInfo = new ScopeBean(ScopeProvider.instance.get()); + + if(!scopeInfo.is(Type.INFRASTRUCTURE)) + return Response.status(status).entity(responseBean).build(); + + try{ + + UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager(); + GCubeUser user = userManager.getUserByUsername(username); + responseBean.setSuccess(true); + responseBean.setMessage(null); + responseBean.setResult(user != null); + status = Status.OK; + + }catch(Exception e){ + logger.error("Unable to retrieve such information", e); + responseBean.setMessage(e.getMessage()); + status = Status.INTERNAL_SERVER_ERROR; + } + + return Response.status(status).entity(responseBean).build(); + } + +} \ No newline at end of file diff --git a/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/VREs.java b/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/VREs.java new file mode 100644 index 0000000..6efa424 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/VREs.java @@ -0,0 +1,116 @@ +package org.gcube.portal.social.networking.ws.methods.v2; + +import io.swagger.annotations.Api; +import io.swagger.annotations.Authorization; + +import java.util.List; + +import javax.validation.ValidationException; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.gcube.common.authorization.library.provider.AuthorizationProvider; +import org.gcube.common.authorization.library.utils.Caller; +import org.gcube.portal.social.networking.liferay.ws.GroupManagerWSBuilder; +import org.gcube.portal.social.networking.liferay.ws.RoleManagerWSBuilder; +import org.gcube.portal.social.networking.liferay.ws.UserManagerWSBuilder; +import org.gcube.portal.social.networking.swagger.config.Bootstrap; +import org.gcube.portal.social.networking.swagger.config.SwaggerConstants; +import org.gcube.portal.social.networking.ws.outputs.ResponseBean; +import org.gcube.portal.social.networking.ws.utils.TokensUtils; +import org.gcube.vomanagement.usermanagement.GroupManager; +import org.gcube.vomanagement.usermanagement.RoleManager; +import org.gcube.vomanagement.usermanagement.UserManager; +import org.gcube.vomanagement.usermanagement.model.GCubeGroup; +import org.gcube.vomanagement.usermanagement.model.GCubeUser; +import org.gcube.vomanagement.usermanagement.model.GatewayRolesNames; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.slf4j.LoggerFactory; + + +/** + * REST interface for the social networking library (vres). + * @author Costantino Perciante at ISTI-CNR + */ +@Path("2/vres") +@Api(value=SwaggerConstants.VRES, authorizations={@Authorization(value = Bootstrap.GCUBE_TOKEN_IN_QUERY_DEF), @Authorization(value = Bootstrap.GCUBE_TOKEN_IN_HEADER_DEF)}) +public class VREs { + + // Logger + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Users.class); + + @SuppressWarnings("unchecked") + @GET + @Path("get-my-vres/") + @Produces(MediaType.APPLICATION_JSON) + public Response getMyVres( + @DefaultValue("false") @QueryParam("getManagers") boolean getManagers + ) throws ValidationException { + + Caller caller = AuthorizationProvider.instance.get(); + String username = caller.getClient().getId(); + ResponseBean responseBean = new ResponseBean(); + Status status = Status.OK; + + if(!TokensUtils.isUserToken(caller)){ + status = Status.FORBIDDEN; + responseBean.setMessage("This method can only be invoked with a user token!"); + logger.warn("Trying to access get-my-vres method via a token different than USER is not allowed"); + }else{ + GroupManager gmManager = GroupManagerWSBuilder.getInstance().getGroupManager(); + UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager(); + RoleManager roleManager = RoleManagerWSBuilder.getInstance().getRoleManager(); + try{ + + GCubeUser user = userManager.getUserByUsername(username); + List vres = gmManager.listVresByUser(user.getUserId()); + JSONArray toReturn = new JSONArray(); + + for (GCubeGroup group : vres) { + + // # ticket 9333 + JSONObject obj = new JSONObject(); + obj.put("name", group.getGroupName()); + obj.put("context", gmManager.getInfrastructureScope(group.getGroupId())); + obj.put("description", group.getDescription()); + //obj.put("thumbnail_url", ...); // TODO + JSONArray managers = new JSONArray(); + + if(getManagers){ + List vreManagers = userManager.listUsersByGroupAndRole(group.getGroupId(), + roleManager.getRoleIdByName(GatewayRolesNames.VRE_MANAGER.getRoleName())); + + for (GCubeUser vreManager : vreManagers) { + JSONObject manager = new JSONObject(); + manager.put("username", vreManager.getUsername()); + manager.put("fullname", vreManager.getFullname()); + managers.add(manager); + } + + obj.put("managers", managers); + } + toReturn.add(obj); + } + + responseBean.setSuccess(true); + responseBean.setResult(toReturn); + + }catch(Exception e){ + + logger.error("Unable to retrieve vres for user.", e); + responseBean.setMessage(e.toString()); + responseBean.setSuccess(false); + status = Status.INTERNAL_SERVER_ERROR; + + } + } + return Response.status(status).entity(responseBean).build(); + } +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/outputs/ResponseBean.java b/src/main/java/org/gcube/portal/social/networking/ws/outputs/ResponseBean.java new file mode 100644 index 0000000..7a89c25 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/outputs/ResponseBean.java @@ -0,0 +1,79 @@ +package org.gcube.portal.social.networking.ws.outputs; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +import java.io.Serializable; + +/** + * Response bean + * @author Costantino Perciante at ISTI-CNR + * (costantino.perciante@isti.cnr.it) + * + */ +@ApiModel(description="A response object", value="Response") +public class ResponseBean implements Serializable { + + private static final long serialVersionUID = -2725238162673879658L; + @ApiModelProperty(value="The result of the request: true if it succeeded, false otherwise") + private boolean success; + + @ApiModelProperty(value="An error message if something wrong happened, null/empty otherwise") + private String message; + + @ApiModelProperty(value="The result object of the request") + private Object result; + + public ResponseBean() { + super(); + } + + /** + * @param success + * @param message + * @param result + * @param help + */ + public ResponseBean(boolean success, String message, Object result) { + super(); + this.success = success; + this.message = message; + this.result = result; + } + + + public boolean isSuccess() { + return success; + } + + + public void setSuccess(boolean success) { + this.success = success; + } + + + public String getMessage() { + return message; + } + + + public void setMessage(String message) { + this.message = message; + } + + + public Object getResult() { + return result; + } + + + public void setResult(Object result) { + this.result = result; + } + + @Override + public String toString() { + return "ResponseBean [success=" + success + + ", message=" + message + ", result=" + result + "]"; + } +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/providers/CustomObjectMapper.java b/src/main/java/org/gcube/portal/social/networking/ws/providers/CustomObjectMapper.java new file mode 100644 index 0000000..b5b3554 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/providers/CustomObjectMapper.java @@ -0,0 +1,37 @@ +package org.gcube.portal.social.networking.ws.providers; + +import javax.ws.rs.ext.ContextResolver; +import javax.ws.rs.ext.Provider; + +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.SerializationFeature; + +@Provider +/** + * Custom mapper with property CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES to perform serialization/deserialization + * with snake case over camel case for json beans. + * TODO check https://github.com/FasterXML/jackson-docs/wiki/JacksonMixInAnnotations for applying such transformation + * only to some classes. + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ +public class CustomObjectMapper implements ContextResolver { + + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(CustomObjectMapper.class); + private final ObjectMapper mapper; + + public CustomObjectMapper() { + logger.debug("new ObjectMapperResolver()"); + mapper = new ObjectMapper(); + mapper.enable(SerializationFeature.INDENT_OUTPUT); + mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + } + + @Override + public ObjectMapper getContext(Class type) { + logger.debug("ObjectMapperResolver.getContext(...) invoked"); + return mapper; + } +} \ No newline at end of file diff --git a/src/main/java/org/gcube/portal/social/networking/ws/providers/JobStatusTypeDeserializer.java b/src/main/java/org/gcube/portal/social/networking/ws/providers/JobStatusTypeDeserializer.java new file mode 100644 index 0000000..fc3b5c1 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/providers/JobStatusTypeDeserializer.java @@ -0,0 +1,46 @@ +package org.gcube.portal.social.networking.ws.providers; + +import java.io.IOException; + +import org.gcube.portal.databook.shared.JobStatusType; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +/** + * Deserializer used to map a string representing the status in this JobNotificationBean to the JobStatusType enum. + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ +public class JobStatusTypeDeserializer extends JsonDeserializer{ + + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(JobStatusTypeDeserializer.class); + + @Override + public JobStatusType deserialize(JsonParser p, + DeserializationContext ctxt) throws IOException, + JsonProcessingException { + + logger.debug("Status deserializer called"); + + String status = p.getText(); + JobStatusType toReturn = null; + + logger.debug("Status coming from json object is " + status); + + JobStatusType[] values = JobStatusType.values(); + + for (JobStatusType jobStatusType : values) { + if(jobStatusType.toString().toLowerCase().contains(status.toLowerCase())){ + toReturn = jobStatusType; + break; + } + } + + logger.debug("JOB STATUS deserialized as " + toReturn); + return toReturn; + } + +} \ No newline at end of file diff --git a/src/main/java/org/gcube/portal/social/networking/ws/utils/CassandraConnection.java b/src/main/java/org/gcube/portal/social/networking/ws/utils/CassandraConnection.java new file mode 100644 index 0000000..1a7fee3 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/utils/CassandraConnection.java @@ -0,0 +1,50 @@ +package org.gcube.portal.social.networking.ws.utils; + +import org.gcube.portal.databook.server.DBCassandraAstyanaxImpl; +import org.gcube.portal.databook.server.DatabookStore; +import org.gcube.smartgears.ContextProvider; +import org.gcube.smartgears.context.application.ApplicationContext; +import org.slf4j.LoggerFactory; + +/** + * Cassandra connection class. + * @author Costantino Perciante at ISTI-CNR + */ +public class CassandraConnection { + + // Logger + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(CassandraConnection.class); + + // databook store (singleton) + private static DatabookStore store; + + private static CassandraConnection singleton = new CassandraConnection(); + + private CassandraConnection(){ + ApplicationContext ctx = ContextProvider.get(); // get this info from SmartGears + logger.info("Getting connection to cassandra"); + store = new DBCassandraAstyanaxImpl(ctx.container().configuration().infrastructure()); + logger.info("Connection to cassandra created"); + } + + /** + * Returns the object to query the cassandra cluster. + * @return connection pool to cassandra cluster + */ + public DatabookStore getDatabookStore(){ + + return store; + + } + + /** + * Get the instance + * @return + */ + public static CassandraConnection getInstance(){ + + return singleton; + + } + +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/utils/ElasticSearchConnection.java b/src/main/java/org/gcube/portal/social/networking/ws/utils/ElasticSearchConnection.java new file mode 100644 index 0000000..32939cc --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/utils/ElasticSearchConnection.java @@ -0,0 +1,52 @@ +package org.gcube.portal.social.networking.ws.utils; + +import org.gcube.smartgears.ContextProvider; +import org.gcube.smartgears.context.application.ApplicationContext; +import org.gcube.socialnetworking.social_data_search_client.ElasticSearchClient; +import org.gcube.socialnetworking.social_data_search_client.ElasticSearchClientImpl; +import org.slf4j.LoggerFactory; + +/** + * The class discovers and offer connections to the elastic search cluster. + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ +public class ElasticSearchConnection { + + // Logger + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(ElasticSearchConnection.class); + + // databook store (singleton) + private ElasticSearchClient es; + + // singleton + private static ElasticSearchConnection singleton = new ElasticSearchConnection(); + + private ElasticSearchConnection(){ + try { + ApplicationContext ctx = ContextProvider.get(); // get this info from SmartGears + logger.info("Creating connection to Elasticsearch"); + es = new ElasticSearchClientImpl(ctx.container().configuration().infrastructure()); + logger.info("Elasticsearch connection created"); + } catch (Exception e) { + logger.error("Failed to connect to elasticsearch", e); + } + } + + public static ElasticSearchConnection getSingleton(){ + + return singleton; + + } + + /** + * Returns the object to connect to cassandra cluster. + * @return connection pool to cassandra cluster + * @throws Exception + */ + public ElasticSearchClient getElasticSearchClient(){ + + return es; + + } + +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/utils/ErrorMessages.java b/src/main/java/org/gcube/portal/social/networking/ws/utils/ErrorMessages.java new file mode 100644 index 0000000..d1a8852 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/utils/ErrorMessages.java @@ -0,0 +1,20 @@ +package org.gcube.portal.social.networking.ws.utils; + + +/** + * Class that contains error messages to be returned in the HTTP responses. + * @author Costantino Perciante at ISTI-CNR + */ +public class ErrorMessages { + + public static final String MISSING_TOKEN = "Missing token."; + public static final String MISSING_PARAMETERS = "Missing request parameters."; + public static final String INVALID_TOKEN = "Invalid token."; + public static final String TOKEN_GENERATION_APP_FAILED = "Token generation failed."; + public static final String NOT_APP_TOKEN = "Invalid token: not belonging to an application."; + public static final String NOT_APP_ID = "Invalid application id: it doesn't belong to an application."; + public static final String NO_APP_PROFILE_FOUND = "There is no application profile for this app id/scope."; + public static final String BAD_REQUEST = "Please check the parameter you passed, it seems a bad request"; + public static final String ERROR_IN_API_RESULT = "The error is reported into the 'message' field of the returned object"; + public static final String POST_OUTSIDE_VRE = "A post cannot be written into a context that is not a VRE"; +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/utils/Filters.java b/src/main/java/org/gcube/portal/social/networking/ws/utils/Filters.java new file mode 100644 index 0000000..89bc663 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/utils/Filters.java @@ -0,0 +1,170 @@ +package org.gcube.portal.social.networking.ws.utils; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.gcube.portal.databook.server.DatabookStore; +import org.gcube.portal.databook.shared.Comment; +import org.gcube.portal.databook.shared.EnhancedFeed; +import org.gcube.portal.databook.shared.Feed; +import org.gcube.portal.social.networking.liferay.ws.GroupManagerWSBuilder; +import org.gcube.vomanagement.usermanagement.GroupManager; +import org.gcube.vomanagement.usermanagement.exception.GroupRetrievalFault; +import org.gcube.vomanagement.usermanagement.exception.UserManagementSystemException; +import org.gcube.vomanagement.usermanagement.model.GCubeGroup; +import org.slf4j.LoggerFactory; + +/** + * Filters to apply to feeds/comments etc + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ +public class Filters { + + // Logger + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Filters.class); + + private static List getContexts(String context) throws IllegalArgumentException, UserManagementSystemException, GroupRetrievalFault{ + + // retrieve group information + GroupManager gm = GroupManagerWSBuilder.getInstance().getGroupManager(); + GCubeGroup group = gm.getGroup(gm.getGroupIdFromInfrastructureScope(context)); + + List contexts = new ArrayList(); + + if(gm.isRootVO(group.getGroupId())){ + + } + else if(gm.isVO(group.getGroupId())){ + + List vres = group.getChildren(); + for (GCubeGroup gCubeGroup : vres) { + contexts.add(gm.getInfrastructureScope(gCubeGroup.getGroupId())); + } + + }else{ + contexts.add(gm.getInfrastructureScope(group.getGroupId())); + } + + return contexts; + } + + /** + * Given a list of not filtered feeds, the methods remove feeds unaccessible in this scope. + * If the initial context is the root: all feeds are returned; + * If the initial context is a VO: feeds for vres within the vo are returned; + * If the initial context is a vre: feeds of the vre are returned; + * @param feedsIds + * @param context + * @throws Exception + */ + public static void filterFeedsPerContextById( + List feedsIds, String context) throws Exception { + + DatabookStore datastore = CassandraConnection.getInstance().getDatabookStore(); + List feeds = new ArrayList(); + + for (String feedId : feedsIds) { + try{ + feeds.add(datastore.readFeed(feedId)); + }catch(Exception e){ + logger.error("Unable to read feed with id " + feedId, e); + } + } + + // filter + filterFeedsPerContext(feeds, context); + + // clear and convert + feedsIds.clear(); + for (Feed feed : feeds) { + feedsIds.add(feed.getKey()); + } + + } + + /** + * Given a list of not filtered feeds, the methods remove feeds unaccessible in this scope. + * If the initial context is the root: all feeds are returned; + * If the initial context is a VO: feeds for vres within the vo are returned; + * If the initial context is a vre: feeds of the vre are returned; + * @param retrievedLikedFeeds + * @param context + * @throws Exception + */ + public static void filterFeedsPerContext(List feeds, String context) throws Exception { + + List contexts = getContexts(context); + + // filter + Iterator iterator = feeds.iterator(); + while (iterator.hasNext()) { + Feed feed = (Feed) iterator.next(); + if(!contexts.contains(feed.getVreid())) + iterator.remove(); + } + + } + + /** + * Filter comments per context + * @param comments + * @param context + * @throws Exception + */ + public static void filterCommentsPerContext(List comments, String context) throws Exception { + + List contexts = getContexts(context); + + // get cassandra store + DatabookStore datastore = CassandraConnection.getInstance().getDatabookStore(); + + // filter + Iterator iterator = comments.iterator(); + while (iterator.hasNext()) { + try{ + Comment comment = (Comment) iterator.next(); + Feed parent = datastore.readFeed(comment.getFeedid()); + if(!contexts.contains(parent.getVreid())) + iterator.remove(); + }catch(Exception e){ + logger.warn("Failed to analyze this comment", e); + iterator.remove(); // remove it anyway + } + } + + } + + /** + * Depending on the type of object provided (e.g. Feed, Comment etc), some information are removed + * @param comments + * @throws Exception + */ + public static void hideSensitiveInformation(List toClear, String usernameCaller){ + + if(toClear == null || toClear.isEmpty() || usernameCaller == null || usernameCaller.isEmpty()) + return; + else{ + + // for feeds + if(toClear.get(0).getClass().equals(Feed.class)){ + + for (T feed : toClear) { + Feed feeded = ((Feed)feed); + if(!usernameCaller.equals(feeded.getEntityId())) + feeded.setEmail(""); // remove the email field + } + + }else if(toClear.get(0).getClass().equals(EnhancedFeed.class)){ + for (T enhancedFeed : toClear) { + Feed feeded = ((EnhancedFeed)enhancedFeed).getFeed(); + if(!usernameCaller.equals(feeded.getEntityId())) + feeded.setEmail(""); // remove the email field + } + } + + } + + } + +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/utils/SocialUtils.java b/src/main/java/org/gcube/portal/social/networking/ws/utils/SocialUtils.java new file mode 100644 index 0000000..741ab31 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/utils/SocialUtils.java @@ -0,0 +1,454 @@ +package org.gcube.portal.social.networking.ws.utils; + +import static org.gcube.resources.discovery.icclient.ICFactory.client; + +import java.io.StringReader; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.UUID; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.gcube.applicationsupportlayer.social.ApplicationNotificationsManager; +import org.gcube.applicationsupportlayer.social.NotificationsManager; +import org.gcube.applicationsupportlayer.social.ex.ApplicationProfileNotFoundException; +import org.gcube.applicationsupportlayer.social.shared.SocialNetworkingSite; +import org.gcube.applicationsupportlayer.social.shared.SocialNetworkingUser; +import org.gcube.common.authorization.library.utils.Caller; +import org.gcube.common.resources.gcore.utils.XPathHelper; +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.common.scope.impl.ScopeBean; +import org.gcube.portal.databook.shared.ApplicationProfile; +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.ex.FeedIDNotFoundException; +import org.gcube.portal.notifications.thread.PostNotificationsThread; +import org.gcube.portal.social.networking.caches.SocialNetworkingSiteFinder; +import org.gcube.portal.social.networking.liferay.ws.GroupManagerWSBuilder; +import org.gcube.portal.social.networking.liferay.ws.UserManagerWSBuilder; +import org.gcube.resources.discovery.client.api.DiscoveryClient; +import org.gcube.resources.discovery.client.queries.api.Query; +import org.gcube.resources.discovery.client.queries.impl.QueryBox; +import org.gcube.vomanagement.usermanagement.GroupManager; +import org.gcube.vomanagement.usermanagement.UserManager; +import org.gcube.vomanagement.usermanagement.model.GCubeUser; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Node; +import org.xml.sax.InputSource; + +/** + * Utility class. + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ +public class SocialUtils { + + // Logger + private static final org.slf4j.Logger logger = LoggerFactory.getLogger(SocialUtils.class); + + public final static String NO_TEXT_FILE_SHARE = "_N0_73X7_SH4R3_"; + + // name of the portlet for vre notification + public static final String NEWS_FEED_PORTLET_CLASSNAME = "org.gcube.portlets.user.newsfeed.server.NewsServiceImpl"; + + /** + * Method used when an application needs to publish something. + * @param feedText + * @param uriParams + * @param previewTitle + * @param previewDescription + * @param httpImageUrl + * @return true upon success, false on failure + */ + public static Feed shareApplicationUpdate( + String postText, + String uriParams, + String previewTitle, + String previewDescription, + String httpImageUrl, + ApplicationProfile applicationProfile, + Caller caller, + boolean notifyGroup + ){ + + String escapedFeedText = org.gcube.social_networking.socialutillibrary.Utils.escapeHtmlAndTransformUrl(postText); + logger.info("Escaped post text is " + escapedFeedText); + String scope = ScopeProvider.instance.get(); + String appId = caller.getClient().getId(); + + List hashtags = org.gcube.social_networking.socialutillibrary.Utils.getHashTags(postText); + if (hashtags != null && !hashtags.isEmpty()) + escapedFeedText = org.gcube.social_networking.socialutillibrary.Utils.convertHashtagsAnchorHTML(escapedFeedText, hashtags); + + Feed toWrite = + buildFeed( + escapedFeedText, + uriParams == null ? "" : uriParams, + previewTitle == null ? "" : previewTitle, + previewDescription == null ? "" : previewDescription, + httpImageUrl == null ? "" : httpImageUrl, + applicationProfile, + scope); + + // try to save it + boolean res = CassandraConnection.getInstance().getDatabookStore().saveAppFeed(toWrite); + + if(res){ + logger.info("Feed correctly written by application " + appId); + + try { + CassandraConnection.getInstance().getDatabookStore().saveHashTags(toWrite.getKey(), scope, hashtags); + } catch (FeedIDNotFoundException e1) { + logger.error("Failed to save hashtags in Cassandra", e1); + } + + if(notifyGroup){ + + logger.debug("Sending notifications for " + appId + " " + scope); + + try{ + + String name = new ScopeBean(scope).name(); // scope such as devVRE + + // retrieve group information + GroupManager gManager = GroupManagerWSBuilder.getInstance().getGroupManager(); + + long groupId = gManager.getGroupId(name); + String groupName = gManager.getGroup(groupId).getGroupName(); + + logger.debug("Company id and name " + groupId + " " + groupName); + + // build the notification manager + SocialNetworkingSite site = SocialNetworkingSiteFinder.getSocialNetworkingSiteFromScope(ScopeProvider.instance.get()); + SocialNetworkingUser user = new SocialNetworkingUser(appId, "", applicationProfile.getName(), applicationProfile.getImageUrl()); + NotificationsManager nm = new ApplicationNotificationsManager( + UserManagerWSBuilder.getInstance().getUserManager(), + site, + scope, + user, + NEWS_FEED_PORTLET_CLASSNAME); + + // start notification thread + new Thread(new PostNotificationsThread( + UserManagerWSBuilder.getInstance().getUserManager(), + toWrite.getKey(), + toWrite.getDescription(), + ""+groupId, + nm, + new HashSet(hashtags), + new HashSet()) + ).start(); + + }catch (Exception e) { + logger.debug("Feed succesfully created but unable to send notifications."); + } + + } + return toWrite; + } + else + return null; + + } + + /** + * Build an ApplicationProfile Feed. + * + * @param description add a description for the update you are sharing + * @param uriParams the additional parameters your applicationProfile needs to open the subject of this update e.g. id=12345&type=foo + * @param previewTitle the title to show in the preview + * @param previewDescription the description to show in the preview + * @param previewThumbnailUrl the image url to show in the preview + * @return a feed instance ready to be written + */ + private static Feed buildFeed( + String description, + String uriParams, + String previewTitle, + String previewDescription, + String previewThumbnailUrl, + ApplicationProfile applicationProfile, + String scopeApp) { + + String uri = applicationProfile.getUrl(); + + //add the GET params if necessary + if (uriParams != null && uriParams.compareTo("") != 0) + uri += "?"+uriParams; + + Feed toReturn = new Feed( + UUID.randomUUID().toString(), + FeedType.PUBLISH, + applicationProfile.getKey(), + new Date(), + scopeApp, + uri, + previewThumbnailUrl, + description, + PrivacyLevel.SINGLE_VRE, + applicationProfile.getName(), + "no-email", + applicationProfile.getImageUrl(), + previewTitle, + previewDescription, + "", + true); + + return toReturn; + } + + /** + * This method looks up the applicationProfile profile among the ones available in the infrastructure + * @param idApp as identifier of your application (as reported in the ApplicationProfile) + * @param scopeApp the scope of the application + */ + public static ApplicationProfile getProfileFromInfrastrucure(String idApp, String scopeApp) { + ScopeBean scope = new ScopeBean(scopeApp); + + logger.debug("Trying to fetch applicationProfile profile from the infrastructure for " + idApp + " scope: " + scope); + + // set the scope of the root infrastructure + String rootInfrastructure = scopeApp.split("/")[1]; + ScopeProvider.instance.set("/"+rootInfrastructure); + + try { + + ApplicationProfile toReturn = new ApplicationProfile(); + Query q = new QueryBox("for $profile in collection('/db/Profiles/GenericResource')//Resource " + + "where $profile/Profile/SecondaryType/string() eq 'ApplicationProfile' and $profile/Profile/Body/AppId/string() " + + " eq '" + idApp + "'" + + "return $profile"); + + + DiscoveryClient client = client(); + List appProfile = client.submit(q); + + if (appProfile == null || appProfile.size() == 0) + throw new Exception("Your applicationProfile is not registered in the infrastructure"); + + else { + + String elem = appProfile.get(0); + DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Node node = docBuilder.parse(new InputSource(new StringReader(elem))).getDocumentElement(); + XPathHelper helper = new XPathHelper(node); + + List currValue = null; + currValue = helper.evaluate("/Resource/Profile/Name/text()"); + if (currValue != null && currValue.size() > 0) { + + toReturn.setName(currValue.get(0)); + + } + else throw new ApplicationProfileNotFoundException("Your applicationProfile NAME was not found in the profile"); + + currValue = helper.evaluate("/Resource/Profile/Description/text()"); + if (currValue != null && currValue.size() > 0) { + + toReturn.setDescription(currValue.get(0)); + + } + else logger.warn("No Description exists for " + toReturn.getName()); + + currValue = helper.evaluate("/Resource/Profile/Body/AppId/text()"); + + if (currValue != null && currValue.size() > 0) { + + toReturn.setKey(currValue.get(0)); + + } + else throw new ApplicationProfileNotFoundException("Your applicationProfile ID n was not found in the profile, consider adding element in "); + + currValue = helper.evaluate("/Resource/Profile/Body/ThumbnailURL/text()"); + if (currValue != null && currValue.size() > 0) { + + toReturn.setImageUrl(currValue.get(0)); + + } + else throw new Exception("Your applicationProfile Image Url was not found in the profile, consider adding element in "); + currValue = helper.evaluate("/Resource/Profile/Body/EndPoint/Scope/text()"); + if (currValue != null && currValue.size() > 0) { + + List scopes = currValue; + boolean foundUrl = false; + for (int i = 0; i < scopes.size(); i++) { + if (currValue.get(i).trim().compareTo(scope.toString()) == 0) { + toReturn.setUrl(helper.evaluate("/Resource/Profile/Body/EndPoint/URL/text()").get(i)); + toReturn.setScope(scope.toString()); + foundUrl = true; + break; + } + } + + if (! foundUrl) + throw new ApplicationProfileNotFoundException("Your applicationProfile URL was not found in the profile for Scope: " + scope.toString()); + } + + else throw new ApplicationProfileNotFoundException("Your applicationProfile EndPoint was not found in the profile, consider adding element in "); + logger.debug("Returning " + toReturn); + return toReturn; + } + + } catch (Exception e) { + + logger.error("Error while trying to fetch applicationProfile profile from the infrastructure", e); + + } finally{ + + // set the scope back + ScopeProvider.instance.set(scopeApp); + + } + + return null; + + } + + /** + * Allows user to post a feed in a certain vre. + * @param userId + * @param postText + * @param vreId + * @param previewTitle + * @param previewDescription + * @param previewHost + * @param previewUrl + * @param urlThumbnail + * @param notifyGroup + * @return The written Feed + */ + public static Feed shareUserUpdate( + String userId, + String postText, + String vreId, + String previewTitle, + String previewDescription, + String previewHost, + String previewUrl, + String urlThumbnail, + boolean notifyGroup + ) { + + String escapedFeedText = org.gcube.social_networking.socialutillibrary.Utils.escapeHtmlAndTransformUrl(postText); + + List hashtags = org.gcube.social_networking.socialutillibrary.Utils.getHashTags(postText); + if (hashtags != null && !hashtags.isEmpty()) + escapedFeedText = org.gcube.social_networking.socialutillibrary.Utils.convertHashtagsAnchorHTML(escapedFeedText, hashtags); + + GCubeUser user; + + // retrieve group information + UserManager uManager = UserManagerWSBuilder.getInstance().getUserManager(); + try{ + + user = uManager.getUserByUsername(userId); + + }catch(Exception e){ + + logger.error("Unable to get user informations, feed write fails.", e); + return null; + + } + + String email = user.getEmail(); + String fullName = user.getFirstName() + " " + user.getLastName(); + String thumbnailURL = user.getUserAvatarURL(); + + String linkTitle = previewTitle == null ? "" : previewTitle; + String linkDesc = previewDescription == null ? "" : previewDescription; + String host = previewHost == null ? "" : previewHost; + String url = previewUrl == null ? "" : previewUrl; + if (urlThumbnail == null) + urlThumbnail = "null"; + + //this means the user has shared a file without text in it. + String textToPost = ""; + if (escapedFeedText.trim().compareTo(NO_TEXT_FILE_SHARE) == 0) { + textToPost = org.gcube.social_networking.socialutillibrary.Utils.convertFileNameAnchorHTML(url); + } else { + textToPost = escapedFeedText; + } + + Feed toShare = new Feed(UUID.randomUUID().toString(), FeedType.PUBLISH, userId, new Date(), + vreId, url, urlThumbnail, textToPost, PrivacyLevel.SINGLE_VRE, fullName, email, thumbnailURL, linkTitle, linkDesc, host); + + logger.info("Attempting to save Feed with text: " + textToPost + " Level = " + PrivacyLevel.SINGLE_VRE + " Timeline = " + vreId); + + boolean result = CassandraConnection.getInstance().getDatabookStore().saveUserFeed(toShare); + + if(vreId != null && vreId.compareTo("") != 0 && result) { + + logger.trace("Attempting to write onto " + vreId); + + try { + + try{ + logger.info("Sleeping waiting for cassandra's update"); + Thread.sleep(1000); + + }catch(Exception e){ + + logger.error(e.toString()); + + } + CassandraConnection.getInstance().getDatabookStore().saveFeedToVRETimeline(toShare.getKey(), vreId); + + if (hashtags != null && !hashtags.isEmpty()) + CassandraConnection.getInstance().getDatabookStore().saveHashTags(toShare.getKey(), vreId, hashtags); + + } catch (FeedIDNotFoundException e) { + + logger.error("Error writing onto VRES Time Line" + vreId); + } + + logger.trace("Success writing onto " + vreId); + } + + if (!result) + return null; + + //send the notification about this posts to everyone in the group if notifyGroup is true + if (vreId != null && vreId.compareTo("") != 0 && notifyGroup) { + + try{ + + // get the site from the http request + SocialNetworkingSite site = SocialNetworkingSiteFinder.getSocialNetworkingSiteFromScope(ScopeProvider.instance.get()); + + // retrieve group information + GroupManager gManager = GroupManagerWSBuilder.getInstance().getGroupManager(); + + GCubeUser userInfo = uManager.getUserByUsername(userId); + SocialNetworkingUser socialUser = + new SocialNetworkingUser(userId, userInfo.getEmail(), userInfo.getFullname(), userInfo.getUserAvatarURL()); + + + // handle the scope + String name = new ScopeBean(vreId).name(); // scope such as devVR + long groupId = gManager.getGroupId(name); + String groupName = gManager.getGroup(groupId).getGroupName(); + + logger.debug("Company id and name " + groupId + " " + groupName); + + NotificationsManager nm = new ApplicationNotificationsManager(UserManagerWSBuilder.getInstance().getUserManager(), site, vreId, socialUser, NEWS_FEED_PORTLET_CLASSNAME); + new Thread( + new PostNotificationsThread( + UserManagerWSBuilder.getInstance().getUserManager(), + toShare.getKey(), + toShare.getDescription(), + ""+groupId, + nm, + new HashSet(), + new HashSet(hashtags)) + ).start(); + + logger.debug("Start sending notifications for feed written by " + userId); + }catch(Exception e){ + logger.error("Unable to notify users", e); + } + } + return toShare; + } +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/utils/TokensUtils.java b/src/main/java/org/gcube/portal/social/networking/ws/utils/TokensUtils.java new file mode 100644 index 0000000..55f8322 --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/utils/TokensUtils.java @@ -0,0 +1,76 @@ +package org.gcube.portal.social.networking.ws.utils; + +import org.gcube.common.authorization.library.ClientType; +import org.gcube.common.authorization.library.utils.Caller; + + +/** + * Tokens utils methods + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ +public class TokensUtils { + + // a user context token (not qualified) has as qualifier the word "TOKEN" + private static final String DEFAULT_QUALIFIER_USER_TOKEN = "TOKEN"; + + /** + * Check if it is a service token + * @return a boolean value + */ + public static boolean isServiceToken(Caller caller){ + + return caller.getClient().getType().equals(ClientType.SERVICE); + + } + + /** + * Check if it is an application token + * @return a boolean value + */ + public static boolean isApplicationToken(Caller caller){ + + return caller.getClient().getType().equals(ClientType.EXTERNALSERVICE); + + } + + /** + * Check if it is a container token + * @return a boolean value + */ + public static boolean isContainerToken(Caller caller){ + + return caller.getClient().getType().equals(ClientType.CONTAINER); + + } + + /** + * Check if it is a user token + * @return a boolean value + */ + public static boolean isUserToken(Caller caller){ + + return caller.getClient().getType().equals(ClientType.USER); + + } + + /** + * Check if it is a user token (not qualified) + * @return a boolean value + */ + public static boolean isUserTokenDefault(Caller caller){ + + return caller.getClient().getType().equals(ClientType.USER) && caller.getTokenQualifier().equals(DEFAULT_QUALIFIER_USER_TOKEN); + + } + + /** + * Check if it is a user token (qualified) + * @return a boolean value + */ + public static boolean isUserTokenQualified(Caller caller){ + + return caller.getClient().getType().equals(ClientType.USER) && !caller.getTokenQualifier().equals(DEFAULT_QUALIFIER_USER_TOKEN); + + } + +} diff --git a/src/main/java/org/gcube/portal/social/networking/ws/utils/UserProfileExtendedWithVerifiedEmail.java b/src/main/java/org/gcube/portal/social/networking/ws/utils/UserProfileExtendedWithVerifiedEmail.java new file mode 100644 index 0000000..3c7588f --- /dev/null +++ b/src/main/java/org/gcube/portal/social/networking/ws/utils/UserProfileExtendedWithVerifiedEmail.java @@ -0,0 +1,194 @@ +package org.gcube.portal.social.networking.ws.utils; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class UserProfileExtendedWithVerifiedEmail { + + @JsonProperty("id") + private String username; + + @JsonProperty("roles") + private List roles; + + @JsonProperty("picture") + private String avatar; + + @JsonProperty("name") + private String fullname; + + @JsonProperty("middle_name") + private String middleName; + + @JsonProperty("male") + private boolean male; + + @JsonProperty("location_industry") + private String locationIndustry; + + @JsonProperty("given_name") + private String firstName; + + @JsonProperty("email") + private String email; + + @JsonProperty("job_title") + private String jobTitle; + + @JsonProperty("family_name") + private String lastName; + + @JsonProperty("verified_email") + private boolean verifiedEmail = true; + + public UserProfileExtendedWithVerifiedEmail() { + super(); + } + + /** + * @param username + * @param roles + * @param avatar + * @param fullname + */ + public UserProfileExtendedWithVerifiedEmail(String username, List roles, String avatar, String fullname) { + this.username = username; + this.roles = roles; + this.avatar = avatar; + this.fullname = fullname; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + public String getAvatar() { + return avatar; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getFullname() { + return fullname; + } + + public void setFullname(String fullname) { + this.fullname = fullname; + } + + public String getMiddleName() { + return middleName; + } + + public void setMiddleName(String middleName) { + this.middleName = middleName; + } + + public boolean isMale() { + return male; + } + + public void setMale(boolean male) { + this.male = male; + } + + public String getLocationIndustry() { + return locationIndustry; + } + + public void setLocationIndustry(String locationIndustry) { + this.locationIndustry = locationIndustry; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getJobTitle() { + return jobTitle; + } + + public void setJobTitle(String jobTitle) { + this.jobTitle = jobTitle; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public boolean isVerifiedEmail() { + return verifiedEmail; + } + + public void setVerifiedEmail(boolean verifiedEmail) { + this.verifiedEmail = verifiedEmail; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("UserProfileExtendedWithVerifiedEmail [username="); + builder.append(username); + builder.append(", roles="); + builder.append(roles); + builder.append(", avatar="); + builder.append(avatar); + builder.append(", fullname="); + builder.append(fullname); + builder.append(", middleName="); + builder.append(middleName); + builder.append(", male="); + builder.append(male); + builder.append(", locationIndustry="); + builder.append(locationIndustry); + builder.append(", firstName="); + builder.append(firstName); + builder.append(", email="); + builder.append(email); + builder.append(", jobTitle="); + builder.append(jobTitle); + builder.append(", lastName="); + builder.append(lastName); + builder.append(", verifiedEmail="); + builder.append(verifiedEmail); + builder.append("]"); + return builder.toString(); + } + + + + +} diff --git a/src/main/resources/config.properties b/src/main/resources/config.properties new file mode 100644 index 0000000..01c7a5b --- /dev/null +++ b/src/main/resources/config.properties @@ -0,0 +1,4 @@ +#These properties are used to build up the SocialNetworkingSite object to send notifications +PROD_FALLBACK_GATEWAY=D4Science.org Gateway +DEV_FALLBACK_GATEWAY=gCube Dev4 Snapshot Gateway +PREPROD_FALLBACK_GATEWAY=gCube Preprod (dev) Gateway \ No newline at end of file diff --git a/src/main/resources/ehcache.xml b/src/main/resources/ehcache.xml new file mode 100644 index 0000000..e00e3ad --- /dev/null +++ b/src/main/resources/ehcache.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/gcube-app.xml b/src/main/webapp/WEB-INF/gcube-app.xml new file mode 100644 index 0000000..27ca951 --- /dev/null +++ b/src/main/webapp/WEB-INF/gcube-app.xml @@ -0,0 +1,11 @@ + + SocialNetworking + Portal + 2.1.0-SNAPSHOT + SocialNetworking Service + + /rest/ + /rest/swagger.yaml + /rest/swagger.json + /rest/application.wadl + \ 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..9a8bd29 --- /dev/null +++ b/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,49 @@ + + + + Restful Web Application + + jersey-servlet + org.glassfish.jersey.servlet.ServletContainer + + jersey.config.beanValidation.enableOutputValidationErrorEntity.server + true + + + jersey.config.server.provider.packages + + io.swagger.jaxrs.listing, + org.gcube.portal.social.networking + + + 1 + + + + SwaggerBootstrap + org.gcube.portal.social.networking.swagger.config.Bootstrap + 2 + + + + jersey-servlet + /rest/* + + + + The token of the user J.A.R.V.I.S. on the portal (root context) + NOTIFIER_TOKEN + + + + + org.gcube.portal.social.networking.liferay.ws.ServletContextClass + + + + index.jsp + + diff --git a/src/main/webapp/index.jsp b/src/main/webapp/index.jsp new file mode 100644 index 0000000..12ec4fa --- /dev/null +++ b/src/main/webapp/index.jsp @@ -0,0 +1,5 @@ + + +

The social networking web service is up and running!

+ + diff --git a/src/test/java/org/gcube/portal/test/GcoreEndpointReader.java b/src/test/java/org/gcube/portal/test/GcoreEndpointReader.java new file mode 100644 index 0000000..6185e66 --- /dev/null +++ b/src/test/java/org/gcube/portal/test/GcoreEndpointReader.java @@ -0,0 +1,78 @@ +/** + * + */ +package org.gcube.portal.test; + +import static org.gcube.resources.discovery.icclient.ICFactory.client; +import static org.gcube.resources.discovery.icclient.ICFactory.queryFor; + +import java.util.List; + +import org.gcube.common.resources.gcore.GCoreEndpoint; +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.resources.discovery.client.api.DiscoveryClient; +import org.gcube.resources.discovery.client.queries.api.SimpleQuery; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class GcoreEndpointReader { + + private static final String resource = "jersey-servlet"; + private static final String serviceName = "SocialNetworking"; + private static final String serviceClass = "Portal"; + + private static Logger logger = LoggerFactory.getLogger(GcoreEndpointReader.class); + private String resourceEntyName; + + /** + * Instantiates a new gcore endpoint reader. + * + * @param scope the scope + * @throws Exception the exception + */ + public GcoreEndpointReader(String scope) throws Exception { + + String currentScope = ScopeProvider.instance.get(); + + try{ + + logger.info("set scope "+scope); + ScopeProvider.instance.set(scope); + + SimpleQuery query = queryFor(GCoreEndpoint.class); + query.addCondition(String.format("$resource/Profile/ServiceClass/text() eq '%s'",serviceClass)); + query.addCondition("$resource/Profile/DeploymentData/Status/text() eq 'ready'"); + query.addCondition(String.format("$resource/Profile/ServiceName/text() eq '%s'",serviceName)); + query.setResult("$resource/Profile/AccessPoint/RunningInstanceInterfaces//Endpoint[@EntryName/string() eq \""+resource+"\"]/text()"); + + logger.debug("submitting quey "+query.toString()); + + DiscoveryClient client = client(); + List endpoints = client.submit(query); + if (endpoints == null || endpoints.isEmpty()) throw new Exception("Cannot retrieve the GCoreEndpoint serviceName: "+serviceName +", serviceClass: " +serviceClass +", in scope: "+scope); + + + this.resourceEntyName = endpoints.get(0); + if(resourceEntyName==null) + throw new Exception("Endpoint:"+resource+", is null for serviceName: "+serviceName +", serviceClass: " +serviceClass +", in scope: "+scope); + + logger.info("found entyname "+resourceEntyName+" for ckanResource: "+resource); + + }catch(Exception e){ + String error = "An error occurred during GCoreEndpoint discovery, serviceName: "+serviceName +", serviceClass: " +serviceClass +", in scope: "+scope +"."; + logger.error(error, e); + throw new Exception(error); + }finally{ + if(currentScope != null) + ScopeProvider.instance.set(currentScope); + } + } + + /** + * @return the ResourceEntyName + */ + public String getResourceEntyName() { + + return resourceEntyName; + } +} diff --git a/src/test/java/org/gcube/portal/test/JTests.java b/src/test/java/org/gcube/portal/test/JTests.java new file mode 100644 index 0000000..3d3a2c1 --- /dev/null +++ b/src/test/java/org/gcube/portal/test/JTests.java @@ -0,0 +1,201 @@ +package org.gcube.portal.test; + + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; + +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.AuthCache; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.auth.BasicScheme; +import org.apache.http.impl.client.BasicAuthCache; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.portal.social.networking.liferay.ws.LiferayJSONWsCredentials; +import org.gcube.portal.social.networking.ws.inputs.MessageInputBean; +import org.gcube.portal.social.networking.ws.inputs.Recipient; +import org.json.simple.JSONObject; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class JTests { + + private static final String YOUR_TOKEN_HERE = ""; + private static final String METHOD = "messages/writeMessageToUsers"; + private static final String SCOPE = "/gcube"; + + //@Test + public void readSocialServiceEndPoint() throws Exception { + + String findInContext = SCOPE; + ScopeProvider.instance.set(findInContext); + + ServiceEndPointReaderSocial readerSE = new ServiceEndPointReaderSocial(findInContext); + System.out.println("Found base path " + readerSE.getBasePath()); + + } + + + //@Test + public void testWithApacheClient() throws Exception { + + ServiceEndPointReaderSocial reader = new ServiceEndPointReaderSocial(SCOPE); + String requestForMessage = reader.getBasePath() + METHOD + "?gcube-token=" + YOUR_TOKEN_HERE; + requestForMessage = requestForMessage.replace("http", "https"); // remove the port (or set it to 443) otherwise you get an SSL error + + System.out.println("Request url is going to be " + requestForMessage); + + try(CloseableHttpClient client = HttpClientBuilder.create().build();){ + + HttpPost postRequest = new HttpPost(requestForMessage); + + // put the sender, the recipients, subject and body of the mail here + StringEntity input = new StringEntity("sender=andrea.rossi&recipients=gianpaolo.coro&subject=Sample mail&body=Sample mail object"); + input.setContentType("application/x-www-form-urlencoded"); + postRequest.setEntity(input); + + HttpResponse response = client.execute(postRequest); + + System.out.println("Error is " + response.getStatusLine().getReasonPhrase()); + + if (response.getStatusLine().getStatusCode() != 201) { + throw new RuntimeException("Failed : HTTP error code : " + + response.getStatusLine().getStatusCode()); + } + + BufferedReader br = new BufferedReader( + new InputStreamReader((response.getEntity().getContent()))); + + String output; + System.out.println("Output from Server .... \n"); + while ((output = br.readLine()) != null) { + System.out.println(output); + } + + System.out.println(response.toString()); + + }catch(Exception e){ + System.err.println("error while performing post method " + e.toString()); + } + } + + //@Test + public void parserJSON() throws IOException{ + + MessageInputBean message = new MessageInputBean(); + message.setBody("a caso"); + message.setSubject("subject"); + ArrayList recipients = new ArrayList(); + Recipient recipient = new Recipient("recipient1"); + recipients.add(recipient); + message.setRecipients(recipients); + + + //Object mapper instance + ObjectMapper mapper = new ObjectMapper(); + + //Convert POJO to JSON + String json = mapper.writeValueAsString(message); + + MessageInputBean obje = mapper.readValue(json, MessageInputBean.class); + System.out.println(json); + System.out.println(obje); + + } + + //@Test + public void callLiferayWS() throws Exception{ + + HttpHost target = new HttpHost("localhost", 8080, "http"); + + CredentialsProvider credsProvider = new BasicCredentialsProvider(); + credsProvider.setCredentials( + new AuthScope(target.getHostName(), target.getPort()), + new UsernamePasswordCredentials("test@liferay.com", "random321")); + CloseableHttpClient httpclient = HttpClients.custom() + .setDefaultCredentialsProvider(credsProvider).build(); + try { + + // Create AuthCache instance + AuthCache authCache = new BasicAuthCache(); + // Generate BASIC scheme object and add it to the local + // auth cache + BasicScheme basicAuth = new BasicScheme(); + authCache.put(target, basicAuth); + + // Add AuthCache to the execution context + HttpClientContext localContext = HttpClientContext.create(); + localContext.setAuthCache(authCache); + + HttpGet httpget = new HttpGet("/api/jsonws" + "/user/get-user-by-screen-name/company-id/20155/screen-name/costantino.perciante"); + + System.out.println("Executing request " + httpget.getRequestLine() + " to target " + target); + CloseableHttpResponse response = httpclient.execute(target, httpget, localContext); + try { + System.out.println("----------------------------------------"); + System.out.println(response.getStatusLine()); + System.out.println(EntityUtils.toString(response.getEntity())); + } finally { + response.close(); + } + } finally { + httpclient.close(); + } + + } + + //@Test + public void retrieveCredentials(){ + + ScopeProvider.instance.set("/gcube"); + LiferayJSONWsCredentials cred = LiferayJSONWsCredentials.getSingleton(); + System.out.println("Password is " + cred.getPassword()); + System.out.println("Host is " + cred.getHost()); + + } + + //@Test + public void readGcoreEndPoint() throws Exception{ + + GcoreEndpointReader reader = new GcoreEndpointReader("/gcube"); + reader.getResourceEntyName(); + + } + + //@Test + public void sendNotification() throws ClientProtocolException, IOException{ + + String url ="https://socialnetworking-d-d4s.d4science.org/social-networking-library-ws/rest//2/notifications/notify-job-status?gcube-token=07f5f961-d0e0-4bc4-af90-a305e8b63ac7-98187548"; + CloseableHttpClient client = HttpClientBuilder.create().build(); + JSONObject obj = new JSONObject(); + obj.put("job_id", "bbbbb"); + obj.put("recipient", "costantino.perciante"); + obj.put("job_name", "aaaaaa"); + obj.put("service_name", "Test"); + obj.put("status", "SUCCEEDED"); + + HttpPost request = new HttpPost(url); + request.addHeader("Content-type", ContentType.APPLICATION_JSON.toString()); + StringEntity paramsEntity = new StringEntity(obj.toJSONString(), ContentType.APPLICATION_JSON); + request.setEntity(paramsEntity); + client.execute(request); + + } + +} diff --git a/src/test/java/org/gcube/portal/test/ServiceEndPointReaderSocial.java b/src/test/java/org/gcube/portal/test/ServiceEndPointReaderSocial.java new file mode 100644 index 0000000..a98a765 --- /dev/null +++ b/src/test/java/org/gcube/portal/test/ServiceEndPointReaderSocial.java @@ -0,0 +1,103 @@ +package org.gcube.portal.test; + +import static org.gcube.resources.discovery.icclient.ICFactory.clientFor; +import static org.gcube.resources.discovery.icclient.ICFactory.queryFor; + +import java.util.Iterator; +import java.util.List; + +import org.gcube.common.resources.gcore.ServiceEndpoint; +import org.gcube.common.resources.gcore.ServiceEndpoint.AccessPoint; +import org.gcube.common.scope.api.ScopeProvider; +import org.gcube.resources.discovery.client.api.DiscoveryClient; +import org.gcube.resources.discovery.client.queries.api.SimpleQuery; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Retrieves the base url of the social-networking service in the scope provided + * @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it) + */ +public class ServiceEndPointReaderSocial { + + private String basePath = null; + + private static Logger logger = LoggerFactory.getLogger(ServiceEndPointReaderSocial.class); + private final static String RUNTIME_RESOURCE_NAME = "SocialNetworking"; + private final static String CATEGORY = "Portal"; + + public ServiceEndPointReaderSocial(String context){ + + if(context == null || context.isEmpty()) + throw new IllegalArgumentException("A valid context is needed to discover the service"); + + + String oldContext = ScopeProvider.instance.get(); + ScopeProvider.instance.set(context); + + try{ + + List resources = getConfigurationFromIS(); + if (resources.size() == 0){ + logger.error("There is no Runtime Resource having name " + RUNTIME_RESOURCE_NAME +" and Category " + CATEGORY + " in this scope."); + throw new Exception("There is no Runtime Resource having name " + RUNTIME_RESOURCE_NAME +" and Category " + CATEGORY + " in this scope."); + } + else { + + for (ServiceEndpoint res : resources) { + + Iterator accessPointIterator = res.profile().accessPoints().iterator(); + + while (accessPointIterator.hasNext()) { + ServiceEndpoint.AccessPoint accessPoint = (ServiceEndpoint.AccessPoint) accessPointIterator + .next(); + + // get base path + basePath = accessPoint.address(); + + // break + break; + } + } + + } + + }catch(Exception e){ + + logger.error("Unable to retrieve such service endpoint information!", e); + + }finally{ + + if(oldContext != null && !oldContext.equals(context)) + ScopeProvider.instance.set(oldContext); + + } + + logger.info("Found base path " + basePath + " for the service"); + + } + + /** + * Retrieve endpoints information from IS for DB + * @return list of endpoints for ckan database + * @throws Exception + */ + private List getConfigurationFromIS() throws Exception{ + + SimpleQuery query = queryFor(ServiceEndpoint.class); + query.addCondition("$resource/Profile/Name/text() eq '"+ RUNTIME_RESOURCE_NAME +"'"); + query.addCondition("$resource/Profile/Category/text() eq '"+ CATEGORY +"'"); + DiscoveryClient client = clientFor(ServiceEndpoint.class); + List toReturn = client.submit(query); + return toReturn; + + } + + /** + * Get the base path of the social networking service + * @return + */ + public String getBasePath() { + return basePath; + } +}