partial implementation (reads)

This commit is contained in:
Massimiliano Assante 2022-09-14 16:26:17 +02:00
parent ea37bcb93c
commit 2c2a68b580
5 changed files with 242 additions and 37 deletions

View File

@ -1,8 +1,12 @@
# Changelog
## [v2.7.1-SNAPSHOT] - 2022-09-14
- - Feature #23847 Social service: temporarily block of notifications for given username(s)
## [v2.7.0] - 2022-09-12
- Sphinx documentation added
- Sphinx documentation added
## [v2.6.2] - 2022-07-27

42
pom.xml
View File

@ -12,7 +12,7 @@
<groupId>org.gcube.portal</groupId>
<artifactId>social-networking-library-ws</artifactId>
<packaging>war</packaging>
<version>2.7.0</version>
<version>2.7.1-SNAPSHOT]</version>
<name>social-networking-library-ws</name>
<description>Rest interface for the social networking library.</description>
<properties>
@ -39,6 +39,11 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.2</version>
</dependency>
</dependencies>
</dependencyManagement>
@ -73,6 +78,16 @@
<artifactId>ehcache</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>net.spy</groupId>
<artifactId>spymemcached</artifactId>
<version>2.12.3</version>
</dependency>
<dependency>
<groupId>org.gcube.common</groupId>
<artifactId>authorization-control-library</artifactId>
<version>[1.0.1,2.0.0-SNAPSHOT)</version>
</dependency>
<dependency>
<groupId>org.gcube.common.portal</groupId>
<artifactId>portal-manager</artifactId>
@ -301,7 +316,6 @@
<build>
<plugins>
<!-- SPHINX PLUGIN triggered at 'compile' -->
<plugin>
<groupId>kr.motd.maven</groupId>
<artifactId>sphinx-maven-plugin</artifactId>
@ -348,7 +362,29 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.7</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
<aspectLibraries>
<aspectLibrary>
<groupId>org.gcube.common</groupId>
<artifactId>authorization-control-library</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Copy Enunciate Documentation from your-application/docs to your-application.war -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>

View File

@ -0,0 +1,16 @@
package org.gcube.portal.social.networking.ws.ex;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response.Status;
public class AuthException extends WebApplicationException {
/**
*
*/
private static final long serialVersionUID = 1L;
public AuthException(Throwable cause) {
super(cause, Status.FORBIDDEN);
}
}

View File

@ -26,6 +26,7 @@ import org.gcube.applicationsupportlayer.social.NotificationsManager;
import org.gcube.applicationsupportlayer.social.ScopeBeanExt;
import org.gcube.applicationsupportlayer.social.shared.SocialNetworkingSite;
import org.gcube.applicationsupportlayer.social.shared.SocialNetworkingUser;
import org.gcube.common.authorization.control.annotations.AuthorizationControl;
import org.gcube.common.authorization.library.provider.AuthorizationProvider;
import org.gcube.common.authorization.library.utils.Caller;
import org.gcube.common.scope.api.ScopeProvider;
@ -40,11 +41,13 @@ 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.LiferayJSONWsCredentials;
import org.gcube.portal.social.networking.liferay.ws.UserManagerWSBuilder;
import org.gcube.portal.social.networking.ws.ex.AuthException;
import org.gcube.portal.social.networking.ws.mappers.CatalogueEventTypeMapper;
import org.gcube.portal.social.networking.ws.mappers.JobMapper;
import org.gcube.portal.social.networking.ws.mappers.WorkspaceItemMapper;
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.DistributedCacheClient;
import org.gcube.portal.social.networking.ws.utils.ErrorMessages;
import org.gcube.social_networking.socialnetworking.model.beans.JobNotificationBean;
@ -72,6 +75,8 @@ import com.webcohesion.enunciate.metadata.rs.RequestHeaders;
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import net.spy.memcached.MemcachedClient;
/**
* REST interface for the social networking library (notifications).
*/
@ -84,6 +89,7 @@ public class Notifications {
// Logger
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Notifications.class);
private static final String INFRASTRUCTURE_MANAGER_ROLE = "Infrastructure-Manager";
@GET
@Path("get-range-notifications/")
@ -130,6 +136,47 @@ public class Notifications {
return Response.status(status).entity(responseBean).build();
}
@GET
@Path("is-user-disabled/")
@Produces(MediaType.APPLICATION_JSON)
/**
* Return whether the notifications for this user are enabled or not
* @param from must be greater or equal to 1, range[0, infinity]
* @param quantity quantity must be greater or equal to 0
* @return notifications up to quantity
* @throws ValidationException
*/
@StatusCodes ({
@ResponseCode ( code = 200, condition = "Users with silenced notifications are reported in the 'result' field of the returned object"),
@ResponseCode ( code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT)
})
@AuthorizationControl(allowedRoles={INFRASTRUCTURE_MANAGER_ROLE}, exception=AuthException.class)
public Response isUserDisabled(String usernameToCheck) throws ValidationException{
ResponseBean responseBean = new ResponseBean();
Status status = Status.OK;
try{
Boolean userDisabled= !isNotificationEnabled(usernameToCheck);
responseBean.setResult(userDisabled);
responseBean.setSuccess(true);
logger.debug("are User " + usernameToCheck + " Notifications Disabled?"+userDisabled);
}catch(Exception e){
logger.error("Unable to read whether the notifications for this user are enabled or not.", e);
responseBean.setMessage(e.getMessage());
responseBean.setSuccess(false);
status = Status.INTERNAL_SERVER_ERROR;
}
return Response.status(status).entity(responseBean).build();
}
private boolean isNotificationEnabled(String usernameToCheck) {
MemcachedClient entries = new DistributedCacheClient().getMemcachedClient();
Boolean userEnabled = false;
if(entries.get(usernameToCheck) == null)
userEnabled = true;
return userEnabled;
}
/**
* Send a JOB notification to a given recipient
* @param job The job bean
@ -225,26 +272,28 @@ public class Notifications {
if (! event.idsAsGroup()) {
for (int i = 0; i < idsToNotify.length; i++) {
String userIdToNotify = idsToNotify[i];
String username2Notify = null;
try {
username2Notify = um.getUserByUsername(userIdToNotify).getUsername();
}
catch (Exception e) {
status = Status.BAD_REQUEST;
logger.error("Username not found", e);
responseBean.setSuccess(false);
responseBean.setMessage("Username not found, got: " + userIdToNotify);
return Response.status(status).entity(responseBean).build();
}
deliveryResult =
if (isNotificationEnabled(userIdToNotify)) {
String username2Notify = null;
try {
username2Notify = um.getUserByUsername(userIdToNotify).getUsername();
}
catch (Exception e) {
status = Status.BAD_REQUEST;
logger.error("Username not found", e);
responseBean.setSuccess(false);
responseBean.setMessage("Username not found, got: " + userIdToNotify);
return Response.status(status).entity(responseBean).build();
}
deliveryResult =
nm.notifyCatalogueEvent(
CatalogueEventTypeMapper.getType(event.getType()),
username2Notify,
event.getItemId(),
event.getNotifyText(),
event.getItemURL());
}
}
} else { //the ids are contexts
for (int i = 0; i < idsToNotify.length; i++) {
@ -261,13 +310,15 @@ public class Notifications {
String[] userIdsToNotify = getUsernamesByContext(scope).toArray(new String[0]); //resolve the members
for (int j = 0; j < userIdsToNotify.length; j++) {
String userIdToNotify = userIdsToNotify[j];
deliveryResult =
nm.notifyCatalogueEvent(
CatalogueEventTypeMapper.getType(event.getType()),
userIdToNotify,
event.getItemId(),
event.getNotifyText(),
event.getItemURL());
if (isNotificationEnabled(userIdToNotify)) {
deliveryResult =
nm.notifyCatalogueEvent(
CatalogueEventTypeMapper.getType(event.getType()),
userIdToNotify,
event.getItemId(),
event.getNotifyText(),
event.getItemURL());
}
}
}
}
@ -338,18 +389,20 @@ public class Notifications {
if (! event.idsAsGroup()) {
for (int i = 0; i < idsToNotify.length; i++) {
String userIdToNotify = idsToNotify[i];
String username2Notify = "";
try {
username2Notify = um.getUserByUsername(userIdToNotify).getUsername();
if (isNotificationEnabled(userIdToNotify)) {
String username2Notify = "";
try {
username2Notify = um.getUserByUsername(userIdToNotify).getUsername();
}
catch (Exception e) {
status = Status.NOT_ACCEPTABLE;
logger.error("Username not found", e);
responseBean.setSuccess(false);
responseBean.setMessage("Username not found, received: " + userIdToNotify);
return Response.status(status).entity(responseBean).build();
}
deliveryResult = notifyWorkspaceEvent(event, nm, username2Notify);
}
catch (Exception e) {
status = Status.NOT_ACCEPTABLE;
logger.error("Username not found", e);
responseBean.setSuccess(false);
responseBean.setMessage("Username not found, received: " + userIdToNotify);
return Response.status(status).entity(responseBean).build();
}
deliveryResult = notifyWorkspaceEvent(event, nm, username2Notify);
}
} else { //the ids are contexts
for (int i = 0; i < idsToNotify.length; i++) {
@ -366,7 +419,8 @@ public class Notifications {
String[] userIdsToNotify = getUsernamesByContext(scope).toArray(new String[0]); //resolve the members
for (int j = 0; j < userIdsToNotify.length; j++) {
String userIdToNotify = userIdsToNotify[j];
deliveryResult = notifyWorkspaceEvent(event, nm, userIdToNotify);
if (isNotificationEnabled(userIdToNotify))
deliveryResult = notifyWorkspaceEvent(event, nm, userIdToNotify);
}
}
}

View File

@ -0,0 +1,95 @@
package org.gcube.portal.social.networking.ws.utils;
import static org.gcube.resources.discovery.icclient.ICFactory.clientFor;
import static org.gcube.resources.discovery.icclient.ICFactory.queryFor;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import org.gcube.common.resources.gcore.ServiceEndpoint;
import org.gcube.common.resources.gcore.ServiceEndpoint.AccessPoint;
import org.gcube.common.resources.gcore.utils.Group;
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.LoggerFactory;
import net.spy.memcached.KetamaConnectionFactory;
import net.spy.memcached.MemcachedClient;
/**
* @author Massimiliano Assante at ISTI-CNR
*/
public class DistributedCacheClient {
// Logger
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(DistributedCacheClient.class);
private static final String MEMCACHED_RESOURCE_NAME = "Memcached";
private static final String CATEGORY = "Database";
private MemcachedClient mClient;
/**
* Build the singleton instance
*/
public DistributedCacheClient(){
List<InetSocketAddress> addrs = discoverHostOfServiceEndpoint();
try {
mClient = new MemcachedClient(new KetamaConnectionFactory(), addrs);
} catch (IOException e) {
e.printStackTrace();
}
}
public MemcachedClient getMemcachedClient() {
return mClient;
}
/**
* Retrieve endpoint resoruce from IS
* @return List of InetSocketAddresses
* @throws Exception
*/
private static List<InetSocketAddress> discoverHostOfServiceEndpoint(){
String currentScope = ScopeProvider.instance.get();
ApplicationContext ctx = ContextProvider.get(); // get this info from SmartGears
String infrastructure = "/"+ctx.container().configuration().infrastructure();
ScopeProvider.instance.set(infrastructure);
List<InetSocketAddress> toReturn = new ArrayList<InetSocketAddress>();
try{
SimpleQuery query = queryFor(ServiceEndpoint.class);
query.addCondition("$resource/Profile/Name/text() eq '"+ MEMCACHED_RESOURCE_NAME +"'");
query.addCondition("$resource/Profile/Category/text() eq '"+ CATEGORY +"'");
DiscoveryClient<ServiceEndpoint> client = clientFor(ServiceEndpoint.class);
List<ServiceEndpoint> ses = client.submit(query);
if (ses.isEmpty()) {
logger.error("There is no Memcached cluster having name: " + MEMCACHED_RESOURCE_NAME + " and Category " + CATEGORY + " on root context in this infrastructure: ");
return null;
}
for (ServiceEndpoint se : ses) {
Group<AccessPoint> aps = se.profile().accessPoints();
for (AccessPoint ap : aps.asCollection()) {
String address = ap.address(); //e.g. socialnetworking-d-d4s.d4science.org:11211
String[] splits = address.split(":");
String hostname = splits[0];
int port = Integer.parseInt(splits[1]);
toReturn.add(new InetSocketAddress(hostname, port));
}
break;
}
} catch(Exception e){
logger.error("Error while retrieving hosts for the Memcached cluster having name: " + MEMCACHED_RESOURCE_NAME + " and Category " + CATEGORY + " on root context");
}finally{
ScopeProvider.instance.set(currentScope);
}
ScopeProvider.instance.set(currentScope);
return toReturn;
}
}