001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: */
019:
020: package org.apache.axis2.transport.mail;
021:
022: import org.apache.axiom.soap.SOAP12Constants;
023: import org.apache.axiom.soap.SOAPEnvelope;
024: import org.apache.axis2.AxisFault;
025: import org.apache.axis2.Constants;
026: import org.apache.axis2.addressing.EndpointReference;
027: import org.apache.axis2.builder.BuilderUtil;
028: import org.apache.axis2.context.*;
029: import org.apache.axis2.context.MessageContext;
030: import org.apache.axis2.description.*;
031: import org.apache.axis2.i18n.Messages;
032: import org.apache.axis2.transport.TransportListener;
033: import org.apache.axis2.transport.TransportUtils;
034: import org.apache.axis2.util.Utils;
035: import org.apache.axis2.wsdl.WSDLConstants;
036: import org.apache.commons.logging.Log;
037: import org.apache.commons.logging.LogFactory;
038:
039: import javax.mail.*;
040: import javax.mail.internet.MimeMessage;
041: import javax.xml.stream.XMLStreamException;
042: import java.io.File;
043: import java.io.IOException;
044: import java.io.InputStream;
045: import java.util.ArrayList;
046: import java.util.Hashtable;
047: import java.util.Iterator;
048: import java.util.Properties;
049:
050: /**
051: * This is the implementation for Mail Listener in Axis2. It has the full capability
052: * of connecting to a POP3 or IMPA server with SSL or regualar connection. This listener intend
053: * to use as a server in client side as well with the involcation is Async with addressing.
054: */
055:
056: public class SimpleMailListener implements Runnable, TransportListener {
057: private static final Log log = LogFactory
058: .getLog(SimpleMailListener.class);
059:
060: private ConfigurationContext configurationContext = null;
061:
062: private boolean running = true;
063: /*password and replyTo is Axis2 specific*/
064: private String user = "";
065: private String replyTo = "";
066:
067: /*This hold properties for pop3 or impa server connection*/
068: private Properties pop3Properties = new Properties();
069:
070: private final EmailReceiver receiver;
071:
072: /**
073: * Time has been put from best guest. Let the default be 3 mins.
074: * This value is configuralble from Axis2.xml. Under mail transport listener
075: * simply set the following parameter.
076: * <parameter name="transport.listener.interval">[custom listener interval]</parameter>
077: */
078: private int listenerWaitInterval = 1000 * 60 * 3;
079:
080: public SimpleMailListener() {
081: receiver = new EmailReceiver();
082: }
083:
084: public void init(ConfigurationContext configurationContext,
085: TransportInDescription transportIn) throws AxisFault {
086: this .configurationContext = configurationContext;
087:
088: ArrayList mailParameters = transportIn.getParameters();
089:
090: String password = "";
091: String host = "";
092: String protocol = "";
093: String port = "";
094: URLName urlName;
095:
096: for (Iterator iterator = mailParameters.iterator(); iterator
097: .hasNext();) {
098: Parameter param = (Parameter) iterator.next();
099: String paramKey = param.getName();
100: String paramValue = Utils.getParameterValue(param);
101: if (paramKey == null || paramValue == null) {
102: String error = Messages.getMessage("canNotBeNull",
103: "Parameter name and value");
104: log.error(error);
105: throw new AxisFault(error);
106:
107: }
108: pop3Properties.setProperty(paramKey, paramValue);
109: if (paramKey
110: .equals(org.apache.axis2.transport.mail.Constants.POP3_USER)) {
111: user = paramValue;
112: }
113: if (paramKey
114: .equals(org.apache.axis2.transport.mail.Constants.POP3_PASSWORD)) {
115: password = paramValue;
116: }
117: if (paramKey
118: .equals(org.apache.axis2.transport.mail.Constants.POP3_HOST)) {
119: host = paramValue;
120: }
121: if (paramKey
122: .equals(org.apache.axis2.transport.mail.Constants.STORE_PROTOCOL)) {
123: protocol = paramValue;
124: }
125: if (paramKey
126: .equals(org.apache.axis2.transport.mail.Constants.POP3_PORT)) {
127: port = paramValue;
128: }
129:
130: //Transport specific
131: if (paramKey
132: .equals(org.apache.axis2.transport.mail.Constants.REPLY_TO)) {
133: replyTo = paramValue;
134: }
135: if (paramKey
136: .equals(org.apache.axis2.transport.mail.Constants.LISTENER_INTERVAL)) {
137: listenerWaitInterval = Integer.parseInt(paramValue);
138: }
139:
140: }
141: if (password.length() == 0 || user.length() == 0
142: || host.length() == 0 || protocol.length() == 0) {
143: String error = SimpleMailListener.class.getName()
144: + " one or more of Password, User, Host and Protocol are null or empty";
145: log.error(error);
146: throw new AxisFault(error);
147: }
148:
149: if (port.length() == 0) {
150: urlName = new URLName(protocol, host, -1, "", user,
151: password);
152: } else {
153: urlName = new URLName(protocol, host, Integer
154: .parseInt(port), "", user, password);
155: }
156:
157: receiver.setPop3Properties(pop3Properties);
158: receiver.setUrlName(urlName);
159: Object obj = configurationContext
160: .getProperty(org.apache.axis2.transport.mail.Constants.MAPPING_TABLE);
161: if (obj == null) {
162: configurationContext
163: .setProperty(
164: org.apache.axis2.transport.mail.Constants.MAPPING_TABLE,
165: new Hashtable());
166: }
167:
168: Object callBackTable = configurationContext
169: .getProperty(org.apache.axis2.transport.mail.Constants.CALLBACK_TABLE);
170: if (callBackTable == null) {
171: configurationContext
172: .setProperty(
173: org.apache.axis2.transport.mail.Constants.CALLBACK_TABLE,
174: new Hashtable());
175: }
176:
177: }
178:
179: public void initFromRuntime(Properties properties,
180: MessageContext msgContext) throws AxisFault {
181:
182: this .configurationContext = msgContext
183: .getConfigurationContext();
184:
185: String password = "";
186: String host = "";
187: String protocol = "";
188: String port = "";
189: URLName urlName;
190:
191: pop3Properties.clear();
192: pop3Properties.putAll(properties);
193:
194: user = properties
195: .getProperty(org.apache.axis2.transport.mail.Constants.POP3_USER);
196: password = properties
197: .getProperty(org.apache.axis2.transport.mail.Constants.POP3_PASSWORD);
198: host = properties
199: .getProperty(org.apache.axis2.transport.mail.Constants.POP3_HOST);
200: protocol = properties
201: .getProperty(org.apache.axis2.transport.mail.Constants.STORE_PROTOCOL);
202: port = properties
203: .getProperty(org.apache.axis2.transport.mail.Constants.POP3_PORT);
204: replyTo = properties
205: .getProperty(org.apache.axis2.transport.mail.Constants.REPLY_TO);
206: String value = properties
207: .getProperty(org.apache.axis2.transport.mail.Constants.LISTENER_INTERVAL);
208: if (value != null) {
209: listenerWaitInterval = Integer.parseInt(value);
210: }
211:
212: if (password.length() == 0 || user.length() == 0
213: || host.length() == 0 || protocol.length() == 0) {
214: String error = SimpleMailListener.class.getName()
215: + " one or more of Password, User,"
216: + " Host and Protocol are null or empty"
217: + "in runtime settings";
218: log.error(error);
219: throw new AxisFault(error);
220: }
221:
222: if (port == null) {
223: urlName = new URLName(protocol, host, -1, "", user,
224: password);
225: } else {
226: urlName = new URLName(protocol, host, Integer
227: .parseInt(port), "", user, password);
228: }
229:
230: receiver.setPop3Properties(pop3Properties);
231: receiver.setUrlName(urlName);
232: Object obj = configurationContext
233: .getProperty(org.apache.axis2.transport.mail.Constants.MAPPING_TABLE);
234: if (obj == null) {
235: configurationContext
236: .setProperty(
237: org.apache.axis2.transport.mail.Constants.MAPPING_TABLE,
238: new Hashtable());
239: }
240: Object callBackTable = configurationContext
241: .getProperty(org.apache.axis2.transport.mail.Constants.CALLBACK_TABLE);
242: if (callBackTable == null) {
243: configurationContext
244: .setProperty(
245: org.apache.axis2.transport.mail.Constants.CALLBACK_TABLE,
246: new Hashtable());
247: }
248: }
249:
250: /**
251: * Server process.
252: */
253: public static void main(String args[]) throws AxisFault {
254: if (args.length < 2) {
255: log.info("java SimpleMailListener <repository>");
256: printUsage();
257: } else {
258: String path = args[0];
259: String axis2xml = args[1];
260: ConfigurationContext configurationContext;
261: File repo = new File(path);
262: if (repo.exists()) {
263: configurationContext = ConfigurationContextFactory
264: .createConfigurationContextFromFileSystem(path,
265: axis2xml);
266: } else {
267: printUsage();
268: throw new AxisFault("repository not found");
269: }
270: SimpleMailListener sas = new SimpleMailListener();
271: TransportInDescription transportIn = configurationContext
272: .getAxisConfiguration().getTransportIn(
273: Constants.TRANSPORT_MAIL);
274: if (transportIn != null) {
275: sas.init(configurationContext, transportIn);
276: log
277: .info("Starting the SimpleMailListener with repository "
278: + new File(args[0]).getAbsolutePath());
279: sas.start();
280: } else {
281: log
282: .info("Startup failed, mail transport not configured, Configure the mail trnasport in the axis2.xml file");
283: }
284: }
285: }
286:
287: private static void printUsage() {
288: System.out
289: .println("Please provide the repository location and axis2.xml location ");
290: }
291:
292: /**
293: * Accept requests from a given TCP port and send them through the Axis
294: * engine for processing.
295: */
296: public void run() {
297:
298: // Accept and process requests from the socket
299: if (running) {
300: log.info("Mail listner strated to listen to the address "
301: + user);
302: }
303:
304: while (running) {
305: log.info("Info started polling");
306: try {
307: synchronized (receiver) {
308: receiver.connect();
309:
310: Message[] msgs = receiver.receiveMessages();
311:
312: if ((msgs != null) && (msgs.length > 0)) {
313: log.info(msgs.length + " Message(s) Found");
314:
315: for (int i = 0; i < msgs.length; i++) {
316: MimeMessage msg = (MimeMessage) msgs[i];
317: try {
318: MessageContext mc = createMessageContextToMailWorker(msg);
319: msg.setFlag(Flags.Flag.DELETED, true);
320: if (mc == null) {
321: continue;
322: }
323: MailWorker worker = new MailWorker(
324: configurationContext, mc);
325: this .configurationContext
326: .getThreadPool()
327: .execute(worker);
328: } catch (Exception e) {
329: log
330: .error(
331: "Error in SimpleMailListener - processing mail",
332: e);
333: } finally {
334: // delete mail in any case
335: }
336: }
337: }
338:
339: receiver.disconnect();
340: }
341:
342: } catch (Exception e) {
343: log.error("Error in SimpleMailListener", e);
344: } finally {
345: try {
346: Thread.sleep(listenerWaitInterval);
347: } catch (InterruptedException e) {
348: log.warn("Error Encountered " + e);
349: }
350: }
351: }
352:
353: }
354:
355: private MessageContext createMessageContextToMailWorker(
356: MimeMessage msg) throws Exception {
357: Object content = msg.getContent();
358: if (!(content instanceof Multipart)) {
359: return null;
360: }
361: MessageContext msgContext = null;
362: TransportInDescription transportIn = configurationContext
363: .getAxisConfiguration().getTransportIn(
364: org.apache.axis2.Constants.TRANSPORT_MAIL);
365: TransportOutDescription transportOut = configurationContext
366: .getAxisConfiguration().getTransportOut(
367: org.apache.axis2.Constants.TRANSPORT_MAIL);
368: if ((transportIn != null) && (transportOut != null)) {
369: // create Message Context
370: msgContext = configurationContext.createMessageContext();
371: msgContext.setTransportIn(transportIn);
372: msgContext.setTransportOut(transportOut);
373: msgContext.setServerSide(true);
374: msgContext
375: .setProperty(
376: org.apache.axis2.transport.mail.Constants.CONTENT_TYPE,
377: msg.getContentType());
378: msgContext
379: .setIncomingTransportName(org.apache.axis2.Constants.TRANSPORT_MAIL);
380:
381: MailBasedOutTransportInfo transportInfo = new MailBasedOutTransportInfo();
382: Address[] mimefroms = msg.getFrom();
383: if (mimefroms != null && mimefroms.length > 0) {
384: EndpointReference fromEPR = new EndpointReference(
385: org.apache.axis2.transport.mail.Constants.MAILTO
386: + ":" + msg.getFrom()[0].toString());
387: transportInfo.setFrom(fromEPR);
388: }
389:
390: // Save Message-Id to set as In-Reply-To on reply
391: String smtpMessageId = msg.getMessageID();
392: if (smtpMessageId != null) {
393: transportInfo.setInReplyTo(smtpMessageId);
394: }
395: String inReplyTo = getMailHeader(
396: msg,
397: org.apache.axis2.transport.mail.Constants.IN_REPLY_TO);
398: if (inReplyTo != null) {
399: transportInfo.setInReplyTo(inReplyTo);
400: }
401: msgContext.setProperty(
402: org.apache.axis2.Constants.OUT_TRANSPORT_INFO,
403: transportInfo);
404:
405: buildSOAPEnvelope(msg, msgContext);
406: if (!fillMessageContextFromAvaiableData(msgContext,
407: inReplyTo)) {
408: return null;
409: }
410: }
411: return msgContext;
412: }
413:
414: private boolean fillMessageContextFromAvaiableData(
415: MessageContext msgContext, String messageID)
416: throws AxisFault {
417: Hashtable mappingTable = (Hashtable) configurationContext
418: .getProperty(org.apache.axis2.transport.mail.Constants.MAPPING_TABLE);
419:
420: if (mappingTable != null && messageID != null) {
421: String messageConetextId = (String) mappingTable
422: .get(messageID);
423: if (messageConetextId != null) {
424: OperationContext opContext = configurationContext
425: .getOperationContext(messageConetextId);
426: if (opContext != null && !opContext.isComplete()) {
427: AxisOperation axisOp = opContext.getAxisOperation();
428: //TODO need to handle fault case as well ,
429: //TODO need to check whether the message contains fault , if so we need to get the fault message
430: AxisMessage inMessage = axisOp
431: .getMessage(WSDLConstants.MESSAGE_LABEL_IN_VALUE);
432: msgContext.setOperationContext(opContext);
433: msgContext.setAxisMessage(inMessage);
434: opContext.addMessageContext(msgContext);
435: msgContext.setServiceContext(opContext
436: .getServiceContext());
437: }
438: }
439: }
440: Hashtable callBackTable = (Hashtable) configurationContext
441: .getProperty(org.apache.axis2.transport.mail.Constants.CALLBACK_TABLE);
442: if (messageID != null && callBackTable != null) {
443: SynchronousMailListener listener = (SynchronousMailListener) callBackTable
444: .get(messageID);
445: if (listener != null) {
446: listener.setInMessageContext(msgContext);
447: return false;
448: }
449: }
450: return true;
451: }
452:
453: private void buildSOAPEnvelope(MimeMessage msg,
454: MessageContext msgContext) throws AxisFault {
455: //TODO we assume for the time being that there is only one attachement and this attachement contains the soap evelope
456: try {
457: Multipart mp = (Multipart) msg.getContent();
458: if (mp != null) {
459: for (int i = 0, n = mp.getCount(); i < n; i++) {
460: Part part = mp.getBodyPart(i);
461:
462: String disposition = part.getDisposition();
463:
464: if (disposition != null
465: && disposition
466: .equalsIgnoreCase(Part.ATTACHMENT)) {
467: String soapAction;
468:
469: /* Set the Charactorset Encoding */
470: String contentType = part.getContentType();
471: String charSetEncoding = BuilderUtil
472: .getCharSetEncoding(contentType);
473: if (charSetEncoding != null) {
474: msgContext
475: .setProperty(
476: org.apache.axis2.Constants.Configuration.CHARACTER_SET_ENCODING,
477: charSetEncoding);
478: } else {
479: msgContext
480: .setProperty(
481: org.apache.axis2.Constants.Configuration.CHARACTER_SET_ENCODING,
482: MessageContext.DEFAULT_CHAR_SET_ENCODING);
483: }
484:
485: /* SOAP Action */
486: soapAction = getMailHeaderFromPart(
487: part,
488: org.apache.axis2.transport.mail.Constants.HEADER_SOAP_ACTION);
489: msgContext.setSoapAction(soapAction);
490:
491: String contentDescription = getMailHeaderFromPart(
492: part, "Content-Description");
493:
494: /* As an input stream - using the getInputStream() method.
495: Any mail-specific encodings are decoded before this stream is returned.*/
496: if (contentDescription != null) {
497: msgContext.setTo(new EndpointReference(
498: contentDescription));
499: }
500:
501: if (contentType
502: .indexOf(SOAP12Constants.SOAP_12_CONTENT_TYPE) > -1) {
503: TransportUtils.processContentTypeForAction(
504: contentType, msgContext);
505: } else {
506: // According to the mail sepec, mail transport should support only
507: // application/soap+xml;
508: String message = "According to the mail sepec, mail transport "
509: + "should support only application/soap+xml";
510: log.error(message);
511: throw new AxisFault(message);
512: }
513:
514: String cte = getMailHeaderFromPart(part,
515: "Content-Transfer-Encoding");
516: if (!(cte != null && cte
517: .equalsIgnoreCase("base64"))) {
518: String message = "Processing of Content-Transfer-Encoding faild.";
519: log.error(message);
520: throw new AxisFault(message);
521: }
522: InputStream inputStream = part.getInputStream();
523: SOAPEnvelope envelope = TransportUtils
524: .createSOAPMessage(msgContext,
525: inputStream, contentType);
526: msgContext.setEnvelope(envelope);
527: }
528: }
529:
530: }
531: } catch (IOException e) {
532: throw new AxisFault(e.getMessage(), e);
533: } catch (MessagingException e) {
534: throw new AxisFault(e.getMessage(), e);
535: } catch (XMLStreamException e) {
536: throw new AxisFault(e.getMessage(), e);
537: }
538: }
539:
540: private String getMailHeader(MimeMessage msg, String headerName)
541: throws AxisFault {
542: try {
543: String values[] = msg.getHeader(headerName);
544:
545: if (values != null) {
546: return parseHeaderForQuotes(values[0]);
547: } else {
548: return null;
549: }
550: } catch (MessagingException e) {
551: throw new AxisFault(e.getMessage(), e);
552: }
553: }
554:
555: private String parseHeaderForQuotes(String value) {
556: if (value != null) {
557: if (value.length() > 1 && value.startsWith("\"")
558: && value.endsWith("\"")) {
559: value = value.substring(1, value.length() - 1);
560: }
561:
562: }
563: return value;
564: }
565:
566: private String getMailHeaderFromPart(Part part, String headerName)
567: throws AxisFault {
568: try {
569: String values[] = part.getHeader(headerName);
570:
571: if (values != null) {
572: return parseHeaderForQuotes(values[0]);
573: } else {
574: return null;
575: }
576: } catch (MessagingException e) {
577: throw new AxisFault(e.getMessage(), e);
578: }
579: }
580:
581: /**
582: * Start this listener
583: */
584: public void start() throws AxisFault {
585: this .configurationContext.getThreadPool().execute(this );
586: }
587:
588: /**
589: * Stop this server.
590: * <p/>
591: */
592: public void stop() {
593: running = false;
594: log.info("Stopping the mail listner");
595: }
596:
597: public EndpointReference getEPRForService(String serviceName,
598: String ip) throws AxisFault {
599: return getEPRsForService(serviceName, ip)[0];
600: }
601:
602: public EndpointReference[] getEPRsForService(String serviceName,
603: String ip) throws AxisFault {
604: return new EndpointReference[] {
605: new EndpointReference(
606: Constants.TRANSPORT_MAIL
607: + ":"
608: + replyTo
609: + "?"
610: + org.apache.axis2.transport.mail.Constants.X_SERVICE_PATH
611: + "="
612: + configurationContext
613: .getServiceContextPath() + "/"
614: + serviceName),
615: new EndpointReference(Constants.TRANSPORT_MAIL + ":"
616: + replyTo + "?"
617: + configurationContext.getServiceContextPath()
618: + "/" + serviceName) };
619: }
620:
621: public SessionContext getSessionContext(
622: MessageContext messageContext) {
623: return null;
624: }
625:
626: public void destroy() {
627: this.configurationContext = null;
628: }
629: }
|