402 lines
17 KiB
Java
402 lines
17 KiB
Java
package org.gcube.datacatalogue.grsf_manage_widget.server.manage;
|
|
|
|
import static org.gcube.resources.discovery.icclient.ICFactory.client;
|
|
import static org.gcube.resources.discovery.icclient.ICFactory.queryFor;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
|
|
import org.gcube.common.authorization.library.provider.SecurityTokenProvider;
|
|
import org.gcube.common.resources.gcore.GCoreEndpoint;
|
|
import org.gcube.common.scope.api.ScopeProvider;
|
|
import org.gcube.datacatalogue.ckanutillibrary.server.DataCatalogue;
|
|
import org.gcube.datacatalogue.common.Constants;
|
|
import org.gcube.datacatalogue.grsf_manage_widget.server.manage.RevertOperationUrl.Operation;
|
|
import org.gcube.datacatalogue.grsf_manage_widget.shared.ManageProductBean;
|
|
import org.gcube.resources.discovery.client.api.DiscoveryClient;
|
|
import org.gcube.resources.discovery.client.queries.api.SimpleQuery;
|
|
import org.gcube.vomanagement.usermanagement.RoleManager;
|
|
import org.gcube.vomanagement.usermanagement.UserManager;
|
|
import org.gcube.vomanagement.usermanagement.impl.LiferayRoleManager;
|
|
import org.gcube.vomanagement.usermanagement.impl.LiferayUserManager;
|
|
import org.gcube.vomanagement.usermanagement.model.GCubeTeam;
|
|
import org.json.simple.JSONArray;
|
|
import org.json.simple.JSONObject;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
|
import eu.trentorise.opendata.jackan.internal.org.apache.http.HttpEntity;
|
|
import eu.trentorise.opendata.jackan.internal.org.apache.http.HttpResponse;
|
|
import eu.trentorise.opendata.jackan.internal.org.apache.http.client.methods.HttpPost;
|
|
import eu.trentorise.opendata.jackan.internal.org.apache.http.entity.StringEntity;
|
|
import eu.trentorise.opendata.jackan.internal.org.apache.http.impl.client.CloseableHttpClient;
|
|
import eu.trentorise.opendata.jackan.internal.org.apache.http.impl.client.HttpClientBuilder;
|
|
import eu.trentorise.opendata.jackan.internal.org.apache.http.impl.client.LaxRedirectStrategy;
|
|
import eu.trentorise.opendata.jackan.internal.org.apache.http.util.EntityUtils;
|
|
|
|
|
|
/**
|
|
* For managing the different interactions with social channels (posts and mails)
|
|
* @author Costantino Perciante at ISTI-CNR (costantino.perciante@isti.cnr.it)
|
|
*/
|
|
public class SocialCommunications {
|
|
|
|
private static final Logger logger = LoggerFactory.getLogger(SocialCommunications.class);
|
|
|
|
// for discovering social networking service
|
|
private static final String resource = "jersey-servlet";
|
|
private static final String serviceName = "SocialNetworking";
|
|
private static final String serviceClass = "Portal";
|
|
|
|
// social operations
|
|
private static final String SOCIAL_SERVICE_APPLICATION_TOKEN = "/2/tokens/generate-application-token/";
|
|
private static final String SOCIAL_SERVICE_WRITE_APPLICATION_POST = "/2/posts/write-post-app/";
|
|
private static final String SOCIAL_SEND_EMAIL = "/2/messages/write-message/";
|
|
private static final String MEDIATYPE_JSON = "application/json";
|
|
|
|
// for writing a post in the GRSF admin context
|
|
private static final String APPLICATION_ID_CATALOGUE_MANAGER = "org.gcube.datacatalogue.GRSFNotifier";
|
|
|
|
// emails to be sent to editors and reviewers and post to be written into the grsf admin vre
|
|
private static final String POST_MESSAGE = "Dear members,"
|
|
+ "<br>The record 'PRODUCT_TITLE' has been just updated by USER_FULLNAME."
|
|
+ "<br>You can inspect it here: PRODUCT_URL<br>";
|
|
|
|
private static final String EMAIL_MESSAGE_REVIEWER = "Dear GRSF Reviewer,"
|
|
+ "<br>an update on the record named 'PRODUCT_TITLE' has been requested by USER_FULLNAME."
|
|
+ "<br>It is available here LINK_RECORD.";
|
|
|
|
private static final String EMAIL_MESSAGE_EDITOR = "Dear USER_FULLNAME,"
|
|
+ "<br>your request for the record 'PRODUCT_TITLE' has been accepted."
|
|
+ "<br>It is available here LINK_RECORD.";
|
|
|
|
private static final String REVERT_LINK_PIECE = "<br>The request involves a merge operation. You can reject the merge by exploiting this link LINK in the following 24 hours.";
|
|
|
|
/**
|
|
*
|
|
* @param context
|
|
* @return
|
|
*/
|
|
private static String getBaseUrlSocialService(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);
|
|
|
|
String basePath = null;
|
|
try{
|
|
|
|
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()");
|
|
|
|
DiscoveryClient<String> client = client();
|
|
List<String> endpoints = client.submit(query);
|
|
if (endpoints == null || endpoints.isEmpty())
|
|
throw new Exception("Cannot retrieve the GCoreEndpoint serviceName: "+serviceName +", serviceClass: " +serviceClass +", in scope: "+context);
|
|
|
|
|
|
basePath = endpoints.get(0);
|
|
if(basePath==null)
|
|
throw new Exception("Endpoint:"+resource+", is null for serviceName: "+serviceName +", serviceClass: " +serviceClass +", in scope: "+context);
|
|
|
|
}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");
|
|
return basePath;
|
|
}
|
|
|
|
/**
|
|
* Notify the users about the required changes.
|
|
* @param bean
|
|
* @param url
|
|
* @param username
|
|
* @param fullName
|
|
* @param hashtags
|
|
* @param enablePostNotification
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public static void writeProductPost(ManageProductBean bean, String username, String fullName, String report, boolean enablePostNotification){
|
|
|
|
// discover service endpoint for the social networking library
|
|
String currentScope = ScopeProvider.instance.get();
|
|
String tokenUser = SecurityTokenProvider.instance.get();
|
|
|
|
logger.info("Current scope for writeProductPost is " + currentScope + " and token is " + tokenUser.substring(0, 10) + "***************");
|
|
String basePath = getBaseUrlSocialService(currentScope);
|
|
|
|
if(basePath == null){
|
|
|
|
logger.error("Unable to write a post because there is no social networking service available");
|
|
|
|
}else{
|
|
|
|
basePath = basePath.endsWith("/") ? basePath : basePath + "/";
|
|
|
|
try(CloseableHttpClient client = HttpClientBuilder.create().setRedirectStrategy(new LaxRedirectStrategy()).build();){
|
|
|
|
// ask token application
|
|
HttpPost postRequest = new HttpPost(basePath + SOCIAL_SERVICE_APPLICATION_TOKEN + "?gcube-token=" + tokenUser);
|
|
StringEntity input = new StringEntity("{\"app_id\":\"" + APPLICATION_ID_CATALOGUE_MANAGER + "\"}");
|
|
input.setContentType(MEDIATYPE_JSON);
|
|
postRequest.setEntity(input);
|
|
HttpResponse response = client.execute(postRequest);
|
|
|
|
logger.debug("Url is " + basePath + SOCIAL_SERVICE_APPLICATION_TOKEN + "?gcube-token=" + tokenUser);
|
|
|
|
if (response.getStatusLine().getStatusCode() != 201) {
|
|
throw new RuntimeException("Failed to retrieve application token : HTTP error code : "
|
|
+ response.getStatusLine().getStatusCode());
|
|
}else{
|
|
|
|
Map<String, Object> mapResponseGeneratedToken = getResponseEntityAsJSON(response);
|
|
boolean successGeneratedToken = (boolean)mapResponseGeneratedToken.get("success");
|
|
if(!successGeneratedToken){
|
|
|
|
throw new RuntimeException("Failed to generate the token for the application!"
|
|
+ " Error message is " + mapResponseGeneratedToken.get("message"));
|
|
|
|
}else{
|
|
|
|
String applicationToken = (String)mapResponseGeneratedToken.get("result");
|
|
|
|
// replace
|
|
String message = POST_MESSAGE.replace("PRODUCT_TITLE", bean.getGrsfName()).replace("PRODUCT_URL", bean.getRecordUrl()).replace("USER_FULLNAME", fullName);
|
|
|
|
// evaluate hashtags from the report ... TODO
|
|
// if(hashtags != null && !hashtags.isEmpty())
|
|
// for (String hashtag : hashtags) {
|
|
// String modifiedHashtag = hashtag.replaceAll(" ", "_").replace("_+", "_"); // no empty spaces allowed
|
|
// if(modifiedHashtag.endsWith("_"))
|
|
// modifiedHashtag = modifiedHashtag.substring(0, modifiedHashtag.length() - 1);
|
|
// message += " #" + modifiedHashtag;
|
|
// }
|
|
|
|
logger.info("The post that is going to be written is -> " + message);
|
|
postRequest = new HttpPost(basePath + SOCIAL_SERVICE_WRITE_APPLICATION_POST + "?gcube-token=" + applicationToken);
|
|
JSONObject object = new JSONObject();
|
|
object.put("text", message);
|
|
object.put("enable_notification", enablePostNotification);
|
|
input = new StringEntity(object.toJSONString());
|
|
input.setContentType(MEDIATYPE_JSON);
|
|
postRequest.setEntity(input);
|
|
response = client.execute(postRequest);
|
|
|
|
Map<String, Object> mapResponseWritePost = getResponseEntityAsJSON(response);
|
|
|
|
if (response.getStatusLine().getStatusCode() != 201)
|
|
throw new RuntimeException("Failed to write application post : HTTP error code : "
|
|
+ response.getStatusLine().getStatusCode() + mapResponseWritePost.get("message"));
|
|
}
|
|
|
|
}
|
|
|
|
}catch(Exception e){
|
|
logger.error("Failed to create a post", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send an email to the administrator as well as the
|
|
* @param bean
|
|
* @param catalogue
|
|
* @param username
|
|
* @param fullName
|
|
* @param isMergeInvolved
|
|
* @param httpSession
|
|
* @throws Exceptio
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public static void sendEmailAdministrators(
|
|
ManageProductBean bean,
|
|
DataCatalogue catalogue,
|
|
String username,
|
|
String fullName,
|
|
long groupId,
|
|
HttpServletRequest httpServletRequest,
|
|
boolean isMergeInvolved) throws Exception {
|
|
|
|
// get the list of GRSF Reviewers to alert them as well
|
|
RoleManager roleManager = new LiferayRoleManager();
|
|
List<GCubeTeam> teamRoles = roleManager.listTeamsByGroup(groupId);
|
|
List<String> reviewers = new ArrayList<>();
|
|
UserManager um = new LiferayUserManager();
|
|
|
|
for(GCubeTeam tr: teamRoles){
|
|
if(tr.getTeamName().equals(Constants.GRSF_CATALOGUE_REVIEWER_ROLE))
|
|
reviewers.add(um.getUserById(tr.getUserId()).getUsername());
|
|
}
|
|
|
|
// if the user is a reviewer, then send the email just once
|
|
reviewers.remove(username);
|
|
|
|
logger.info("List of " + Constants.GRSF_CATALOGUE_REVIEWER_ROLE + " is " + reviewers);
|
|
|
|
// build the url that allows to revert the operation
|
|
Operation operation = Operation.MERGE;
|
|
|
|
// discover service endpoint for the social networking library
|
|
String currentScope = ScopeProvider.instance.get();
|
|
String tokenUser = SecurityTokenProvider.instance.get();
|
|
|
|
logger.info("Current scope for writeProductPost is " + currentScope + " and token is " + tokenUser.substring(0, 10) + "***************");
|
|
String basePath = getBaseUrlSocialService(currentScope);
|
|
|
|
if(basePath == null){
|
|
|
|
logger.error("Unable to write a post because there is no social networking service available");
|
|
|
|
}else{
|
|
|
|
basePath = basePath.endsWith("/") ? basePath : basePath + "/";
|
|
|
|
try(CloseableHttpClient client = HttpClientBuilder.create().setRedirectStrategy(new LaxRedirectStrategy()).build();){
|
|
|
|
// ask token application
|
|
HttpPost postRequest = new HttpPost(basePath + SOCIAL_SERVICE_APPLICATION_TOKEN + "?gcube-token=" + tokenUser);
|
|
StringEntity input = new StringEntity("{\"app_id\":\"" + APPLICATION_ID_CATALOGUE_MANAGER + "\"}");
|
|
input.setContentType(MEDIATYPE_JSON);
|
|
postRequest.setEntity(input);
|
|
HttpResponse response = client.execute(postRequest);
|
|
|
|
logger.debug("Url is " + basePath + SOCIAL_SERVICE_APPLICATION_TOKEN + "?gcube-token=" + tokenUser);
|
|
|
|
if (response.getStatusLine().getStatusCode() != 201) {
|
|
throw new RuntimeException("Failed to retrieve application token : HTTP error code : "
|
|
+ response.getStatusLine().getStatusCode());
|
|
}else{
|
|
|
|
Map<String, Object> mapResponseGeneratedToken = getResponseEntityAsJSON(response);
|
|
boolean successGeneratedToken = (boolean)mapResponseGeneratedToken.get("success");
|
|
if(!successGeneratedToken){
|
|
|
|
throw new RuntimeException("Failed to generate the token for the application!"
|
|
+ " Error message is " + mapResponseGeneratedToken.get("message"));
|
|
|
|
}else{
|
|
|
|
String applicationToken = (String)mapResponseGeneratedToken.get("result");
|
|
|
|
String revertUrl = getEncodedUrlManage(operation, username, System.currentTimeMillis(), bean.getKnowledgeBaseIdentifier(), httpServletRequest);
|
|
|
|
String messageToEditor = (EMAIL_MESSAGE_EDITOR +
|
|
(isMergeInvolved? REVERT_LINK_PIECE : "")).replace("USER_FULLNAME", fullName).replace("PRODUCT_TITLE", bean.getGrsfName()).replace("LINK_RECORD", bean.getRecordUrl()).replace("LINK", revertUrl);
|
|
String messageToReviewer = (EMAIL_MESSAGE_REVIEWER+
|
|
(isMergeInvolved? REVERT_LINK_PIECE : "")).replace("USER_FULLNAME", fullName).replace("PRODUCT_TITLE", bean.getGrsfName()).replace("LINK_RECORD", bean.getRecordUrl()).replace("LINK", revertUrl);
|
|
String subject = "Update request on GRSF Record";
|
|
|
|
// send email to the editor
|
|
logger.info("The message that is going to be send to the editor is\n" + messageToEditor);
|
|
postRequest = new HttpPost(basePath + SOCIAL_SEND_EMAIL + "?gcube-token=" + applicationToken);
|
|
JSONObject reqMessage = new JSONObject();
|
|
reqMessage.put("subject", subject);
|
|
reqMessage.put("body", messageToEditor);
|
|
JSONArray recipients = new JSONArray();
|
|
JSONObject recipient = new JSONObject();
|
|
recipient.put("id", username);
|
|
recipients.add(recipient);
|
|
reqMessage.put("recipients", recipients);
|
|
input = new StringEntity(reqMessage.toJSONString());
|
|
input.setContentType(MEDIATYPE_JSON);
|
|
postRequest.setEntity(input);
|
|
response = client.execute(postRequest);
|
|
|
|
Map<String, Object> mapResponseWritePost = getResponseEntityAsJSON(response);
|
|
|
|
if (response.getStatusLine().getStatusCode() != 201){
|
|
logger.error("Failed to send message to editor : HTTP error code : "
|
|
+ response.getStatusLine().getStatusCode() + mapResponseWritePost.get("message"));
|
|
}
|
|
|
|
// send email to the reviewers
|
|
logger.info("The message that is going to be send to the reviewers is\n" + messageToReviewer);
|
|
postRequest = new HttpPost(basePath + SOCIAL_SEND_EMAIL + "?gcube-token=" + applicationToken);
|
|
reqMessage = new JSONObject();
|
|
reqMessage.put("subject", subject);
|
|
reqMessage.put("body", messageToReviewer);
|
|
recipients = new JSONArray();
|
|
for(String reviewer: reviewers){
|
|
JSONObject recip = new JSONObject();
|
|
recip.put("id", reviewer);
|
|
recipients.add(recip);
|
|
}
|
|
reqMessage.put("recipients", recipients);
|
|
input = new StringEntity(reqMessage.toJSONString());
|
|
input.setContentType(MEDIATYPE_JSON);
|
|
postRequest.setEntity(input);
|
|
response = client.execute(postRequest);
|
|
mapResponseWritePost = getResponseEntityAsJSON(response);
|
|
|
|
if (response.getStatusLine().getStatusCode() != 201){
|
|
logger.error("Failed to send message to editor : HTTP error code : "
|
|
+ response.getStatusLine().getStatusCode() + mapResponseWritePost.get("message"));
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}catch(Exception e){
|
|
logger.error("Failed to create a post", e);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Create the url to be send for reverting the operation
|
|
* @param httpSession
|
|
* @return
|
|
* @throws Exception
|
|
*/
|
|
public static String getEncodedUrlManage(Operation operation, String administrator, long timestamp, String uuid, HttpServletRequest httpServletRequest) throws Exception{
|
|
String clientUrl = Utils.getCurrentClientUrl(httpServletRequest).split("\\?")[0]; // ignore other parameters
|
|
RevertOperationUrl operationUrl = new RevertOperationUrl(clientUrl, administrator, timestamp, uuid, operation);
|
|
String shortUrl = operationUrl.getShortUrl();
|
|
return shortUrl;
|
|
}
|
|
|
|
/**
|
|
* Convert the json response to a map
|
|
* @param response
|
|
* @return
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
private static Map<String, Object> getResponseEntityAsJSON(HttpResponse response){
|
|
|
|
Map<String, Object> toReturn = null;
|
|
HttpEntity entity = response.getEntity();
|
|
|
|
if (entity != null) {
|
|
try {
|
|
toReturn = new HashMap<String, Object>();
|
|
String jsonString = EntityUtils.toString(response.getEntity());
|
|
logger.debug("Response as string is " + jsonString);
|
|
ObjectMapper objectMapper = new ObjectMapper();
|
|
toReturn = objectMapper.readValue(jsonString, HashMap.class);
|
|
logger.debug("Map is " + toReturn);
|
|
}catch(Exception e){
|
|
logger.error("Failed to read json object", e);
|
|
}
|
|
}
|
|
|
|
return toReturn;
|
|
}
|
|
|
|
}
|