001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.camel.component.mail; 018 019 import java.util.LinkedList; 020 import java.util.Queue; 021 import javax.mail.Flags; 022 import javax.mail.Folder; 023 import javax.mail.FolderNotFoundException; 024 import javax.mail.Message; 025 import javax.mail.MessagingException; 026 import javax.mail.Store; 027 import javax.mail.search.FlagTerm; 028 029 import org.apache.camel.BatchConsumer; 030 import org.apache.camel.Exchange; 031 import org.apache.camel.Processor; 032 import org.apache.camel.impl.ScheduledPollConsumer; 033 import org.apache.commons.logging.Log; 034 import org.apache.commons.logging.LogFactory; 035 import org.springframework.mail.javamail.JavaMailSenderImpl; 036 037 /** 038 * A {@link org.apache.camel.Consumer Consumer} which consumes messages from JavaMail using a 039 * {@link javax.mail.Transport Transport} and dispatches them to the {@link Processor} 040 * 041 * @version $Revision: 782062 $ 042 */ 043 public class MailConsumer extends ScheduledPollConsumer implements BatchConsumer { 044 public static final long DEFAULT_CONSUMER_DELAY = 60 * 1000L; 045 private static final transient Log LOG = LogFactory.getLog(MailConsumer.class); 046 047 private final MailEndpoint endpoint; 048 private final JavaMailSenderImpl sender; 049 private Folder folder; 050 private Store store; 051 private int maxMessagesPerPoll; 052 053 public MailConsumer(MailEndpoint endpoint, Processor processor, JavaMailSenderImpl sender) { 054 super(endpoint, processor); 055 this.endpoint = endpoint; 056 this.sender = sender; 057 } 058 059 @Override 060 protected void doStart() throws Exception { 061 super.doStart(); 062 } 063 064 @Override 065 protected void doStop() throws Exception { 066 if (folder != null && folder.isOpen()) { 067 folder.close(true); 068 } 069 if (store != null && store.isConnected()) { 070 store.close(); 071 } 072 073 super.doStop(); 074 } 075 076 protected void poll() throws Exception { 077 ensureIsConnected(); 078 079 if (store == null || folder == null) { 080 throw new IllegalStateException("MailConsumer did not connect properly to the MailStore: " 081 + endpoint.getConfiguration().getMailStoreLogInformation()); 082 } 083 084 if (LOG.isDebugEnabled()) { 085 LOG.debug("Polling mailfolder: " + endpoint.getConfiguration().getMailStoreLogInformation()); 086 } 087 088 if (endpoint.getConfiguration().getFetchSize() == 0) { 089 LOG.warn("Fetch size is 0 meaning the configuration is set to poll no new messages at all. Camel will skip this poll."); 090 return; 091 } 092 093 // ensure folder is open 094 if (!folder.isOpen()) { 095 folder.open(Folder.READ_WRITE); 096 } 097 098 try { 099 int count = folder.getMessageCount(); 100 if (count > 0) { 101 Message[] messages; 102 103 // should we process all messages or only unseen messages 104 if (endpoint.getConfiguration().isUnseen()) { 105 messages = folder.search(new FlagTerm(new Flags(Flags.Flag.SEEN), false)); 106 } else { 107 messages = folder.getMessages(); 108 } 109 110 Queue<Exchange> exchanges = createExchanges(messages); 111 processBatch(exchanges); 112 113 } else if (count == -1) { 114 throw new MessagingException("Folder: " + folder.getFullName() + " is closed"); 115 } 116 } catch (Exception e) { 117 handleException(e); 118 } finally { 119 // need to ensure we release resources 120 try { 121 if (folder.isOpen()) { 122 folder.close(true); 123 } 124 } catch (Exception e) { 125 // some mail servers will lock the folder so we ignore in this case (CAMEL-1263) 126 LOG.debug("Could not close mailbox folder: " + folder.getName(), e); 127 } 128 } 129 } 130 131 public void setMaxMessagesPerPoll(int maxMessagesPerPoll) { 132 this.maxMessagesPerPoll = maxMessagesPerPoll; 133 } 134 135 public void processBatch(Queue exchanges) throws Exception { 136 int total = exchanges.size(); 137 138 // limit if needed 139 if (maxMessagesPerPoll > 0 && total > maxMessagesPerPoll) { 140 LOG.debug("Limiting to maximum messages to poll " + maxMessagesPerPoll + " as there was " + total + " messages in this poll."); 141 total = maxMessagesPerPoll; 142 } 143 144 for (int index = 0; index < total && isRunAllowed(); index++) { 145 // only loop if we are started (allowed to run) 146 MailExchange exchange = (MailExchange) exchanges.poll(); 147 // add current index and total as properties 148 exchange.setProperty(Exchange.BATCH_INDEX, index); 149 exchange.setProperty(Exchange.BATCH_SIZE, total); 150 exchange.setProperty(Exchange.BATCH_COMPLETE, index == total - 1); 151 152 // process the current exchange 153 processExchange(exchange); 154 if (!exchange.isFailed()) { 155 processCommit(exchange); 156 } else { 157 processRollback(exchange); 158 } 159 } 160 } 161 162 protected Queue<Exchange> createExchanges(Message[] messages) throws MessagingException { 163 Queue<Exchange> answer = new LinkedList<Exchange>(); 164 165 int fetchSize = endpoint.getConfiguration().getFetchSize(); 166 int count = fetchSize == -1 ? messages.length : Math.min(fetchSize, messages.length); 167 168 if (LOG.isDebugEnabled()) { 169 LOG.debug("Fetching " + count + " messages. Total " + messages.length + " messages."); 170 } 171 172 for (int i = 0; i < count; i++) { 173 Message message = messages[i]; 174 if (!message.getFlags().contains(Flags.Flag.DELETED)) { 175 MailExchange exchange = endpoint.createExchange(message); 176 answer.add(exchange); 177 } else { 178 if (LOG.isDebugEnabled()) { 179 LOG.debug("Skipping message as it was flagged as deleted: " + MailUtils.dumpMessage(message)); 180 } 181 } 182 } 183 184 return answer; 185 } 186 187 /** 188 * Strategy to process the mail message. 189 */ 190 protected void processExchange(MailExchange exchange) throws Exception { 191 if (LOG.isDebugEnabled()) { 192 LOG.debug("Processing message: " + MailUtils.dumpMessage(exchange.getIn().getMessage())); 193 } 194 getProcessor().process(exchange); 195 } 196 197 /** 198 * Strategy to flag the message after being processed. 199 */ 200 protected void processCommit(MailExchange exchange) throws MessagingException { 201 Message message = exchange.getIn().getMessage(); 202 203 if (endpoint.getConfiguration().isDelete()) { 204 LOG.debug("Exchange processed, so flagging message as DELETED"); 205 message.setFlag(Flags.Flag.DELETED, true); 206 } else { 207 LOG.debug("Exchange processed, so flagging message as SEEN"); 208 message.setFlag(Flags.Flag.SEEN, true); 209 } 210 } 211 212 /** 213 * Strategy when processing the exchange failed. 214 */ 215 protected void processRollback(MailExchange exchange) throws MessagingException { 216 LOG.warn("Exchange failed, so rolling back message status: " + exchange); 217 } 218 219 private void ensureIsConnected() throws MessagingException { 220 MailConfiguration config = endpoint.getConfiguration(); 221 222 boolean connected = false; 223 try { 224 if (store != null && store.isConnected()) { 225 connected = true; 226 } 227 } catch (Exception e) { 228 LOG.debug("Exception while testing for is connected to MailStore: " 229 + endpoint.getConfiguration().getMailStoreLogInformation() 230 + ". Caused by: " + e.getMessage(), e); 231 } 232 233 if (!connected) { 234 if (LOG.isDebugEnabled()) { 235 LOG.debug("Connecting to MailStore: " + endpoint.getConfiguration().getMailStoreLogInformation()); 236 } 237 store = sender.getSession().getStore(config.getProtocol()); 238 store.connect(config.getHost(), config.getPort(), config.getUsername(), config.getPassword()); 239 } 240 241 if (folder == null) { 242 folder = store.getFolder(config.getFolderName()); 243 if (folder == null || !folder.exists()) { 244 throw new FolderNotFoundException(folder, "Folder not found or invalid: " + config.getFolderName()); 245 } 246 } 247 } 248 249 }