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.camel.util.CastUtils; 034 import org.apache.camel.util.ObjectHelper; 035 import org.apache.commons.logging.Log; 036 import org.apache.commons.logging.LogFactory; 037 import org.springframework.mail.javamail.JavaMailSenderImpl; 038 039 /** 040 * A {@link org.apache.camel.Consumer Consumer} which consumes messages from JavaMail using a 041 * {@link javax.mail.Transport Transport} and dispatches them to the {@link Processor} 042 * 043 * @version $Revision: 884525 $ 044 */ 045 public class MailConsumer extends ScheduledPollConsumer implements BatchConsumer { 046 public static final long DEFAULT_CONSUMER_DELAY = 60 * 1000L; 047 private static final transient Log LOG = LogFactory.getLog(MailConsumer.class); 048 049 private final MailEndpoint endpoint; 050 private final JavaMailSenderImpl sender; 051 private Folder folder; 052 private Store store; 053 private int maxMessagesPerPoll; 054 055 public MailConsumer(MailEndpoint endpoint, Processor processor, JavaMailSenderImpl sender) { 056 super(endpoint, processor); 057 this.endpoint = endpoint; 058 this.sender = sender; 059 } 060 061 @Override 062 protected void doStart() throws Exception { 063 super.doStart(); 064 } 065 066 @Override 067 protected void doStop() throws Exception { 068 if (folder != null && folder.isOpen()) { 069 folder.close(true); 070 } 071 if (store != null && store.isConnected()) { 072 store.close(); 073 } 074 075 super.doStop(); 076 } 077 078 protected void poll() throws Exception { 079 ensureIsConnected(); 080 081 if (store == null || folder == null) { 082 throw new IllegalStateException("MailConsumer did not connect properly to the MailStore: " 083 + endpoint.getConfiguration().getMailStoreLogInformation()); 084 } 085 086 if (LOG.isDebugEnabled()) { 087 LOG.debug("Polling mailfolder: " + endpoint.getConfiguration().getMailStoreLogInformation()); 088 } 089 090 if (endpoint.getConfiguration().getFetchSize() == 0) { 091 LOG.warn("Fetch size is 0 meaning the configuration is set to poll no new messages at all. Camel will skip this poll."); 092 return; 093 } 094 095 // ensure folder is open 096 if (!folder.isOpen()) { 097 folder.open(Folder.READ_WRITE); 098 } 099 100 try { 101 int count = folder.getMessageCount(); 102 if (count > 0) { 103 Message[] messages; 104 105 // should we process all messages or only unseen messages 106 if (endpoint.getConfiguration().isUnseen()) { 107 messages = folder.search(new FlagTerm(new Flags(Flags.Flag.SEEN), false)); 108 } else { 109 messages = folder.getMessages(); 110 } 111 112 processBatch(CastUtils.cast(createExchanges(messages))); 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<Object> 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 Exchange exchange = ObjectHelper.cast(Exchange.class, 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 Exchange 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(Exchange exchange) throws Exception { 191 if (LOG.isDebugEnabled()) { 192 MailMessage msg = (MailMessage) exchange.getIn(); 193 LOG.debug("Processing message: " + MailUtils.dumpMessage(msg.getMessage())); 194 } 195 getProcessor().process(exchange); 196 } 197 198 /** 199 * Strategy to flag the message after being processed. 200 */ 201 protected void processCommit(Exchange exchange) throws MessagingException { 202 MailMessage msg = (MailMessage) exchange.getIn(); 203 // Use the "original" Message, in case a copy ended up being made 204 Message message = msg.getOriginalMessage(); 205 206 if (endpoint.getConfiguration().isDelete()) { 207 LOG.debug("Exchange processed, so flagging message as DELETED"); 208 message.setFlag(Flags.Flag.DELETED, true); 209 } else { 210 LOG.debug("Exchange processed, so flagging message as SEEN"); 211 message.setFlag(Flags.Flag.SEEN, true); 212 } 213 } 214 215 /** 216 * Strategy when processing the exchange failed. 217 */ 218 protected void processRollback(Exchange exchange) throws MessagingException { 219 LOG.warn("Exchange failed, so rolling back message status: " + exchange); 220 } 221 222 private void ensureIsConnected() throws MessagingException { 223 MailConfiguration config = endpoint.getConfiguration(); 224 225 boolean connected = false; 226 try { 227 if (store != null && store.isConnected()) { 228 connected = true; 229 } 230 } catch (Exception e) { 231 LOG.debug("Exception while testing for is connected to MailStore: " 232 + endpoint.getConfiguration().getMailStoreLogInformation() 233 + ". Caused by: " + e.getMessage(), e); 234 } 235 236 if (!connected) { 237 // ensure resources get recreated on reconnection 238 store = null; 239 folder = null; 240 241 if (LOG.isDebugEnabled()) { 242 LOG.debug("Connecting to MailStore: " + endpoint.getConfiguration().getMailStoreLogInformation()); 243 } 244 store = sender.getSession().getStore(config.getProtocol()); 245 store.connect(config.getHost(), config.getPort(), config.getUsername(), config.getPassword()); 246 } 247 248 if (folder == null) { 249 if (LOG.isDebugEnabled()) { 250 LOG.debug("Getting folder " + config.getFolderName()); 251 } 252 folder = store.getFolder(config.getFolderName()); 253 if (folder == null || !folder.exists()) { 254 throw new FolderNotFoundException(folder, "Folder not found or invalid: " + config.getFolderName()); 255 } 256 } 257 } 258 259 }