Package org.xmlBlaster.util.protocol.email

Source Code of org.xmlBlaster.util.protocol.email.Pop3Driver

/*------------------------------------------------------------------------------
Name:      Pop3Driver.java
Project:   xmlBlaster.org
Copyright: xmlBlaster.org, see xmlBlaster-LICENSE file
Comment:   javac EmailData.java Pop3Driver.java
------------------------------------------------------------------------------*/
package org.xmlBlaster.util.protocol.email;

import javax.mail.NoSuchProviderException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Message;
import javax.mail.Store;
import javax.mail.Folder;
import javax.mail.Flags;
import javax.mail.Address;
import javax.mail.Authenticator;
import javax.mail.MessagingException;
import javax.mail.URLName;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimePart;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.xmlBlaster.util.Global;
import org.xmlBlaster.util.I_ResponseListener;
import org.xmlBlaster.util.I_Timeout;
import org.xmlBlaster.util.Timeout;
import org.xmlBlaster.util.Timestamp;
import org.xmlBlaster.util.XbUri;
import org.xmlBlaster.util.XmlBlasterException;
import org.xmlBlaster.util.context.ContextNode;
import org.xmlBlaster.util.def.Constants;
import org.xmlBlaster.util.def.ErrorCode;
import org.xmlBlaster.util.plugin.I_Plugin;
import org.xmlBlaster.util.plugin.I_PluginConfig;
import org.xmlBlaster.util.plugin.PluginInfo;

/**
* This class is capable to poll for emails using the POP3 protocol.
*
* Configuration is done in <code>xmlBlasterPlugins.xml</code>:
*
* <pre>
*    &lt;plugin id='pop3' className='org.xmlBlaster.util.protocol.email.Pop3Driver'&gt;
*    &lt;action do='LOAD' onStartupRunlevel='7' sequence='2'
*    onFail='resource.configuration.pluginFailed'/&gt;
*    &lt;action do='STOP' onShutdownRunlevel='7' sequence='5'/&gt;  
*    &lt;attribute id='mail.pop3.url'&gt;pop3://xmlBlaster:xmlBlaster@localhost:110/INBOX&lt;/attribute&gt;
*    &lt;attribute id='pop3PollingInterval'&gt;500&lt;/attribute&gt;
*    &lt;/plugin&gt;
* </pre>
*
* <p>
* Switch on logging with
*
* <pre>
* -logging/org.xmlBlaster.util.protocol.email.Pop3Driver FINE
* </pre>
*
* and add to xmlBlasterJdk14Logging.properties:
*
* <pre>
* handlers = org.xmlBlaster.util.log.XmlBlasterJdk14LoggingHandler.level = FINEST
* </pre>
* <p />
* Standalone test:
* <pre>
*  1. Start a command line poller for user 'xmlBlaster':
*
*  java -Dmail.pop3.url=pop3://xmlBlaster:xmlBlaster@localhost/INBOX org.xmlBlaster.util.protocol.email.Pop3Driver -receivePolling
*
* 2. Send from command line an email:
*
* java -Dmail.smtp.url=smtp://xmlBlaster:xmlBlaster@localhost org.xmlBlaster.util.protocol.email.SmtpClient -from xmlBlaster@localhost -to xmlBlaster@localhost
* </pre>
* <p>
* TODO: Implement reusing inbox.getMessage() for better performance
* currently we reconnect for each query (
* the benefit is that we don't block the POP3 server
* with a permanent connection).
* </p>
* @see <a
*      href="http://www-106.ibm.com/developerworks/java/library/j-james1.html">James
*      MTA</a>
* @see <a href="http://java.sun.com/products/javamail/javadocs/index.html">Java
*      Mail API</a>
* @see <a href="http://java.sun.com/developer/onlineTraining/JavaMail/contents.html">Javamail tutorial</a>
* @see <a
*      href="http://www.xmlBlaster.org/xmlBlaster/doc/requirements/protocol.email.html">The
*      protocol.email requirement</a>
* @author <a href="mailto:xmlBlaster@marcelruff.info">Marcel Ruff</a>
*/
public class Pop3Driver extends Authenticator
implements I_Plugin, I_Timeout,
      Pop3DriverMBean {
   private static Logger log = Logger.getLogger(Pop3Driver.class.getName());

   private Global glob;

   private ContextNode contextNode;

   private Session session;

   private String pop3Url;

   private I_PluginConfig pluginConfig;

   private Timeout timeout;

   private Timestamp timeoutHandle;

   private long pollingInterval;

   private Properties props;

   private final Map listeners = new HashMap();

   private boolean firstException = true;

   // Avoid too many logging output lines
   private boolean isConnected;

   private PasswordAuthentication authentication;

   protected XbUri xbUri;

   /** My JMX registration */
   private Object mbeanHandle;

   // Not tested and currently switched off
   private Map holdbackMap = Collections.synchronizedMap(new HashMap());

   private long holdbackExpireTimeout;

   public static final boolean CLEAR_MESSAGES = true;

   public static final boolean LEAVE_MESSAGES = false;

   public static final String POP3_FOLDER = "inbox";

   public static String threadName = "POP3Driver-pollingTimer";
  
   public static final String OBJECTENTRY_KEY = Pop3Driver.class.getName();
  
   public static final String DISCARD = "--discard--";

   private static String messageIdFileName;
  
   /**
    * The Pop3Driver is a singleton in the Global scope.
    * Access this singleton for the given global, and if it
    * doesn't exist create one instance.
    * @param glob
    * @param pluginConfig
    * @return never null
    * @throws XmlBlasterException
    */
   public static Pop3Driver getPop3Driver(Global glob, I_PluginConfig pluginConfig, String msgIdFileName)
                              throws XmlBlasterException {
      Global serverNode = (org.xmlBlaster.util.Global)glob.getObjectEntry(Constants.OBJECT_ENTRY_ServerScope);
      if (serverNode == null) serverNode = glob;
      Pop3Driver pop3Driver = (Pop3Driver)serverNode.getObjectEntry(OBJECTENTRY_KEY);
      if (pop3Driver != null)
         return pop3Driver;
     
      synchronized(glob.objectMapMonitor) {
         pop3Driver = (Pop3Driver)serverNode.getObjectEntry(OBJECTENTRY_KEY);
         if (pop3Driver == null) {
            pop3Driver = new Pop3Driver();
            // Uhhh - a downcast:
            pop3Driver.init(glob, pluginConfig); // adds itself as ObjectEntry
            pop3Driver.setMessageIdFileName(msgIdFileName);
         }
         return pop3Driver;
      }
   }

   private void setMessageIdFileName(String msgIdFileName) {
      this.messageIdFileName = msgIdFileName;
   }
   /**
    * Used by Authenticator to access user name and password
    */
   public PasswordAuthentication getPasswordAuthentication() {
      if (this.authentication == null) return null;
      if (log.isLoggable(Level.FINE))
         log.fine("Entering getPasswordAuthentication: "
               + this.authentication.toString());
      return this.authentication;
   }

   /**
    * This method is called by the PluginManager (enforced by I_Plugin). The
    * Pop3Driver singleton is registered in the Global object store.
    *
    * @see org.xmlBlaster.util.plugin.I_Plugin#init(org.xmlBlaster.util.Global,org.xmlBlaster.util.plugin.PluginInfo)
    */
   public void init(Global glob, PluginInfo pluginInfo)
         throws XmlBlasterException {
      init(glob, (I_PluginConfig)pluginInfo);
   }

   public void init(Global glob, I_PluginConfig pluginConfig)
      throws XmlBlasterException {
      this.glob = glob;
      this.pluginConfig = pluginConfig;

      // For JMX instanceName may not contain ","
      this.contextNode = new ContextNode(ContextNode.SERVICE_MARKER_TAG,
            "Pop3Driver[" + getType() + "]", this.glob
                  .getScopeContextNode());
      this.mbeanHandle = this.glob.registerMBean(this.contextNode, this);

      this.pollingInterval = glob.get("pop3PollingInterval", 2000L, null,
            this.pluginConfig);

      boolean activate = glob.get("activate", true, null, this.pluginConfig);

      // Default is 20 sec, use 0 to switch off
      this.holdbackExpireTimeout = glob.get("holdbackExpireTimeout", 20000, null,
            this.pluginConfig);

      setSessionProperties(null, glob, this.pluginConfig);

      // Make this singleton available for others
      // key="org.xmlBlaster.util.protocol.email.Pop3Driver"
      Global serverNode = (org.xmlBlaster.util.Global)this.glob.getObjectEntry(Constants.OBJECT_ENTRY_ServerScope);
      if (serverNode == null) serverNode = this.glob;
      serverNode.addObjectEntry(OBJECTENTRY_KEY, this);

      this.timeout = new Timeout(threadName);
      if (activate) {
         try {
            activate();
         } catch (Exception e) {
            throw (XmlBlasterException) e;
         }
      }
   }

   /**
    * You need to call setSessionProperties() thereafter.
    */
   public Pop3Driver() {
   }

   /**
    * Access the xmlBlaster internal name of the protocol driver.
    *
    * @return The configured [type] in xmlBlaster.properties, defaults to "pop3"
    */
   public String getProtocolId() {
      return (this.pluginConfig == null) ? "pop3" : this.pluginConfig.getType();
   }

   /**
    * Enforced by I_Plugin
    *
    * @return The configured type in xmlBlaster.properties, defaults to "pop3"
    */
   public String getType() {
      return getProtocolId();
   }

   /**
    * If you are interested in an email register it here.
    *
    * @param key
    *           <secretSessionId>:<requestId>
    * @param listener
    */
   public void registerForEmail(String secretSessionId, String requestId,
         I_ResponseListener listener) {
      if (secretSessionId == null)
         secretSessionId = "";
      if (requestId == null)
         requestId = "";
      if (secretSessionId.length() == 0 && requestId.length() == 0)
         throw new IllegalArgumentException(
               "registerForEmail with null arguments");
      String key = secretSessionId + requestId;
      synchronized (this.listeners) {
         this.listeners.put(key, listener);
      }
      if (log.isLoggable(Level.FINE))
         log.fine("Added listener with key=" + key);

      // Try to deliver hold back messages
      tryToDeliverHoldbackMails(messageIdFileName);
   }

   public Object deregisterForEmail(String secretSessionId, String requestId) {
      if (secretSessionId == null && requestId == null)
         throw new IllegalArgumentException(
               "deregisterForEmail with null arguments");
      if (secretSessionId == null)
         secretSessionId = "";
      if (requestId == null)
         requestId = "";
      String key = secretSessionId + requestId;
      synchronized (this.listeners) {
         return this.listeners.remove(key);
      }
   }

   /**
    * Deregister all existing registrations for the given listener.
    * @param listener The listener to cleanup
    * @return Number of registrations cleared
    */
   public int deregisterForEmail(I_ResponseListener listener) {
      if (listener == null)
         throw new IllegalArgumentException(
               "deregisterForEmail with null listener argument");
      int count=0;
      Map.Entry[] arr = getListenerInterfaces();
      for (int i = 0; i < arr.length; i++) {
         if (listener == arr[i].getValue()) {
            synchronized (this.listeners) {
               Object o = this.listeners.remove(arr[i].getKey());
               if (o != null) count++;
            }
         }
      }
      return count;
   }
  
   public Map.Entry[] getListenerInterfaces() {
      synchronized (this.listeners) {
         return (Map.Entry[]) this.listeners.entrySet().toArray(
               new Map.Entry[this.listeners.size()]);
      }
   }

   public String[] getListenerKeys() {
      synchronized (this.listeners) {
         return (String[]) this.listeners.keySet().toArray(
               new String[this.listeners.size()]);
      }
   }

   public String getListeners() {
      String[] arr = getListenerKeys();
      StringBuffer buf = new StringBuffer();
      for (int i = 0; i < arr.length; i++) {
         buf.append(arr[i]);
         if (i < arr.length - 1)
            buf.append(", ");
      }
      return buf.toString();
   }
  
   private void handleLostEmail(EmailData emailData) {
      // TODO: What to do with lost emails?
      /*
      try {
         emailData.convertToException(ErrorCode.COMMUNICATION);
         SmtpClient.getSmtpClient(this.glob, this.pluginConfig).sendEmail(emailData);
      }
      catch (XmlBlasterException e) {
         log.severe("Lost email: " + e.getMessage() + ": " + emailData.toXml(true));
      }
      */
   }

   /**
    * Notify a listener about a new email. The registration remains The listener
    * is searched as a "sessionId-requestId" and as a general "sessionId"
    *
    * @param emailData
    * @param calledFromHoldbackMap is true if we try a redelivery
    * @return The listener notified or null if none was found
    */
   private String notify(EmailData emailData, boolean calledFromHoldbackMap, String msgIdFileName) {
      if (emailData == null)
         return null;

      // TODO: Does not cleanup listeners!!
      // so we deliver them to the registrar and they should decide
      //if (emailData.isExpired())
      //   return DISCARD;
     
      String key = emailData.getSessionId(msgIdFileName) + emailData.getRequestId(msgIdFileName);
      I_ResponseListener listenerSession = null;
      I_ResponseListener listenerRequest = null;
      I_ResponseListener listenerClusterNodeId = null;
      synchronized (this.listeners) {
         listenerRequest = (I_ResponseListener) this.listeners.get(key);
         if (listenerRequest == null) {
            listenerSession = (I_ResponseListener) this.listeners
                  .get(emailData.getSessionId(msgIdFileName));
            if (listenerSession == null) {
               listenerClusterNodeId = (I_ResponseListener) this.listeners
                     .get(this.glob.getId());
            }
         }
      }

      // A request/reply handler is interested in specific messages only
      if (listenerRequest != null) {
         if (log.isLoggable(Level.FINER))
            log.finer("Request specific listener found for key=" + key
                  + ", email is " + emailData.toString());
         listenerRequest.incomingMessage(emailData.getRequestId(msgIdFileName),
               emailData);
         return key;
      }

      // A session is interested in all messages
      if (listenerSession != null) {
         if (log.isLoggable(Level.FINER))
            log.finer("SessRequest specific listener found for key="
                  + emailData.getSessionId(msgIdFileName) + ", email is "
                  + emailData.toString());
         listenerSession.incomingMessage(emailData.getRequestId(msgIdFileName),
               emailData);
         return emailData.getSessionId(msgIdFileName);
      }

      // A cluster node is interested in all messages (EmailDriver.java)
      if (listenerClusterNodeId != null) {
         if (log.isLoggable(Level.FINER))
            log.finer("Node specific listener found for key="
                  + this.glob.getId() + ", email is " + emailData.toString());
         listenerClusterNodeId.incomingMessage(emailData.getRequestId(msgIdFileName),
               emailData);
         return emailData.getSessionId(msgIdFileName);
      }
     
      if (calledFromHoldbackMap) {
         if (log.isLoggable(Level.FINER))
            log.finer("No registrar for holdback mail found, we try again later: " + emailData.toString());
         return null; // try again later
      }

      if (emailData.isExpired(msgIdFileName))
         return DISCARD;

      if (this.holdbackExpireTimeout > 0) {
         Timestamp timestamp = new Timestamp();
         this.holdbackMap.put(new Long(timestamp.getTimestamp()), emailData);
         log.warning("None of our registered listeners '" + getListeners()
               + "' matches for key=" + key + ", email '"
               + emailData.extractMessageId(EmailData.METHODNAME_TAG, msgIdFileName)
               + "' is holdback in RAM, we try later again");
      }
      else {
         log.warning("None of our registered listeners '" + getListeners()
               + "' matches for key=" + key + ", this email is discarded: "
               + emailData.toString());
         handleLostEmail(emailData);
      }
      return null;
   }

   /**
    * The command line key prefix
    *
    * @return The configured type in xmlBlasterPlugins.xml, defaults to
    *         "plugin/pop3"
    */
   public String getEnvPrefix() {
      return "plugin/" + getType().toLowerCase();
      // return (addressServer != null) ? addressServer.getEnvPrefix() :
      // "plugin/"+getType().toLowerCase();
   }

   /** Enforced by I_Plugin */
   public String getVersion() {
      return (this.pluginConfig == null) ? "1.0" : this.pluginConfig.getVersion();
   }

   /**
    * Set session properties and create a session.
    * <p>
    * Example settings:
    * </p>
    *
    * <pre>
    * Properties props = System.getProperties();
    * props.put(&quot;mail.pop3.url&quot;, &quot;pop3://joe:secret@localhost/INBOX&quot;);
    * props.put(&quot;mail.debug&quot;, &quot;false&quot;);
    * </pre>
    *
    * <p>
    * Usage is "pop3://user:password@host:port/INBOX". Only 'INBOX' is supported
    * for pop3. If a property is not found <tt>System.getProperty()</tt> is
    * consulted.
    * </p>
    *
    * @see <a
    *      href="http://java.sun.com/products/javamail/javadocs/com/sun/mail/smtp/package-summary.html">SMTP
    *      API</a>
    * @see <a
    *      href="http://java.sun.com/products/javamail/javadocs/com/sun/mail/pop3/package-summary.html">POP3
    *      API</a>
    */
   public synchronized void setSessionProperties(Properties properties,
         Global glob, I_PluginConfig pluginConfig) throws XmlBlasterException {
      this.props = properties;
      if (this.props == null)
         this.props = new Properties();

      if (this.props.getProperty("mail.debug") == null)
         this.props.put("mail.debug", glob.get("mail.debug", System
               .getProperty("mail.debug", "false"), null, pluginConfig));

      // "pop3://user:password@host:port/INBOX"
      this.pop3Url = glob.get("mail.pop3.url",
            System.getProperty("mail.pop3.url",
                  "pop3://" + System.getProperty("user.name") + ":" + System.getProperty("user.name") + "@127.0.0.1:110/INBOX "
                  //"pop3://xmlBlaster:xmlBlaster@localhost:110/INBOX ",
                  ), null, pluginConfig);

      try {
         this.xbUri = new XbUri(this.pop3Url);
         if (this.xbUri.getPassword() != null) {
            this.props.setProperty("mail.smtp.auth", "true"); //Indicate that authentication is required at pop3 server
            this.authentication = new PasswordAuthentication(this.xbUri.getUser(), this.xbUri.getPassword());
         }
      } catch (URISyntaxException e) {
         throw new XmlBlasterException(glob,
               ErrorCode.RESOURCE_CONFIGURATION_ADDRESS, "Pop3Driver",
               "Your URI '" + this.pop3Url +
               "' is illegal", e);
      }
     
      // Pass "this" for SMTP authentication with Authenticator
      this.session = Session.getInstance(this.props, this);

      // Produces a success logging output
      Store store = null;
      try {
         store = getStore();
      } catch (XmlBlasterException e) {
         log.warning(e.getMessage() + " We poll every " + this.pollingInterval
               + " milliseconds again.");
      }
      finally {
         try {
            if (store != null) store.close();
         } catch (MessagingException e) {
            e.printStackTrace();
         }
      }
   }

   private Long[] getHoldbackTimestamps() {
      synchronized (this.holdbackMap) {
         return (Long[]) this.holdbackMap.keySet().toArray(
               new Long[this.holdbackMap.size()]);
      }
   }

   /**
    * Try to deliver hold back messages to local registrars.
    * This happens if on startup we access POP3 messages but nobody has
    * registered yet
    * <br />
    * Switch this feature on by setting holdbackExpireTimeout to a value > 0.
    * After the given milli seconds the message is discarded.
    * <br />
    * For example a client may on startup receive update() mails before he
    * has initialized the CallbackEmailDriver. Those messages are kept in RAM
    * if the client stops immediately the mails are lost but redelivered by the server
    * after the responseTimeout.
    */
   private void tryToDeliverHoldbackMails(String msgIdFileName) {
      if (this.holdbackExpireTimeout > 0 && getNumberOfHoldbackEmails() > 0) {
         Long[] keys = getHoldbackTimestamps();
         Timestamp now = new Timestamp();
         for (int i = 0; i < keys.length; i++) {
            long tt = new Timestamp(keys[i].longValue()).getMillis();
            EmailData emailData = (EmailData)this.holdbackMap.get(keys[i]);
            if ((tt + this.holdbackExpireTimeout) < now.getMillis()) {
               log.warning("Can't deliver holdback email, we discard it now: " + emailData.toString());
               this.holdbackMap.remove(keys[i]);
               handleLostEmail(emailData);
            } else {
               String listenerKey = notify(emailData, true, msgIdFileName);
               if (listenerKey != null) {
                  if (!DISCARD.equals(listenerKey)) {
                     if (log.isLoggable(Level.FINE))
                        log.fine("Holdback email is now delivered: " + emailData.toString());
                     this.holdbackMap.remove(keys[i]);
                  }
               }
            }
         }
      }
   }

   /**
    * Polling for response messages.
    */
   public void timeout(Object userData) {
      //if (log.isLoggable(Level.FINER))
      //   log.finer("Timeout: Reading POP3 messages from " + getPop3Url());

      // TODO: Remove here again, but for now we leave it to have an expiry check
      //        we need to add a specific holdbackExpireTimeout Timer
      tryToDeliverHoldbackMails(messageIdFileName);

      try {
         EmailData[] msgs = readInbox(Pop3Driver.CLEAR_MESSAGES);
         this.firstException = true;

         boolean responseArrived = false;
         for (int i = 0; i < msgs.length; i++) {
            EmailData emailData = msgs[i];
            if (log.isLoggable(Level.FINER))
               log.finer("Got from POP3 email" + emailData.toXml(true));
            String notifiedListener = notify(emailData, false, messageIdFileName);
            if (notifiedListener == null) {
               if (log.isLoggable(Level.FINE))
                  log.fine("None of the registered listeners ("
                        + getListeners() + ") wants this email: "
                        + emailData.toXml(true));
            }
         }

         if (!responseArrived) {
            //if (log.isLoggable(Level.FINER))
            //   log.finer("No mails via POP3 found");
         }

      } catch (XmlBlasterException e) {
         if (this.firstException
               && !e.isErrorCode(ErrorCode.RESOURCE_CONFIGURATION_CONNECT))
            log.severe("[" + this.pop3Url + "] POP3 polling failed: "
                  + e.getMessage());
         else { // RESOURCE_CONFIGURATION_CONNECT is logged already
            if (log.isLoggable(Level.FINE))
               log.fine("[" + this.pop3Url + "] POP3 polling failed: "
                     + e.getMessage());
         }
         this.firstException = false;
      } catch (Throwable e) {
         log.severe("[" + this.pop3Url + "] POP3 polling failed: "
               + e.toString());
      }

      try {
         this.timeoutHandle = this.timeout.addOrRefreshTimeoutListener(this,
               this.pollingInterval, userData, this.timeoutHandle);
      } catch (XmlBlasterException e) {
         log.severe("Waiting on mail response failed: " + e.getMessage());
      }
   }

   /**
    * Access the mailing session.
    */
   public Session getSession() {
      if (this.session == null) { // after a previous shutdown()
         synchronized (this) {
            if (this.session == null) { // In such a case we should better throw an exception (the session should be initialized?!)
               Thread.dumpStack();
               if (this.xbUri != null && this.xbUri.getPassword() != null) {
                  this.props.setProperty("mail.smtp.auth", "true"); //Indicate that authentication is required at pop3 server
                  this.authentication = new PasswordAuthentication(this.xbUri.getUser(), this.xbUri.getPassword());
               }
               this.session = Session.getInstance(this.props, this);
            }
         }
      }
      return this.session;
   }

   public Message getMessage() {
      return new MimeMessage(getSession());
   }

   /**
    * Returns for example "demo@localhost" which is extracted from
    * pop3Url="pop3://demo:secret@localhost:110/INBOX"
    *
    * @return
    */
   public String getMyEmailAddress() {
      URLName urln = new URLName(this.pop3Url);
      return urln.getUsername() + "@" + urln.getHost();
   }

   /**
    * Returns for example "pop3://demo@localhost:110/INBOX" which is extracted
    * from pop3Url="pop3://demo:secret@localhost:110/INBOX"
    *
    * @return
    */
   public String getUrlWithoutPassword() {
      URLName urln = new URLName(this.pop3Url);
      return urln.getProtocol() + "://" + urln.getUsername() + "@"
            + urln.getHost()
            + ((urln.getPort() > 0) ? (":" + urln.getPort()) : "") + "/"
            + urln.getFile();
   }

   /**
    * Connect to POP3 store.
    * NOTE: You need to call store.close() after usage to cleanup resources.
    * @return never null
    * @throws XmlBlasterException
    */
   private Store getStore() throws XmlBlasterException {
      Store store = null;
      //Fails if username contains '@', should be ok for %40?
      //URLName urln = new URLName(this.pop3Url);
     
      // This constructor automatically transforms a '@' in a username to '%40'
      URLName urln = new URLName(this.xbUri.getScheme(), this.xbUri.getHost(),
            this.xbUri.getPort(), this.xbUri.getPath(),
            this.xbUri.getUser(), this.xbUri.getPassword());
      try {
         store = getSession().getStore(urln);
      } catch (NoSuchProviderException e) {
         throw new XmlBlasterException(this.glob,
               ErrorCode.RESOURCE_CONFIGURATION, Pop3Driver.class.getName(),
               "No POP3 provider for url '" + getUrlWithoutPassword()
                     + "' found", e);
      }
      try {
         store.connect();
         if (!this.isConnected) { // Avoid too many logging output
            log.info("Successfully contacted POP3 server '"
                  + getUrlWithoutPassword() + "', we poll every "
                  + this.pollingInterval + " milliseconds for emails.");
            this.isConnected = true;
         }
         return store;
      } catch (MessagingException e) {
         /*
         if (e.getNextException() != null && (e.getNextException() instanceof ConnectException)) {
            //e.detailMessage=="Connect failed";
            log.warning("POP3 server is down" + e.getMessage());
         }
         */
         if (this.isConnected) { // Avoid too many logging output
            log.warning("No POP3 server '" + this.pop3Url
                  + "' found, we poll every " + this.pollingInterval
                  + " milliseconds again:" + e.toString());
            this.isConnected = false;
         }
         if (e instanceof javax.mail.AuthenticationFailedException)
            throw new XmlBlasterException(this.glob,
                  ErrorCode.RESOURCE_CONFIGURATION_CONNECT, Pop3Driver.class
                        .getName(), "The POP3 server '" + this.pop3Url
                        + "' is not available", e);
         else
            throw new XmlBlasterException(this.glob,
                  ErrorCode.RESOURCE_CONFIGURATION_CONNECT, Pop3Driver.class
                        .getName(), "The POP3 server '"
                        + getUrlWithoutPassword() + "' is not available", e);
      }
   }

   /**
    * Read messages from mail server with POP3.
    * <p>
    * Convenience method which returns the most important fields only
    * </p>
    *
    * @param clear
    *           If CLEAR_MESSAGES=true the messages are destroyed on the server
    * @return Never null
    */
   public EmailData[] readInbox(boolean clear) throws XmlBlasterException {
      // if (isShutdown()) Does it recover automatically after a shutdown?
      // throw new XmlBlasterException(glob, ErrorCode.INTERNAL_ILLEGALSTATE,
      // Pop3Driver.class.getName(), "The plugin is shutdown");
      Store store = null;
      Folder inbox = null;
      try {
         store = getStore();
         Folder root = store.getDefaultFolder();
         inbox = root.getFolder(POP3_FOLDER);
         inbox.open(Folder.READ_WRITE);
         Message[] msgs = inbox.getMessages();
         if (msgs == null)
            msgs = new Message[0];

         EmailData[] datas = new EmailData[msgs.length];
         for (int i = 0; i < msgs.length; i++) {
            log.fine("Reading message #" + (i+1) + "/" + msgs.length + " from INBOX");
            MimeMessage msg = (MimeMessage) msgs[i];
            if (clear)
               msg.setFlag(Flags.Flag.DELETED, true);

            Address[] froms = msg.getFrom();
            String from = (froms != null && froms.length > 0) ? froms[0]
                  .toString() : "";

            Address[] arr = msg.getAllRecipients();
            if (arr == null)
               arr = new Address[0];
            String[] recips = new String[arr.length];
            for (int j = 0; j < arr.length; j++)
               recips[j] = arr[j].toString();
           
            //String content = retrieveContent(msg); // Would sometimes deliver an attachment
            String content = "";
            datas[i] = new EmailData(recips, from, msg.getSubject(), content);
           
            datas[i].setSentDate(msg.getSentDate());
            datas[i].setReplyTo((InternetAddress[])msg.getReplyTo());
           
            /*
            String[] expires = msg.getHeader(EmailData.EXPIRES_HEADER);
            // "X-xmlBlaster-ExpiryDate: 2005-12-24 16:45:55.322"
            if (expires != null && expires.length > 0) {
               // expires[0]="2005-12-24 16:45:55.322"
               String value = expires[0].trim();
               try {
                  datas[i].setExpiryTime(java.sql.Timestamp.valueOf(value)); // Change to IsoDateParser with UTC!
               }
               catch (Throwable e) {
                  System.err.println("xmlBlaster Pop3Driver.java: Ignoring illegal email header '" + expires[0] + "'");
                  e.printStackTrace();
               }
            }
            else {
            */
               // Expires: Thu, 15 Dec 2005 21:45:01 +0100 (CET)
               String[]  expires = msg.getHeader(EmailData.EXPIRES_HEADER_RFC2156);
               if (expires != null && expires.length > 0) {
                  // Date: Thu, 17 Nov 2005 16:45:12 +0100 (CET)
                  String value = expires[0].trim();
                  try {
                     datas[i].setExpiryTime(MailUtil.dateTimeTS(value));
                  }
                  catch (Throwable e) {
                     System.err.println("xmlBlaster Pop3Driver.java: Ignoring illegal email header '" + expires[0] + "'");
                     e.printStackTrace();
                  }
               }
            //}

            datas[i].setAttachments(MailUtil.accessAttachments(msg));
         }
         return datas;
      } catch (MessagingException e) {
         throw new XmlBlasterException(this.glob,
               ErrorCode.RESOURCE_CONFIGURATION, Pop3Driver.class.getName(),
               "Problems to read POP3 email from '" + getUrlWithoutPassword()
                     + "'", e);
      } finally {
         try {
            if (inbox != null) {
               inbox.close(true);
               inbox = null;
            }
         } catch (Exception e) { // MessagingException, IOException
            log.warning("Ignoring inbox close problem: " + e.toString());
         }
         try {
            if (store != null) {
               store.close();
               store = null;
            }
         } catch (Exception e) { // MessagingException, IOException
            log.warning("Ignoring store close problem: " + e.toString());
         }
      }
   }
   /**
    * @return Syntax is "pop3://user:password@host:port/INBOX"
    */
   public String getPop3Url() {
      return this.pop3Url;
   }

   /**
    * @param pop3Url
    *           Syntax is "pop3://user:password@host:port/INBOX"
    */
   public void setPop3Url(String pop3Url) {
      this.pop3Url = pop3Url;
   }

   /**
    * @return Returns the pollingInterval.
    */
   public long getPollingInterval() {
      return this.pollingInterval;
   }

   /**
    * @param pollingInterval
    *           The timeout in milliseconds.
    */
   public void setPollingInterval(long pollingInterval) {
      this.pollingInterval = pollingInterval;
   }

   /**
    * Get content text.
    *
    * @param part
    *           the MimePart to check for content
    * @return The retrieved string
    * @throws MessagingException
    * @throws IOException
    */
   protected String retrieveContent(MimePart part) throws MessagingException,
         IOException {
      if (part.isMimeType("text/plain")) {
         return part.getContent().toString();
      } else if (part.isMimeType("text/html")) {
         return part.getContent().toString();
      } else if (part.isMimeType("multipart/mixed")) {
         // Find the first body part, and determine what to do then.
         MimeMultipart multipart = (MimeMultipart) part.getContent();
         MimeBodyPart firstPart = (MimeBodyPart) multipart.getBodyPart(0);
         return retrieveContent(firstPart);
      } else if (part.isMimeType("multipart/alternative")) {
         MimeMultipart multipart = (MimeMultipart) part.getContent();
         int count = multipart.getCount();
         for (int index = 0; index < count; index++) {
            MimeBodyPart mimeBodyPart = (MimeBodyPart) multipart
                  .getBodyPart(index);
            return retrieveContent(mimeBodyPart);
         }
         return "";
      } else {
         return "";
      }
   }

   /**
    * Activate xmlBlaster access through this protocol. Triggers an immediate
    * POP3 access and starts polling thereafter
    */
   public void activate() throws Exception {
      log.fine("Entering activate()");
      try {
         this.timeoutHandle = this.timeout.addOrRefreshTimeoutListener(this, 0,
               null, this.timeoutHandle);
      } catch (XmlBlasterException e) {
         log.severe("Activating timeout listener failed: " + e.getMessage());
         throw new Exception("Activating timeout listener failed: "
               + e.getMessage());
      }
   }

   public boolean isActive() {
      return this.timeoutHandle != null;
   }

   /**
    * Deactivate xmlBlaster access (standby), no clients can connect.
    */
   public void deActivate() {
      log.fine("Entering deActivate()");
      if (this.timeout != null) {
         this.timeout.removeTimeoutListener(this.timeoutHandle);
         this.timeoutHandle = null;
      }
   }

   /**
    * Halt the plugin.
    */
   public synchronized void shutdown() {
      if (this.session != null) {
         log.info("Shutting down POP3 mail client, removing listeners");
         deActivate();
         synchronized (this.listeners) {
            this.listeners.clear();
         }
         if (this.glob != null)
            this.glob.unregisterMBean(this.mbeanHandle);
         this.session = null;
      }
   }

   public boolean isShutdown() {
      return this.session == null;
   }

   /**
    * JMX
    * @return a human readable usage help string
    */
   public java.lang.String usage() {
      return "The pop3Url has the syntax 'pop3://user:password@host:port/INBOX'"
            + "\nCalling shutdown destroys the service (you can't start it again)"
            + Global.getJmxUsageLinkInfo(this.getClass().getName(), null);
   }

   /**
    * @return A link for JMX usage
    */
   public java.lang.String getUsageUrl() {
      return Global.getJavadocUrl(this.getClass().getName(), null);
   }

   /* dummy to have a copy/paste functionality in jconsole */
   public void setUsageUrl(java.lang.String url) {
   }

   /**
    * @return Returns the holdbackExpireTimeout.
    */
   public long getHoldbackExpireTimeout() {
      return this.holdbackExpireTimeout;
   }

   /**
    * @param holdbackExpireTimeout The holdbackExpireTimeout to set.
    */
   public void setHoldbackExpireTimeout(long holdbackExpireTimeout) {
      this.holdbackExpireTimeout = holdbackExpireTimeout;
   }
  
   public int getNumberOfHoldbackEmails() {
      return this.holdbackMap.size();
   }

   /**
    * java -Dmail.pop3.url=pop3://blue:blue@localhost:110/INBOX
    * org.xmlBlaster.util.protocol.email.Pop3Driver -receivePolling true -clearMessages false
    * <p>
    *
    * @see #setSessionProperties(Properties) for other properties
    */
   public static void main(String[] args) {
      Global glob = new Global(args);
      boolean receivePolling = glob.getProperty().get("receivePolling", false);
      boolean clearMessages = glob.getProperty().get("clearMessages", Pop3Driver.CLEAR_MESSAGES);
      boolean interactive = glob.getProperty().get("interactive", true);
      long pollingInterval = glob.getProperty().get("pop3PollingInterval", 2000L);

      Pop3Driver pop3Client = new Pop3Driver();
      try {
         final boolean debug = false;

         // Here we create the pop3Client Session manually without a JNDI lookup
         Properties props = System.getProperties();
         props.put("pop3Client.debug", "" + debug);
         pop3Client.setSessionProperties(props, glob, null);

         System.out.println("Reading POP3 messages clearMessages=" + clearMessages);
         while (true) {
            long start = System.currentTimeMillis();
            EmailData[] msgs = pop3Client.readInbox(clearMessages);
            long diff = System.currentTimeMillis() - start;

            java.sql.Timestamp date = new java.sql.Timestamp(new java.util.Date().getTime());
            for (int i = 0; i < msgs.length; i++) {
               System.out.println(date.toString() + "\n" + msgs[i].toXml(true));
            }
            if (msgs.length == 0) {
               System.out.println("[" + pop3Client.getPop3Url()
                     + "] No mails over POP3 found (" + diff + " millis)");
            }
            else {
               System.out.println("[" + pop3Client.getPop3Url()
                     + " "+date.toString()+"] Got "+msgs.length +" mails (" + diff + " millis)");
            }
            if (!receivePolling)
               break;
            if (interactive) {
               int ch = Global.waitOnKeyboardHit("[" + pop3Client.getPop3Url()
                     + "] Hit a key for next polling ('q' to quit) >");
               if (ch == 'q')
                  break;
            }
            else
               Thread.sleep(pollingInterval);
         }
      } catch (Exception e) {
         e.printStackTrace();
         System.out.println(pop3Client.getPop3Url() + ": pop3Client failed: "
               + e.toString());
      } finally {
         if (pop3Client != null)
            pop3Client.shutdown();
      }
   }
}
TOP

Related Classes of org.xmlBlaster.util.protocol.email.Pop3Driver

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.