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: 788248 $ 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 Exchange exchange = (Exchange)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 Message message = msg.getMessage(); 204 205 if (endpoint.getConfiguration().isDelete()) { 206 LOG.debug("Exchange processed, so flagging message as DELETED"); 207 message.setFlag(Flags.Flag.DELETED, true); 208 } else { 209 LOG.debug("Exchange processed, so flagging message as SEEN"); 210 message.setFlag(Flags.Flag.SEEN, true); 211 } 212 } 213 214 /** 215 * Strategy when processing the exchange failed. 216 */ 217 protected void processRollback(Exchange exchange) throws MessagingException { 218 LOG.warn("Exchange failed, so rolling back message status: " + exchange); 219 } 220 221 private void ensureIsConnected() throws MessagingException { 222 MailConfiguration config = endpoint.getConfiguration(); 223 224 boolean connected = false; 225 try { 226 if (store != null && store.isConnected()) { 227 connected = true; 228 } 229 } catch (Exception e) { 230 LOG.debug("Exception while testing for is connected to MailStore: " 231 + endpoint.getConfiguration().getMailStoreLogInformation() 232 + ". Caused by: " + e.getMessage(), e); 233 } 234 235 if (!connected) { 236 if (LOG.isDebugEnabled()) { 237 LOG.debug("Connecting to MailStore: " + endpoint.getConfiguration().getMailStoreLogInformation()); 238 } 239 store = sender.getSession().getStore(config.getProtocol()); 240 store.connect(config.getHost(), config.getPort(), config.getUsername(), config.getPassword()); 241 } 242 243 if (folder == null) { 244 folder = store.getFolder(config.getFolderName()); 245 if (folder == null || !folder.exists()) { 246 throw new FolderNotFoundException(folder, "Folder not found or invalid: " + config.getFolderName()); 247 } 248 } 249 } 250 251 }