You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
social-mail-servlet/src/main/java/org/gcube/portal/socialmail/PeriodicTask.java

544 lines
21 KiB
Java

package org.gcube.portal.socialmail;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
import javax.mail.Address;
import javax.mail.BodyPart;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.NoSuchProviderException;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage.RecipientType;
import org.gcube.application.framework.core.session.ASLSession;
import org.gcube.application.framework.core.session.SessionManager;
import org.gcube.application.framework.core.util.GenderType;
import org.gcube.applicationsupportlayer.social.ApplicationNotificationsManager;
import org.gcube.applicationsupportlayer.social.NotificationsManager;
import org.gcube.applicationsupportlayer.social.mailing.AppType;
import org.gcube.applicationsupportlayer.social.mailing.EmailPlugin;
import org.gcube.common.homelibrary.home.HomeLibrary;
import org.gcube.common.homelibrary.home.exceptions.HomeNotFoundException;
import org.gcube.common.homelibrary.home.exceptions.InternalErrorException;
import org.gcube.common.homelibrary.home.workspace.Workspace;
import org.gcube.common.homelibrary.home.workspace.exceptions.ItemNotFoundException;
import org.gcube.common.homelibrary.home.workspace.exceptions.WorkspaceFolderNotFoundException;
import org.gcube.common.homelibrary.home.workspace.sharing.WorkspaceMessage;
import org.gcube.common.homelibrary.home.workspace.sharing.WorkspaceMessageManager;
import org.gcube.common.portal.PortalContext;
import org.gcube.portal.custom.communitymanager.OrganizationsUtil;
import org.gcube.portal.databook.server.DatabookStore;
import org.gcube.portal.databook.shared.Comment;
import org.gcube.portal.databook.shared.Feed;
import org.gcube.portal.databook.shared.Like;
import org.gcube.portal.databook.shared.ex.ColumnNameNotFoundException;
import org.gcube.portal.databook.shared.ex.FeedIDNotFoundException;
import org.gcube.portal.databook.shared.ex.FeedTypeNotFoundException;
import org.gcube.portal.databook.shared.ex.PrivacyLevelTypeNotFoundException;
import org.gcube.portal.notifications.bean.GenericItemBean;
import org.gcube.portal.notifications.thread.CommentNotificationsThread;
import org.gcube.portal.notifications.thread.LikeNotificationsThread;
import org.gcube.portal.notifications.thread.MessageNotificationsThread;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.model.User;
import com.liferay.portal.service.UserLocalServiceUtil;
import com.sun.mail.util.MailSSLSocketFactory;
/**
*
* @author Massimiliano Assante, CNR-ISTI
*
*/
public class PeriodicTask implements Runnable {
private static final Logger _log = LoggerFactory.getLogger(PeriodicTask.class);
private DatabookStore socialStore;
private String host;
private String mailserver_username;
private String password;
private String portalName;
public PeriodicTask(DatabookStore store, String portalName, String host, String mailserver_username, String password) {
super();
this.socialStore = store;
this.portalName = portalName;
this.host = host;
this.mailserver_username = mailserver_username;
this.password = password;
}
@Override
public void run() {
check(portalName, host, mailserver_username, password);
}
/**
* @return a fake session usuful for Notifications
*/
private ASLSession getFakeASLSession(String emailAddress) {
String sessionID = UUID.randomUUID().toString();
PortalContext context = PortalContext.getConfiguration();
String scope = "/" + context.getInfrastructureName();
String username = "";
long companyId;
try {
companyId = OrganizationsUtil.getCompany().getCompanyId();
User user = UserLocalServiceUtil.getUserByEmailAddress(companyId, emailAddress);
username = user.getScreenName();
SessionManager.getInstance().getASLSession(sessionID, username).setScope(scope);
//add the social information needed by apps
String fullName = user.getFirstName() + " " + user.getLastName();
String email = user.getEmailAddress();
String thumbnailURL = "/image/user_male_portrait?img_id="+user.getPortraitId();
boolean isMale = user.isMale();
SessionManager.getInstance().getASLSession(sessionID, username).setUserFullName(fullName);
SessionManager.getInstance().getASLSession(sessionID, username).setUserEmailAddress(email);
SessionManager.getInstance().getASLSession(sessionID, username).setUserAvatarId(thumbnailURL);
SessionManager.getInstance().getASLSession(sessionID, username).setUserGender(isMale? GenderType.MALE : GenderType.FEMALE);
_log.debug("Created fakesession for user " + username + " email="+emailAddress);
} catch (PortalException | SystemException e) {
_log.error("Exception while trying to get the user from email address: " + e.getMessage());
return null;
}
return SessionManager.getInstance().getASLSession(sessionID, username);
}
public void check(String portalName, String host, String user, String password) {
try {
MailSSLSocketFactory sf = null;
try {
sf = new MailSSLSocketFactory();
}
catch (GeneralSecurityException e) {
e.printStackTrace();
}
sf.setTrustAllHosts(true);
Properties pop3Props = new Properties();
pop3Props.setProperty("mail.pop3.ssl.enable", "true");
pop3Props.setProperty("mail.protocol.ssl.trust", host);
pop3Props.put("mail.pop3s.ssl.socketFactory", sf);
pop3Props.setProperty("mail.pop3s.port", "995");
Session emailSession = Session.getDefaultInstance(pop3Props);
emailSession.setDebug(false);
//create the POP3 socialStore object and connect with the pop server
Store store = emailSession.getStore("pop3s");
_log.debug("Trying to connect to " + host + ", user="+user + " passwd (first 3 char)=" + password.substring(0,3)+"******");
store.connect(host, user, password);
//create the folder object and open it
Folder emailFolder = store.getFolder("INBOX");
emailFolder.open(Folder.READ_WRITE);
// retrieve the messages from the folder in an array and print it
Message[] messages = emailFolder.getMessages();
_log.debug("Found " + messages.length + " new messages ...");
for (int i = 0, n = messages.length; i < n; i++) {
Message message = messages[i];
_log.info("--------------- FOUND EMAIL ------------------");
String subject = message.getSubject();
_log.info("Parsing email of " + message.getFrom()[0] + " with subject: " + subject);
if (isValidReply(message)) {
String subAddressField = extractSubaddress(message);
Address[] froms = message.getFrom();
String emailAddress = froms == null ? null : ((InternetAddress) froms[0]).getAddress();
ASLSession fakeSession = getFakeASLSession(emailAddress);
if (fakeSession != null) {
if (subAddressField.endsWith(AppType.POST.toString()) || subAddressField.endsWith(AppType.POST.toString().toLowerCase())) { //it is a post, a comment on a post or a mention
_log.debug("Looks like a post, a comment on a post or a mention to me");
String feedId = extractIdentifier(subAddressField);
handlePostReply(portalName, feedId, message, fakeSession);
}
else if (subAddressField.endsWith(AppType.MSG.toString()) || subAddressField.endsWith(AppType.MSG.toString().toLowerCase())) { //it is a message
_log.debug("Looks like a message reply to me");
String messageId = extractIdentifier(subAddressField);
handleMessageReply(portalName, messageId, message, fakeSession);
} else {
_log.warn("cannot identify the type of this email reply from " + message.getFrom()[0] + " with subject: " + subject);
}
}
else {
_log.warn("User Not Recognized, going to discard Message from emailAddress = " + emailAddress);
}
}
else {
_log.warn("Message is not a valid reply, going to discard Message with subject = " + subject);
}
//delete this message
message.setFlag(Flags.Flag.DELETED, true);
_log.debug("Marked DELETE for message: " + subject);
}
//close the socialStore and folder objects
emailFolder.close(true);
store.close();
} catch (NoSuchProviderException e) {
e.printStackTrace();
} catch (MessagingException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* this method manages the replies coming from message notifications
* @param portalName
* @param messageId the message identifier in the System managing the messages (currently in the Home Library)
* @param message the javax mail Message instance
* @param fakeSession
* @throws Exception
*/
private void handleMessageReply(String portalName, String messageId, Message message, ASLSession fakeSession) {
String subject = "";
String messageText = "";
try {
subject = message.getSubject();
messageText = extractText(portalName, messageId, message);
} catch (Exception e1) {
e1.printStackTrace();
}
_log.debug("Found message reply, subject: " + subject, " body: " + messageText);
String newMessageId = null;
Workspace workspace;
List<String> recipientIds = null;
try {
workspace = getWorkspace(fakeSession);
WorkspaceMessageManager messageManager = workspace.getWorkspaceMessageManager();
WorkspaceMessage theMessage = messageManager.getReceivedMessage(messageId);
recipientIds = theMessage.getAddresses();
//add the sender and remove the person who is replying from the recipients
String sender = theMessage.getSender().getPortalLogin();
recipientIds.add(sender);
recipientIds.remove(fakeSession.getUsername());
_log.debug("Message Recipients:");
for (String rec : recipientIds) {
_log.debug(rec);
}
_log.debug("Trying to send message with subject: " + subject, " to: " + recipientIds.toString());
newMessageId = messageManager.sendMessageToPortalLogins(subject, messageText, new ArrayList<String>(), recipientIds);
} catch (WorkspaceFolderNotFoundException | InternalErrorException | HomeNotFoundException | ItemNotFoundException e) {
e.printStackTrace();
}
_log.debug("Message with subject: " + subject, " hase been sent, returned id: " + newMessageId);
if (newMessageId != null) {
_log.debug("Sending message notifications ... ");
List<GenericItemBean> recipients = getUsersbyUserId(recipientIds);
NotificationsManager nm = new ApplicationNotificationsManager(fakeSession);
Thread thread = new Thread(new MessageNotificationsThread(recipients, newMessageId, subject, messageText, nm));
thread.start();
} else {
_log.debug("Could not send message reply");
}
}
/**
* return the User instance given his id
* @param recipientIds
* @return
*/
private List<GenericItemBean> getUsersbyUserId(List<String> recipientIds) {
List<GenericItemBean> recipients = new ArrayList<GenericItemBean>();
for (String userid : recipientIds) {
com.liferay.portal.model.User user;
try {
user = UserLocalServiceUtil.getUserByScreenName(OrganizationsUtil.getCompany().getCompanyId(), userid);
recipients.add(new GenericItemBean(""+user.getUserId(), user.getScreenName(), user.getFullName(), ""));
} catch (PortalException | SystemException e) {
e.printStackTrace();
}
}
return recipients;
}
/**
* this method manages the replies coming from post notifications
* @param portalName
* @param feedId the identifier in the System managing the feeds
* @param message the javax mail Message instance
* @param fakeSession
* @throws Exception
*/
private void handlePostReply(String portalName, String feedId, Message message, ASLSession fakeSession) throws Exception {
String commentText = extractText(portalName, feedId, message);
_log.info("Extracted id: " + feedId + " text=" + commentText);
String escapedCommentText = Utils.escapeHtmlAndTransformUrl(commentText);
String subject = message.getSubject();
if (escapedCommentText.trim().compareTo("") == 0) {//it is a favorite/subscription
_log.debug("Found favorite/subscription for feed with subject: " + subject);
favoriteFeed(feedId, fakeSession);
}
else {
Comment comment = new Comment(UUID.randomUUID().toString(), fakeSession.getUsername(),
new Date(), feedId, escapedCommentText, fakeSession.getUserFullName(), fakeSession.getUserAvatarId());
_log.debug("The EscapedCommentText =>" + escapedCommentText);
boolean commentCommitResult = false;
try {
if (socialStore.addComment(comment))
commentCommitResult = true;
} catch (FeedIDNotFoundException e) {
_log.error("Related post not found for this comment " + e.getMessage());
e.printStackTrace();
}
if (commentCommitResult) { //the notifications should start
notifyUsersInvolved(comment, escapedCommentText, feedId, fakeSession);
}
}
}
/**
* favorite the feed to subscribe to further comments
* @param feedId
* @param fakeSession
*/
private void favoriteFeed(String feedId, ASLSession fakeSession) {
if (feedId == null || feedId.compareTo("") == 0) {
_log.warn("Found email with no feedId from " + fakeSession.getUserEmailAddress() + ". Going to trash it");
return;
}
Like like = new Like(UUID.randomUUID().toString(), fakeSession.getUsername(),
new Date(), feedId, fakeSession.getUserFullName(), fakeSession.getUserAvatarId());
boolean likeCommitResult = false;
try {
if (socialStore.like(like));
likeCommitResult = true;
} catch (FeedIDNotFoundException e) {
_log.error("Related post not found for this like " + e.getMessage());
e.printStackTrace();
}
if (likeCommitResult) { //the notification should be delivered to the post owner
try {
Feed feed = socialStore.readFeed(feedId);
String feedOwnerId = feed.getEntityId();
boolean isAppFeed = feed.isApplicationFeed();
NotificationsManager nm = new ApplicationNotificationsManager(fakeSession);
if (! fakeSession.getUsername().equals(feedOwnerId) && (!isAppFeed)) {
boolean result = nm.notifyLikedFeed(feedOwnerId, feedId, "");
_log.trace("Like Notification to post owner added? " + result);
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* this method take care of notify all the users that need to be notified when someone comment
* @param comment
* @param feedId
* @param fakeSession
* @throws PrivacyLevelTypeNotFoundException
* @throws FeedTypeNotFoundException
* @throws FeedIDNotFoundException
* @throws ColumnNameNotFoundException
*/
private void notifyUsersInvolved(Comment comment, String commentText, String feedId, ASLSession fakeSession) throws PrivacyLevelTypeNotFoundException, FeedTypeNotFoundException, FeedIDNotFoundException, ColumnNameNotFoundException {
Feed feed = socialStore.readFeed(feedId);
String feedOwnerId = feed.getEntityId();
boolean isAppFeed = feed.isApplicationFeed();
//if the user who commented this post is not the user who posted it notifies the poster user (Feed owner)
NotificationsManager nm = new ApplicationNotificationsManager(fakeSession);
if (! fakeSession.getUsername().equals(feedOwnerId) && (!isAppFeed)) {
boolean result = nm.notifyOwnCommentReply(feedOwnerId, feedId, commentText);
_log.trace("Comment Notification to post owner added? " + result);
}
//if there are users who liked this post they get notified, asynchronously with this thread
ArrayList<Like> favorites = getAllLikesByFeed(feedId);
Thread likesThread = new Thread(new LikeNotificationsThread(commentText, nm, favorites, feedOwnerId));
likesThread.start();
//notify the other users who commented this post (excluding the ones above)
Thread commentsNotificationthread = new Thread(new CommentNotificationsThread(socialStore, fakeSession.getUsername(), comment.getFeedid(), commentText, nm, feedOwnerId, favorites));
commentsNotificationthread.start();
}
/**
*
* @param feedid
* @return
*/
public ArrayList<Like> getAllLikesByFeed(String feedid) {
_log.trace("Asking likes for " + feedid);
ArrayList<Like> toReturn = (ArrayList<Like>) socialStore.getAllLikesByFeed(feedid);
return toReturn;
}
/**
* extracts the sub-address (the part after the + and before the @) from the email addressee
* @param message
* @return the sub-address without + and @
* @throws MessagingException
*/
private static String extractSubaddress(Message message) throws MessagingException {
Address[] emails = message.getRecipients(RecipientType.TO);
String toParse = emails[0].toString();
int plus = toParse.indexOf('+');
int at = toParse.indexOf('@');
if (plus == -1)
return null;
return toParse.substring(plus+1, at);
}
/**
* extracts the identifier (the part before the $) from the Subaddress
* @param message
* @return the identifier
* @throws MessagingException
*/
private static String extractIdentifier(String subAddress) {
String id = subAddress; //for backward compatibility
int at = subAddress.indexOf('$');
if (at > -1)
id = subAddress.substring(0, at);
return id;
}
/**
* the email is considered a valid reply if and only of it contains the EmailPlugin.WRITE_ABOVE_TO_REPLY text in the body
* @param message the message to check
* @return true if the email is a valid reply, false otherwise
*/
private boolean isValidReply(Message message) {
Object messageContent;
try {
messageContent = message.getContent();
String toParse = null;
final String SEPARATOR = EmailPlugin.WRITE_ABOVE_TO_REPLY.substring(0, 23);
// Check if content is pure text/html or in parts
if (messageContent instanceof Multipart) {
_log.debug("Checking if isValidReply, found Multipart Message, getting text part ... looking for separator " + SEPARATOR);
Multipart multipart = (Multipart) messageContent;
BodyPart part = multipart.getBodyPart(0);
part.toString();
toParse = part.getContent().toString();
}
else {
_log.debug("Found a text/plain Message, getting text ... looking for separator " + SEPARATOR);
toParse = messageContent.toString();
}
String[] lines = toParse.split(System.getProperty("line.separator"));
for (int i = 0; i < lines.length; i++) {
if (lines[i].contains(SEPARATOR)) {
_log.debug("Yes, it is a valid Reply");
return true;
}
}
_log.debug("NOT a valid Reply");
return false;
} catch (Exception e) {
e.printStackTrace();
_log.error("Exceptiom returning NOT a valid Reply");
return false;
}
}
/**
*
* @param portalName
* @param subjectId
* @param message
* @return
* @throws Exception
*/
private static String extractText(String portalName, String subjectId, Message message) throws Exception {
Object messageContent = message.getContent();
String toParse = null;
final String SEPARATOR = EmailPlugin.WRITE_ABOVE_TO_REPLY.substring(0, 23);
// Check if content is pure text/html or in parts
if (messageContent instanceof Multipart) {
_log.debug("Found Multipart Message, getting text part ... looking for separator " + SEPARATOR);
Multipart multipart = (Multipart) messageContent;
BodyPart part = multipart.getBodyPart(0);
part.toString();
toParse = part.getContent().toString();
}
else {
_log.debug("Found text/plain Message, getting text");
toParse = messageContent.toString();
}
_log.debug("Got Message content = " + toParse);
//cut the text below the WRITE_ABOVE_TO_REPLY text
String[] lines = toParse.split(System.getProperty("line.separator"));
int until = -1;
for (int i = 0; i < lines.length; i++) {
if (lines[i].contains(SEPARATOR)) {
until = i;
break;
}
}
StringBuilder sb = new StringBuilder();
/*
* this is needed to remove the text added by the email client like when you reply, e.g. On <day> <sender> wrote ...
* it also handles the case where the user writes between that and the WRITE_ABOVE_TO_REPLY text
*/
for (int i = 0; i < until; i++) {
if (! ( lines[i].contains(portalName) || lines[i].contains(subjectId) || (lines[i].startsWith("> ") && lines[i].trim().length() < 2)) ) {
sb.append(lines[i]);
}
if (! (i == until -1) )
sb.append("\n");
}
String toReturn = sb.toString().trim();
_log.debug("Returning text extracted = " + toReturn);
return toReturn;
}
/**
*
* @return the workspace instance
* @throws InternalErrorException
* @throws HomeNotFoundException
* @throws WorkspaceFolderNotFoundException
*/
private Workspace getWorkspace(ASLSession session) throws InternalErrorException, HomeNotFoundException, WorkspaceFolderNotFoundException {
Workspace workspace = HomeLibrary.getUserWorkspace(session.getUsername());
return workspace;
}
}