MessageChecker.java
/*
* Copyright (C) 2000 - 2024 Silverpeas
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* As a special exception to the terms and conditions of version 3.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* Open Source Software ("FLOSS") applications as described in Silverpeas's
* FLOSS exception. You should have received a copy of the text describing
* the FLOSS exception, and it is also available here:
* "https://www.silverpeas.org/legal/floss_exception.html"
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.silverpeas.components.mailinglist.service.job;
import org.silverpeas.components.mailinglist.service.event.MessageEvent;
import org.silverpeas.components.mailinglist.service.event.MessageListener;
import org.silverpeas.components.mailinglist.service.model.MailingListService;
import org.silverpeas.components.mailinglist.service.model.beans.MailingList;
import org.silverpeas.core.annotation.Service;
import org.silverpeas.core.mail.engine.SmtpConfiguration;
import org.silverpeas.core.scheduler.Scheduler;
import org.silverpeas.core.scheduler.SchedulerEvent;
import org.silverpeas.core.scheduler.SchedulerEventListener;
import org.silverpeas.core.scheduler.SchedulerException;
import org.silverpeas.core.scheduler.SchedulerProvider;
import org.silverpeas.core.scheduler.trigger.JobTrigger;
import org.silverpeas.core.util.ResourceLocator;
import org.silverpeas.core.util.SettingBundle;
import org.silverpeas.core.util.logging.SilverLogger;
import javax.inject.Inject;
import javax.mail.FetchProfile;
import javax.mail.Flags.Flag;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.Message.RecipientType;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.io.IOException;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
@Service
public class MessageChecker {
private static final String MAILING_LIST_JOB_NAME = "mailingListScheduler";
public static final String IMAP_PROTOCOL = "imap";
public static final String IMAP_SSL_PROTOCOL = "imaps";
public static final String POP3_PROTOCOL = "pop3";
private Map<String, MessageListener> listeners;
@Inject
private MailProcessor processor;
private String mailServer;
private String login;
private String password;
private String protocol;
private int port;
private boolean leaveOnServer;
private Session mailSession;
@Inject
private MailingListService mailingListService;
public MailingListService getMailingListService() {
return mailingListService;
}
public Session getMailSession() {
return mailSession;
}
public void schedule(final JobTrigger trigger) throws SchedulerException {
Scheduler scheduler = SchedulerProvider.getVolatileScheduler();
scheduler.scheduleJob(MAILING_LIST_JOB_NAME, trigger, new MessageCheckingListener());
}
public void unschedule() throws SchedulerException {
Scheduler scheduler = SchedulerProvider.getVolatileScheduler();
if (scheduler.isJobScheduled(MAILING_LIST_JOB_NAME)) {
scheduler.unscheduleJob(MAILING_LIST_JOB_NAME);
}
}
/**
* Default constructor
*/
public MessageChecker() {
this.listeners = new HashMap<>(10);
SmtpConfiguration smtpConfig = SmtpConfiguration.fromDefaultSettings();
SettingBundle notifConfig =
ResourceLocator.getSettingBundle("org.silverpeas.mailinglist.notification");
protocol = notifConfig.getString("mail.server.protocol");
mailServer = notifConfig.getString("mail.server.host");
login = notifConfig.getString("mail.server.login");
password = notifConfig.getString("mail.server.password");
port = notifConfig.getInteger("mail.server.port", smtpConfig.getPort());
leaveOnServer = notifConfig.getBoolean("mail.server.leave", true);
mailSession = Session.getInstance(new Properties());
}
public String getLogin() {
return login;
}
public String getPassword() {
return password;
}
public String getMailServer() {
return mailServer;
}
public boolean isLeaveOnServer() {
if (POP3_PROTOCOL.equalsIgnoreCase(this.protocol)) {
this.leaveOnServer = false;
}
return leaveOnServer;
}
public int getPort() {
if (port <= 0) {
if (POP3_PROTOCOL.equals(getProtocol())) {
setPort(110);
} else if (IMAP_PROTOCOL.equals(getProtocol())) {
setPort(143);
} else if (IMAP_SSL_PROTOCOL.equals(getProtocol())) {
setPort(993);
}
setPort(110);
}
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getProtocol() {
return this.protocol;
}
public void setProtocol(String protocol) {
if (POP3_PROTOCOL.equalsIgnoreCase(protocol) || IMAP_PROTOCOL.equalsIgnoreCase(protocol) ||
IMAP_SSL_PROTOCOL.equalsIgnoreCase(protocol)) {
this.protocol = protocol.toLowerCase();
} else {
this.protocol = POP3_PROTOCOL;
}
if (isImap()) {
System.setProperty("mail.imap.partialfetch", "false");
}
}
/**
* Adds a new listener to the list of listeners.
* @param listener the listener to be added.
*/
public synchronized void addMessageListener(MessageListener listener) {
this.listeners.put(listener.getComponentId(), listener);
}
/**
* Gets the new messages on the Mail Server and processes them.
* @param date the date of the checking.
*/
public void checkNewMessages(Date date) {
Store mailAccount = null;
Folder inbox = null;
Map<String, MessageListener> listenersByEmail = prepareListeners();
try {
mailAccount = mailSession.getStore(getProtocol());
mailAccount.connect(getMailServer(), getPort(), getLogin(), getPassword());
inbox = mailAccount.getFolder("INBOX");
if (inbox == null) {
throw new MessagingException("No POP3 INBOX");
}
// -- Open the folder for read write --
inbox.open(Folder.READ_WRITE);
// -- Get the message wrappers and process them --
Message[] msgs = inbox.getMessages();
if (isImap()) {
FetchProfile profile = new FetchProfile();
profile.add(FetchProfile.Item.FLAGS);
inbox.fetch(msgs, profile);
}
Map<MessageListener, MessageEvent> eventsMap = new HashMap<>();
for (final Message msg : msgs) {
processMessage(inbox, listenersByEmail, eventsMap, msg);
}
for (final Map.Entry<MessageListener, MessageEvent> entry : eventsMap.entrySet()) {
MessageListener mailingList = entry.getKey();
mailingList.onMessage(entry.getValue());
}
} catch (Exception mex) {
SilverLogger.getLogger(this).error(mex);
} finally {
// -- Close down nicely --
try {
if (inbox != null) {
inbox.close(!isLeaveOnServer());
}
if (mailAccount != null) {
mailAccount.close();
}
} catch (Exception ex2) {
SilverLogger.getLogger(this).error(ex2);
}
}
}
private void processMessage(final Folder inbox,
final Map<String, MessageListener> listenersByEmail,
final Map<MessageListener, MessageEvent> eventsMap, final Message msg) {
try {
MimeMessage message = (MimeMessage) msg;
if (isImap()) {
if (!message.isSet(Flag.SEEN) && !message.isSet(Flag.DELETED)) {
message = new MimeMessage(message);
processEmail(message, eventsMap, listenersByEmail);
}
} else {
processEmail(message, eventsMap, listenersByEmail);
}
if (isLeaveOnServer() && inbox.getMode() == Folder.READ_WRITE) {
msg.setFlag(Flag.SEEN, true);
} else {
msg.setFlag(Flag.DELETED, true);
}
} catch (MessagingException | IOException e) {
SilverLogger.getLogger(this).error(e);
}
}
/**
* Process an email, building the events to be send when all email have been processed.
* @param mail the mail to be processed
* @param eventsMap the map of MessageEvents
* @param listenersByEmail the map of MessageListners with their emil address as key
* @throws MessagingException
* @throws IOException
*/
protected void processEmail(MimeMessage mail, Map<MessageListener, MessageEvent> eventsMap,
Map<String, MessageListener> listenersByEmail) throws MessagingException, IOException {
BetterMimeMessage email = new BetterMimeMessage(mail);
if (email.isBounced() || email.isSpam()) {
return;
}
Set<String> allRecipients = getAllRecipients(mail);
Set<MessageListener> mailingLists = getRecipientMailingLists(allRecipients, listenersByEmail);
for (final MessageListener mailingList : mailingLists) {
MessageEvent event;
if (!eventsMap.containsKey(mailingList)) {
event = new MessageEvent();
eventsMap.put(mailingList, event);
} else {
event = eventsMap.get(mailingList);
}
this.processor.prepareMessage(mail, mailingList, event);
}
}
/**
* Extracts all the recipients of an email.
* @param mail the email whose recipients are extracted.
* @return a list of InternetAdress.
* @throws MessagingException
* @see javax.mail.internet.InternetAddress
*/
protected Set<String> getAllRecipients(MimeMessage mail) throws MessagingException {
Set<String> allRecipients = new HashSet<>(10);
InternetAddress[] addresses = (InternetAddress[]) mail.getRecipients(RecipientType.TO);
if (addresses != null) {
for (final InternetAddress address : addresses) {
allRecipients.add(address.getAddress());
}
}
addresses = (InternetAddress[]) mail.getRecipients(RecipientType.BCC);
if (addresses != null) {
for (final InternetAddress address : addresses) {
allRecipients.add(address.getAddress());
}
}
addresses = (InternetAddress[]) mail.getRecipients(RecipientType.CC);
if (addresses != null) {
for (final InternetAddress address : addresses) {
allRecipients.add(address.getAddress());
}
}
return allRecipients;
}
/**
* Finds all the mailing lists recipients for an email.
* @param recipients the recipients of the email.
* @return the list of mailing lists (as MessageListener) for this email.
*/
protected Set<MessageListener> getRecipientMailingLists(Collection<String> recipients,
Map<String, MessageListener> listenersByEmail) {
Set<MessageListener> mailingLists = new HashSet<>(recipients.size());
for (final String email : recipients) {
MessageListener mailingList = listenersByEmail.get(email.toLowerCase());
if (mailingList != null) {
mailingLists.add(mailingList);
}
}
return mailingLists;
}
/**
* Prepare a map of subscribed email addresses and their corresponding listeners.
* @return a map of subscribed email addresses and their corresponding listeners.
*/
public synchronized Map<String, MessageListener> prepareListeners() {
Map<String, MessageListener> listenersByEmail = new HashMap<>(this.listeners.size());
for (final MessageListener listener : this.listeners.values()) {
MailingList list = mailingListService.findMailingList(listener.getComponentId());
if (list != null && list.getSubscribedAddress() != null) {
listenersByEmail.put(list.getSubscribedAddress().toLowerCase(), listener);
}
}
return listenersByEmail;
}
/**
* Removes a listener from the list of listeners.
* @param componentId the unique id of the component.
*/
public synchronized void removeListener(String componentId) {
this.listeners.remove(componentId);
}
public MailProcessor getMailProcessor() {
return processor;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((login == null) ? 0 : login.hashCode());
result = prime * result + ((mailServer == null) ? 0 : mailServer.hashCode());
result = prime * result + ((password == null) ? 0 : password.hashCode());
result = prime * result + ((protocol == null) ? 0 : protocol.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
return isEqualTo((MessageChecker) obj);
}
private boolean isEqualTo(final MessageChecker obj) {
final MessageChecker other = obj;
if ((mailServer == null && other.mailServer != null) ||
(mailServer != null && !mailServer.equals(other.mailServer))) {
return false;
}
if ((password == null && other.password != null) ||
(password != null && !password.equals(other.password))) {
return false;
}
return (protocol == null && other.protocol == null) ||
(protocol != null && protocol.equals(other.protocol));
}
protected boolean isImap() {
return IMAP_PROTOCOL.equalsIgnoreCase(getProtocol()) ||
IMAP_SSL_PROTOCOL.equalsIgnoreCase(getProtocol());
}
public class MessageCheckingListener implements SchedulerEventListener {
@Override
public void triggerFired(SchedulerEvent anEvent) {
if (listeners != null && !listeners.isEmpty()) {
checkNewMessages(new Date());
}
}
@Override
public void jobSucceeded(SchedulerEvent anEvent) {
// nothing to do
}
@Override
public void jobFailed(SchedulerEvent anEvent) {
SilverLogger.getLogger(this)
.error(
"The job '" + anEvent.getJobExecutionContext().getJobName() + "' was not successfull",
anEvent.getJobThrowable());
}
}
}