
org.apache.servicemix.mail.MailPollerEndpoint Maven / Gradle / Ivy
Show all versions of servicemix-mail Show documentation
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.servicemix.mail;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.jbi.JBIException;
import javax.jbi.messaging.ExchangeStatus;
import javax.jbi.messaging.InOnly;
import javax.jbi.messaging.MessageExchange;
import javax.jbi.messaging.NormalizedMessage;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.ParseException;
import javax.mail.search.FlagTerm;
import org.apache.servicemix.common.endpoints.PollingEndpoint;
import org.apache.servicemix.mail.marshaler.AbstractMailMarshaler;
import org.apache.servicemix.mail.marshaler.DefaultMailMarshaler;
import org.apache.servicemix.mail.utils.MailConnectionConfiguration;
import org.apache.servicemix.mail.utils.MailUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This is the polling endpoint for the mail component.
*
* @org.apache.xbean.XBean element="poller"
* @author lhein
*/
public class MailPollerEndpoint extends PollingEndpoint implements MailEndpointType {
private final Logger logger = LoggerFactory.getLogger(MailPollerEndpoint.class);
private AbstractMailMarshaler marshaler = new DefaultMailMarshaler();
private final List seenMessages = Collections.synchronizedList(new LinkedList());
private String customTrustManagers;
private MailConnectionConfiguration config;
private String connection;
private int maxFetchSize = -1;
private boolean processOnlyUnseenMessages;
private boolean deleteProcessedMessages;
private boolean debugMode;
private Map customProperties;
private final List foundMessagesInFolder = Collections.synchronizedList(new LinkedList());
private org.apache.servicemix.store.Store storage;
/**
* default constructor
*/
public MailPollerEndpoint() {
super();
this.processOnlyUnseenMessages = true;
this.deleteProcessedMessages = false;
this.debugMode = false;
}
/*
* (non-Javadoc)
* @see
* org.apache.servicemix.common.endpoints.ConsumerEndpoint#getLocationURI()
*/
@Override
public String getLocationURI() {
// return a URI that unique identify this endpoint
return getService() + "#" + getEndpoint();
}
/*
* (non-Javadoc)
* @see org.apache.servicemix.common.endpoints.PollingEndpoint#start()
*/
@Override
@SuppressWarnings("unchecked")
public synchronized void start() throws Exception {
super.start();
if (this.storage == null) {
return;
}
String id = config.getUsername() + " @ " + config.getHost();
try {
// load the list of seen messages
List loadedMsg = (List)this.storage.load(id);
if (loadedMsg == null || loadedMsg.isEmpty()) {
return;
}
for (String uid : loadedMsg) {
if (!this.seenMessages.contains(uid)) {
this.seenMessages.add(uid);
}
}
loadedMsg.clear();
} catch (IOException ioex) {
logger.error("Error loading seen messages for: {}", id, ioex);
}
}
/*
* (non-Javadoc)
* @see org.apache.servicemix.common.endpoints.PollingEndpoint#stop()
*/
@Override
public synchronized void stop() throws Exception {
if (this.storage != null) {
String id = config.getUsername() + " @ " + config.getHost();
try {
// save the list of seen messages
this.storage.store(id, this.seenMessages);
} catch (IOException ioex) {
logger.error("Error saving list of seen messages for: {}", id, ioex);
}
}
super.stop();
}
/*
* (non-Javadoc)
* @see
* org.apache.servicemix.common.ExchangeProcessor#process(javax.jbi.messaging
* .MessageExchange)
*/
public void process(MessageExchange exchange) throws Exception {
// Do nothing. In our case, this method should never be called
// as we only send synchronous InOnly exchange
}
/*
* (non-Javadoc)
* @see org.apache.servicemix.components.util.PollingComponentSupport#poll()
*/
public void poll() throws Exception {
logger.debug("Polling mailfolder {} at host {} ...", config.getFolderName(), config.getHost());
if (maxFetchSize == 0) {
logger.debug("The configuration is set to poll no new messages at all...skipping.");
return;
}
boolean isPopProtocol = this.config.getProtocol().toLowerCase().indexOf("pop") > -1;
// clear the list each run
this.foundMessagesInFolder.clear();
Store store = null;
Folder folder = null;
Session session;
try {
Properties props = MailUtils.getPropertiesForProtocol(this.config, this.customTrustManagers);
props.put("mail.debug", isDebugMode() ? "true" : "false");
// apply the custom properties
applyCustomProperties(props);
// Get session
session = Session.getInstance(props, config.getAuthenticator());
// debug the session
session.setDebug(this.debugMode);
store = session.getStore(config.getProtocol());
store.connect(config.getHost(), config.getUsername(), config.getPassword());
folder = store.getFolder(config.getFolderName());
if (folder == null || !folder.exists()) {
throw new Exception("Folder not found or invalid: " + config.getFolderName());
}
folder.open(Folder.READ_WRITE);
Message[] messages;
if (isProcessOnlyUnseenMessages() && !isPopProtocol) {
messages = folder.search(new FlagTerm(new Flags(Flags.Flag.SEEN), false));
} else {
messages = folder.getMessages();
}
int fetchSize = getMaxFetchSize() == -1 ? messages.length : Math.min(getMaxFetchSize(),
messages.length);
int fetchedMessages = 0;
String uid;
for (Message msg : messages) {
uid = null;
// get the message
MimeMessage mailMsg = (MimeMessage)msg;
if (isProcessOnlyUnseenMessages() && isPopProtocol) {
// POP3 doesn't support flags, so we need to check manually
// if message is new or not
try {
Object ouid = folder.getClass().getMethod("getUID", Message.class).invoke(folder, mailMsg);
// remember each found message
if (ouid != null) {
uid = (String)ouid;
foundMessagesInFolder.add(uid);
}
// check if we already processed the message
if (uid != null && this.seenMessages.contains(uid)) {
// this message was already processed
continue;
}
} catch (Exception ex) {
// this folder doesn't provide UIDs for messages
logger.warn("{}: Unable to determine unique id of mail.", getEndpoint(), ex);
}
}
// only process a message if the max message fetch size isn't
// exceeded then
if (fetchedMessages < fetchSize) {
// create a inOnly exchange
InOnly io = getExchangeFactory().createInOnlyExchange();
// configure the exchange target
configureExchangeTarget(io);
// create the in message
NormalizedMessage normalizedMessage = io.createMessage();
// now let the marshaller convert the mail into a normalized
// message to send to jbi bus
marshaler.convertMailToJBI(io, normalizedMessage, mailMsg);
// then put the in message into the inOnly exchange
io.setInMessage(normalizedMessage);
// and use sendSync to deliver it
sendSync(io);
// increment the fetched messages counter
fetchedMessages++;
// now check if delivery succeeded or went wrong
if (io.getStatus() == ExchangeStatus.ERROR) {
// to ensure reprocessing of the mail we set it to UNSEEN even if we
// did not mark it seen before (seems there are some mail systems out there
// which do set somehow automatically)
mailMsg.setFlag(Flags.Flag.SEEN, false);
Exception e = io.getError();
if (e == null) {
e = new JBIException("Unexpected error occured...");
}
throw e;
} else {
// then mark the mail as processed (only if no errors)
if (deleteProcessedMessages) {
// processed messages have to be marked as deleted
mailMsg.setFlag(Flags.Flag.DELETED, true);
} else {
// processed messages have to be marked as seen
mailMsg.setFlag(Flags.Flag.SEEN, true);
}
// remember the processed mail if needed
if (isProcessOnlyUnseenMessages() && isPopProtocol && uid != null) {
// POP3 doesn't support flags, so we need to
// remember processed mails
this.seenMessages.add(uid);
}
}
}
}
} finally {
// finally clean up and close the folder and store
try {
if (folder != null) {
folder.close(true);
}
if (store != null) {
store.close();
}
// clean up the seen messages list because of maybe deleted
// messages
if (isProcessOnlyUnseenMessages() && isPopProtocol) {
cleanUpSeenMessages();
}
} catch (Exception ignored) {
logger.debug("", ignored);
}
}
}
/**
* this method will check if a seen message was deleted from mail folder and
* remove this from the list of messages already seen
*/
private synchronized void cleanUpSeenMessages() {
List uidsToRemove = new LinkedList();
// first collect all uid's to remove
for (String uid : seenMessages) {
if (!foundMessagesInFolder.contains(uid)) {
// the message was deleted from the mail folder, so delete it
// also from the seen messages list as well
uidsToRemove.add(uid);
}
}
// now remove them
for (String uid : uidsToRemove) {
seenMessages.remove(uid);
}
}
/**
* applies custom properties to the used properties for mail server
* connection
*
* @param props the properties to apply to
*/
private void applyCustomProperties(Properties props) {
// allow custom properties
if (customProperties != null) {
props.putAll(customProperties);
}
}
public boolean isDeleteProcessedMessages() {
return this.deleteProcessedMessages;
}
/**
* This flag is used to indicate what happens to a processed mail polled
* from a mail folder. If it is set to true
the mail will
* be deleted after it was sent into the bus successfully. If set to
* false
the mail will reside inside the mail folder but will
* be marked as already seen.
* If the sending of the mail results in an error, the mail will not be
* deleted / marked and reprocessed on next run of the polling cycle.
* The default value is false
*
* @param deleteProcessedMessages
* a boolean
value as flag
*/
public void setDeleteProcessedMessages(boolean deleteProcessedMessages) {
this.deleteProcessedMessages = deleteProcessedMessages;
}
public AbstractMailMarshaler getMarshaler() {
return this.marshaler;
}
/**
*
With this method you can specify a marshaler class which provides the
* logic for converting a mail into a normalized message. This class has
* to extend the abstract class AbstractMailMarshaler
or an
* extending class. If you don't specify a marshaler, the
* DefaultMailMarshaler
will be used.
*
* @param marshaler
* a class which extends AbstractMailMarshaler
*/
public void setMarshaler(AbstractMailMarshaler marshaler) {
this.marshaler = marshaler;
}
public int getMaxFetchSize() {
return this.maxFetchSize;
}
/**
* This sets the maximum amount of mails to process within one polling cycle.
* If the maximum amount is reached all other mails in "unseen" state will
* be skipped.
* The default value is -1 (unlimited)
*
* @param maxFetchSize
* a int
value for maximum to be polled messages
*/
public void setMaxFetchSize(int maxFetchSize) {
this.maxFetchSize = maxFetchSize;
}
public boolean isProcessOnlyUnseenMessages() {
return this.processOnlyUnseenMessages;
}
/**
* This flag is used to indicate whether all mails are polled from a
* mail folder or only the unseen mails are processed.
* If it is set to true
only the unseen mails will be
* processed.
* If it is set to false
all mails will be processed.
* The default value is true
*
* @param processOnlyUnseenMessages
* a boolean
value as flag
*/
public void setProcessOnlyUnseenMessages(boolean processOnlyUnseenMessages) {
this.processOnlyUnseenMessages = processOnlyUnseenMessages;
}
public String getConnection() {
return this.connection;
}
/**
* Specifies the connection URI used to connect to a mail server.
*
* Templates:
* <protocol>://<user>@<host>[:<port>][/<folder>]?password=<password>
*
OR
* <protocol>://<host>[:<port>][/<folder>]?user=<user>;password=<password>
*
* Details:
*
*
* Name
* Description
*
*
* protocol
* the protocol to use (example: pop3 or imap)
*
*
* user
* the user name used to log into an account
*
*
* host
* the name or ip address of the mail server
*
*
* port
* the port number to use (optional)
*
*
* folder
* the folder to poll from (optional)
*
*
* password
* the password for the login
*
*
*
* Examples:
* imap://lhein@imapserver:143/INBOX?password=mypass
* pop3://pop3server/[email protected];password=mypass
* The default value is null
*
* @param connection
* a String
value containing the connection details
*/
public void setConnection(String connection) {
this.connection = connection;
try {
this.config = MailUtils.configure(this.connection);
} catch (ParseException ex) {
logger.error("The configured connection uri is invalid", ex);
}
}
public boolean isDebugMode() {
return this.debugMode;
}
/**
* Specifies if the JavaMail is run in DEBUG
mode. This means
* that while connecting to server and processing mails a detailed log
* is written to debug output.
* This mode is very handy if you are experiencing problems with your
* mail server connection and you want to find out what is going wrong
* in communication with the server.
*
* true - the debug mode is enabled
*
* false - the debug mode is disabled
* The default value is false
*
* @param debugMode
* a boolean
value for debug mode
*/
public void setDebugMode(boolean debugMode) {
this.debugMode = debugMode;
}
public String getCustomTrustManagers() {
return this.customTrustManagers;
}
/**
* Specifies one or more trust manager classes separated by a semicolon (;).
* These classes have to implement the Trustmanager
interface and need to provide
* an empty default constructor to be valid.
* If you want to accept all security certificates without a check you may
* consider using the DummyTrustManager
class. It is actually only
* an empty stub without any checking logic.
But be aware that this will be
* a security risk in production environments.
* The default value is null
*
* @param customTrustManagers
* a String
value containing one or more full class names separated by ; char
*/
public void setCustomTrustManagers(String customTrustManagers) {
this.customTrustManagers = customTrustManagers;
}
public Map getCustomProperties() {
return this.customProperties;
}
/**
* Specifies a java.util.Map
which may contain additional
* properties for the connection.
*
Example for disabling TOP for POP3 headers:
* key: "mail.pop3.disabletop"
* value: "true"
* The default value is null
*
* @param customProperties
* a java.util.Map<String, String>
containing connection properties
*/
public void setCustomProperties(Map customProperties) {
this.customProperties = customProperties;
}
public org.apache.servicemix.store.Store getStorage() {
return this.storage;
}
/**
* Specifies a org.apache.servicemix.store.Store
object which
* will be used for storing the identifications of already processed messages.
* This store is only used with the POP3 protocol and if unseen mails are
* processed only.
* The default value is null
*
* @param storage
* a org.apache.servicemix.store.Store
object for storing seen message idents
*/
public void setStorage(org.apache.servicemix.store.Store storage) {
this.storage = storage;
}
}