Package org.xmlBlaster.util.protocol.email

Source Code of org.xmlBlaster.util.protocol.email.EmailExecutor$LoopProtection

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

import org.xmlBlaster.util.ReplaceVariable;
import org.xmlBlaster.util.Global;
import org.xmlBlaster.util.I_ResponseListener;
import org.xmlBlaster.util.SessionName;
import org.xmlBlaster.util.XmlBlasterException;
import org.xmlBlaster.util.def.Constants;
import org.xmlBlaster.util.def.ErrorCode;
import org.xmlBlaster.util.def.MethodName;
import org.xmlBlaster.util.plugin.I_PluginConfig;
import org.xmlBlaster.util.plugin.PluginInfo;
import org.xmlBlaster.util.protocol.RequestReplyExecutor;
import org.xmlBlaster.util.protocol.email.Pop3Driver;
import org.xmlBlaster.util.protocol.email.SmtpClient;
import org.xmlBlaster.util.protocol.email.EmailData;
import org.xmlBlaster.util.qos.address.AddressBase;
import org.xmlBlaster.util.xbformat.MsgInfo;
import org.xmlBlaster.util.xbformat.MsgInfoParserFactory;
import org.xmlBlaster.util.xbformat.XbfParser;
import org.xmlBlaster.util.MsgUnitRaw;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.Map;
import java.util.TreeMap;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;

/**
* Base class to handle request/reply for emails.
* <p>
*
* @author <a href="mailto:xmlBlaster@marcelruff.info">Marcel Ruff</a>
*/
public abstract class EmailExecutor extends  RequestReplyExecutor implements I_ResponseListener, EmailExecutorMBean {
   private String ME = "EmailExecutor";

   private static Logger log = Logger.getLogger(EmailExecutor.class.getName());
  
   private boolean isShutdown;

   private AddressBase addressBase;

   private I_PluginConfig pluginConfig;

   protected InternetAddress fromAddress;

   protected InternetAddress toAddress;
  
   protected String cc;

   protected String bcc;

   protected SmtpClient smtpClient;

   private String secretSessionId = "";
  
   private String emailSessionId = "";

   protected Pop3Driver pop3Driver;
  
   private Deflater compressor;
  
   private Inflater decompressor;
  
   /** Which message format parser to use */
   protected String msgInfoParserClassName;
  
   protected Map senderLoopProtectionMap = new TreeMap();
   /**
    * Use to protect against looping messages
    */
   protected class LoopProtection {
      LoopProtection(String key, long lastRequestId, long lastPingRequestId) {
         this.key = key;
         this.lastRequestId = lastRequestId;
         this.lastPingRequestId = lastPingRequestId;
      }
      public String key; // email.from address
      //protected String lastSessionId;
      /** Use to protect against looping messages, is a monotonous ascending timestamp */
      public long lastRequestId=-1;
      /** Ping runs in another thread, we need to protect seperately (an update can legally overtake a ping and vice versa) */
      public long lastPingRequestId=-1;
   }
  
   protected final String BOUNCE_MESSAGEID_KEY = "bounce:messageId";
   public static final String BOUNCE_MAILTO_KEY = "mail.to";
   public static final String BOUNCE_MAILFROM_KEY = "mail.from";

   /** 'messageId.mid' */
   protected String messageIdFileName = "messageId" + EmailData.MESSAGEID_EXTENSION;
  
   /** The extension is added later to for example "xmlBlasterMessage.xfb" */
   protected String payloadFileNamePrefix = "xmlBlasterMessage";
  
   protected String subjectTemplate;
  
   protected final String SUBJECT_MESSAGEID_TOKEN = "$_{xmlBlaster/email/messageId}";

   /**
    * This init() is called after the init(Global, PluginInfo)
    *
    * @param addressBase
    *           Contains the email TO: address
    */
   public void init(Global glob, AddressBase addressBase, PluginInfo pluginConfig)
         throws XmlBlasterException {
      this.addressBase = addressBase;
      this.pluginConfig = pluginConfig;
      this.compressor = new Deflater(Deflater.BEST_COMPRESSION);
      this.decompressor = new Inflater();

      // Add
      //   CbProtocolPlugin[email][1.0]=org.xmlBlaster.protocol.email.CallbackEmailDriver,mail.user=xmlBlaster,mail.password=xmlBlaster,compress/type=zlib:stream
      //   ClientCbServerProtocolPlugin[email][1.0]=org.xmlBlaster.client.protocol.email.EmailCallbackImpl,mail.user=demo,mail.password=demo,mail.pop3.url=pop3://demo:demo@localhost/INBOX,compress/type=zlib:stream
      //   ClientProtocolPlugin[email][1.0]=org.xmlBlaster.client.protocol.email.EmailConnection,mail.user=demo,mail.password=demo,mail.pop3.url=pop3://demo:demo@localhost/INBOX,pop3PollingInterval=1000,compress/type=zlib:stream
      // settings to the clients Address configuration
      if (this.pluginConfig != null)
         this.addressBase.setPluginInfoParameters(this.pluginConfig.getParameters());

      super.initialize(glob, addressBase);
     
      this.secretSessionId = addressBase.getSecretSessionId();

      // The email address to reach the xmlBlaster server (if client side)
      setTo(this.glob.get("mail.smtp.to", "xmlBlaster@localhost", null,
            pluginConfig));
      String to = addressBase.getRawAddress();
      if (to != null && to.length() > 0) {
         // The xmlBlaster address is given on client side
         // but not on server side
         setTo(to);
      }

      Object serverScope = glob.getObjectEntry(Constants.OBJECT_ENTRY_ServerScope); // e.g. in cluster environment
      String fromm = (serverScope != null) ? ((Global)serverScope).getContextNode().getInstanceNameNotNull() : "unknown";
      // from="xmlBlaster@localhost"
      setFrom(this.glob.get("mail.smtp.from",
            fromm+"@localhost", null, this.pluginConfig));

      setCc(this.glob.get("mail.smtp.cc",
            "", null, this.pluginConfig));

      setBcc(this.glob.get("mail.smtp.bcc",
            "", null, this.pluginConfig));
     
      // if template contains SUBJECT_MESSAGEID_TOKEN = "${xmlBlaster/email/messageId}"
      // this will be replaced by the current messageId
      this.subjectTemplate = this.glob.get("mail.subject",
            "XmlBlaster Generated Email "+SUBJECT_MESSAGEID_TOKEN, null, this.pluginConfig);

      messageIdFileName = this.glob.get("mail.messageIdFileName", messageIdFileName, null, pluginConfig);

      this.isShutdown = false;
      if (log.isLoggable(Level.FINE)) log.fine("Initialized email connector from=" + this.fromAddress.toString() + " to=" + to);
   }

   /**
    * Access the Pop3Driver.
    * @return never null
    */
   public Pop3Driver getPop3Driver() throws XmlBlasterException {
      if (this.pop3Driver == null) {
         this.pop3Driver = (Pop3Driver) glob.getObjectEntry(Pop3Driver.OBJECTENTRY_KEY); // Global.instance()
        
         if (this.pop3Driver == null) {
            if (this.glob.isServerSide()) {
               // On server side the Pop3Driver is created by the runlevel manager as configured in xmlBlasterPlugins.xml
               String text = "Please register a Pop3Driver in xmlBlasterPlugins.xml to have 'email' support";
               // If the session was loaded on startup from persistent store we shouldn't to a log.warn
               // but how to detect this? We don't have connectQosServer.isFromPersistenceRecovery(true);
               log.warning(text);
               // Throw a communication exception to go to polling until Pop3Driver is available
               throw new XmlBlasterException(glob, ErrorCode.COMMUNICATION_NOCONNECTION, ME, text);
            }
            // On client side we create it dynamically as configured in xmlBlaster.properties
            this.pop3Driver = Pop3Driver.getPop3Driver(glob, this.pluginConfig, messageIdFileName);
         }
      }
      return this.pop3Driver;
   }
  
   /**
    * TODO: Put into engine.Global and util.Global (see EventPlugin.java)
    * @return
    * @throws XmlBlasterException
    */
   public SmtpClient getSmtpClient() throws XmlBlasterException {
      if (this.smtpClient == null) {
         this.smtpClient = (SmtpClient) glob.getObjectEntry(SmtpClient.OBJECTENTRY_KEY);

         if (this.smtpClient == null) {
            if (this.glob.isServerSide()) {
               // On server side the SmtpClient is created by the runlevel manager as configured in xmlBlasterPlugins.xml
               String text = "Please register a SmtpClient in xmlBlasterPlugins.xml to have 'email' support";
               // If the session was loaded on startup from persistent store we shouldn't to a log.warn
               // but how to detect this?
               log.warning(text);
               throw new XmlBlasterException(glob, ErrorCode.COMMUNICATION_NOCONNECTION, ME, text);
            }
            // On client side we create it dynamically as configured in xmlBlaster.properties
            this.smtpClient = SmtpClient.getSmtpClient(this.glob, this.pluginConfig);
         }
      }
      return this.smtpClient;
   }
  
  // public String getType() {
  //    return "email";
  // }
  
   /**
    * Defaults to one day.
    */
   public long getDefaultResponseTimeout() {
      return Constants.DAY_IN_MILLIS;
   }
  
   /**
    * Defaults to one day.
    */
   public long getDefaultUpdateResponseTimeout() {
      return Constants.DAY_IN_MILLIS;
   }
  
   /**
    * Which parser to use.
    * The 'email' protocol uses as a default setting the XbfParser
    * but usig the XmlScriptParser may be convenient as well.
    * <p />
    * The environment setting 'parserClass=' is checked.
    * @return The class name of the parser, "org.xmlBlaster.util.xbformat.XbfParser"
    */
   public String getMsgInfoParserClassName() {
      if (this.msgInfoParserClassName == null) {
         this.msgInfoParserClassName = this.addressConfig.getEnv("parserClass", XbfParser.class.getName()).getValue();
      }
      return this.msgInfoParserClassName; //XbfParser.class.getName();
   }

   public Object sendEmail(String qos, MethodName methodName,
         boolean expectingResponse) throws XmlBlasterException {
      MsgUnitRaw[] msgArr = { new MsgUnitRaw(null, (byte[])null, qos) };
      return sendEmail(msgArr, methodName,
            expectingResponse);
   }

   public Object sendEmail(String key, String qos, MethodName methodName,
         boolean expectingResponse) throws XmlBlasterException {
      MsgUnitRaw[] msgArr = { new MsgUnitRaw(key, (byte[])null, qos) };
      return sendEmail(msgArr, methodName,
            expectingResponse);
   }

   public Object sendEmail(MsgUnitRaw msgUnit, MethodName methodName,
         boolean expectingResponse) throws XmlBlasterException {
      MsgUnitRaw[] msgArr = { msgUnit };
      return sendEmail(msgArr, methodName, expectingResponse);
   }

   /**
    * This sends the update to the client.
    *
    * @param methodName
    *           MethodName.UPDATE and others
    * @param withResponse
    *           one of SocketExecutor.WAIT_ON_RESPONSE or SocketExecutor.ONEWAY
    */
   public Object sendEmail(MsgUnitRaw[] msgArr, MethodName methodName,
         boolean expectingResponse) throws XmlBlasterException {
      if (msgArr == null || msgArr.length < 1)
         throw new XmlBlasterException(glob,
               ErrorCode.INTERNAL_ILLEGALARGUMENT, ME, "Illegal sendEmail("
                     + methodName.toString() + ") argument");
      if (log.isLoggable(Level.FINE))
         log.fine(methodName.toString() + "(" + msgArr.length + ") with emailSessionId="
               + getEmailSessionId());

      String requestId = null;
      try {
         // We use the RequestReplyExecutor.java for the request/reply pattern

         MsgInfo msgInfo = new MsgInfo(this.glob, MsgInfo.INVOKE_BYTE, methodName,
               getSecretSessionId(), super.getProgressListener());
         msgInfo.addMessage(msgArr);
         requestId = msgInfo.createRequestId(null);

         if (expectingResponse) { // register at the POP3 poller
            getPop3Driver().registerForEmail(getEmailSessionId(), requestId, this);
         }

         // super calls our sendMessage() which effectively sends the message
         Object response = super.requestAndBlockForReply(msgInfo,
               expectingResponse, false);
         return response;
      } catch (XmlBlasterException e) {
         // ErrorCode.USER* errors can't arrive here
         throw e;
      } catch (Throwable e) {
         e.printStackTrace();
         throw new XmlBlasterException(glob,
               ErrorCode.COMMUNICATION_NOCONNECTION, ME,
               "Sorry, email sending " + methodName.toString()
                     + " failed, no mail sent to "
                     + addressBase.getRawAddress(), e);
      } finally {
         if (expectingResponse)
            getPop3Driver().deregisterForEmail(getEmailSessionId(), requestId);
      }
   }

   /**
    * Notification by Pop3Driver when a (response) email message arrives.
    * Enforced by I_ResponseListener
    */
   public void incomingMessage(String requestId, Object response) {
      EmailData emailData = (EmailData) response;

      AttachmentHolder msgUnitAttachmentHolder = null;
      String pop3Url = null;
      try {
         pop3Url = getPop3Driver().getPop3Url(); // for logging only
         msgUnitAttachmentHolder =
            emailData.getMsgUnitAttachment(); // "*.xbf", "*.xbfz", "*.xmlz", ...
      } catch (Throwable e) {
         log.warning("Error parsing email data from "
               + pop3Url
               + ", please check the format: "
               + e.toString());
         return;
      }

      if (msgUnitAttachmentHolder == null) {
         log.warning("Got email from POP3 but there was no MsgUnit attachment, we ignore it: " + emailData.toXml(true));
         return;
      }

      byte[] encodedMsgUnit = msgUnitAttachmentHolder.getContent();
      MsgInfo[] msgInfos = null;
      try {
         if (MsgInfo.isCompressed(msgUnitAttachmentHolder.getFileName(), msgUnitAttachmentHolder.getContentType())) {
            // Decompress the bytes
            int length = encodedMsgUnit.length;
            this.decompressor.reset();
            this.decompressor.setInput(encodedMsgUnit, 0, length);
            byte[] buf = new byte[2048+length];
            ByteArrayOutputStream out = new ByteArrayOutputStream(2048+length);
            while (!this.decompressor.finished()) {
               int resultLength = this.decompressor.inflate(buf);
               if (resultLength > 0)
                  out.write(buf, 0, resultLength);
            }
            encodedMsgUnit = out.toByteArray();
            if (log.isLoggable(Level.FINE)) log.fine("Decompressed message from " + length + " to " + encodedMsgUnit.length + " bytes");
         }
         String parserClassName = MsgInfoParserFactory.instance().guessParserName(msgUnitAttachmentHolder.getFileName(), msgUnitAttachmentHolder.getContentType());
         msgInfos = MsgInfo.parse(glob, this.progressListener, encodedMsgUnit, parserClassName, this.pluginConfig);
         if (msgInfos.length < 1) {
            // spam?
            log.warning("Unexpected msgInfo with length==0, requestId=" + requestId + " data=" + emailData.toXml(true));
            Thread.dumpStack();
            return;
         }
         for (int j=0; j<msgInfos.length; j++) {
            MsgInfo msgInfo = msgInfos[j];
            msgInfo.setRequestIdGuessed(emailData.isRequestIdFromSentDate());
            msgInfo.setBounceObject(BOUNCE_MAILFROM_KEY, emailData.getFrom());
            // The messageId could be in the subject and not in the attachment
            msgInfo.setBounceObject(BOUNCE_MESSAGEID_KEY, emailData.getMessageId(messageIdFileName));
            AttachmentHolder[] attachments = emailData.getAttachments();
            for (int i=0; i<attachments.length; i++) {
               AttachmentHolder a = attachments[i];
               if (a == msgUnitAttachmentHolder)
                  continue;
               // TODO: Determine which attachments to bounce
               msgInfo.setBounceObject(a.getFileName(), a);
            }
         }

      } catch (Throwable e) {
         log.warning("Error parsing email data from "
                           + pop3Url
                           + ", check if client and server have identical compression settings: "
                           + e.toString() + ": " + emailData.toXml(true));
         return;
      }

      // Response and Exception messages should NEVER expire
      if (emailData.isExpired(messageIdFileName) && msgInfos[0].isInvoke()) {
         log.warning("Message is expired, we discard it: " + emailData.toString());
         return;
      }

      // For XmlScript && INVOKE we could have multiple message bundled
      // else length is always 1!
      for (int i=0; i<msgInfos.length; i++) {
         MsgInfo msgInfo = msgInfos[i];
         // If counterside has stripped information we add it again from the messageId attachment
         if (msgInfo.getRequestId().length() == 0)
            msgInfo.setRequestId(emailData.getRequestId(messageIdFileName));
         if (msgInfo.getSecretSessionId().length() == 0)
            msgInfo.setSecretSessionId(emailData.getSessionId(messageIdFileName));
  
         try {
            if (i==0 && msgInfo.isInvoke()) {
               if (isLoopingMail(msgInfo, emailData))
                  return;
            }
  
            /*
              Memory leak cleanup is handled by EmailDriver.java on LogoutEvent
              if (MethodName.DISCONNECT.equals(msgInfo.getMethodName())) {
                removeFromLoopProtection(emailData.getFrom());
              }
            */
           
            // This wakes up the blocking thread of sendEmail() and returns the
            // returnQos or the received invocation
            // On server side it typically invokes the core connect() or publish() etc.
            if (receiveReply(msgInfo, false) == false) {
               log.warning("Error parsing email data from "
                     + getPop3Driver().getPop3Url()
                     + ", CONNECT etc is not yet implemented");
            }
           
            if (i==0 && msgInfos.length > 1 && MethodName.CONNECT.equals(msgInfo.getMethodName())) {
               // If multiple requests where bundled pass the others the secret session id
               for (int k=1; k<msgInfos.length; k++)
                  msgInfos[k].setSecretSessionId(msgInfo.getSecretSessionId());
            }
         } catch (Throwable e) {
            if (log.isLoggable(Level.FINE))
               e.printStackTrace();
            log.warning("Can't process email data from "
                  + pop3Url + ": " + e.toString());
            return;
         }
      }
   }
  
   /**
    * Some weak looping protection.
    * Assume requestId to be strictly increasing
    * to detect email duplicates (which can be produced by MTAs)
    */
   protected boolean isLoopingMail(MsgInfo msgInfo, EmailData emailData) {
      try {
         LoopProtection loopProtection = null;
         synchronized (this.senderLoopProtectionMap) {
            loopProtection = (LoopProtection)this.senderLoopProtectionMap.get(emailData.getFrom());
            if (loopProtection == null) {
               loopProtection = new LoopProtection(emailData.getFrom(), -1, -1);
               this.senderLoopProtectionMap.put(loopProtection.key, loopProtection);
            }
         }
         // long currRequestId = new Long(msgInfo.getRequestId()).longValue();
         long currRequestId = msgInfo.getRequestSequence();
        
         if (MethodName.PING.equals(msgInfo.getMethodName())) {
            if (loopProtection.lastPingRequestId >= 0 && currRequestId <= loopProtection.lastPingRequestId) {
               log.warning("Can't process email data from "
                     + getPop3Driver().getPop3Url()
                     + ", it seems to be looping as requestId="+currRequestId+" (last="+loopProtection.lastPingRequestId+") has been processed already"
                     + ": " + emailData.toXml(true));
               return true;
            }
            loopProtection.lastPingRequestId = currRequestId;
         }
         else {
            if (loopProtection.lastRequestId >= 0 && currRequestId <= loopProtection.lastRequestId) {
               log.warning("Can't process email data from "
                     + getPop3Driver().getPop3Url()
                     + ", it seems to be looping as requestId="+currRequestId+" (last="+loopProtection.lastRequestId+") has been processed already"
                     + ": " + emailData.toXml(true));
               return true;
            }
            loopProtection.lastRequestId = currRequestId;
         }
      }
      catch (Throwable e) {
         log.warning("Cant handle requestId '"+msgInfo.getRequestId()+"' to be of type long");
      }
      return false;
   }

   /**
    * Cleanup
    * @param key this is the sender email address, for example "xmlBlaster@localhost" for a client
    *         or "demo@localhost" on server side (emailData.getFrom())
    * @return The removed entry or null if not found
    */
   protected LoopProtection removeFromLoopProtection(String key) {
      LoopProtection loopProtection = null;
      synchronized (this.senderLoopProtectionMap) {
         loopProtection = (LoopProtection)this.senderLoopProtectionMap.remove(key);
         if (loopProtection == null) {
            // Is OK for email plugins which only invokes
            log.fine("No loopProtection entry found for sender " + key
                  + " which is OK in most cases, current known are: "
                  + getLoopProtectionList());
         }
      }
      return loopProtection;
   }

   /**
    * Returns a comma seperated list of all 'from email addresses'.
    * Used for JMX.
    * @return For example "joe@localhost,blue@localhost", never null
    */
   public String getLoopProtectionList() {
      LoopProtection[] arr = getLoopProtections();
      StringBuffer sb = new StringBuffer(512);
      for (int i=0; i<arr.length; i++) {
         if (i>0) sb.append(",");
         sb.append(arr[i].key);
      }
      return sb.toString();
   }
  
   protected LoopProtection[] getLoopProtections() {
      synchronized (this.senderLoopProtectionMap) {
         return (LoopProtection[])this.senderLoopProtectionMap.values().toArray(new LoopProtection[this.senderLoopProtectionMap.size()]);
      }
   }

   /**
    * @return messageId="<messageId><sessionId>sessionId:4423c443</sessionId><requestId>3</requestId><methodName>subscribe</methodName></messageId>"
    */
   protected String createMessageId(MsgInfo msgInfo, String requestId, MethodName methodName, Timestamp expiryTimestamp) {
      String messageId = (String)msgInfo.getBounceObject(BOUNCE_MESSAGEID_KEY);
      if (messageId == null) {
         String sessionId = getEmailSessionId(msgInfo);
         {
            // Hardcoded simplification of email subject for messages send manually from a normal email client
            // like this the GUI user can simply push the reply button to send further publish() etc.
            // RE: <messageId><sessionId>sessionId:127.0.0.2-null-1134497522115--102159664-3</sessionId></messageId>
            //if (methodName.equals(MethodName.CONNECT)) {
               // We send the secretSessionId in the SUBJECT of a ConnectReturnQos
               //sessionId = msgInfo.getSecretSessionId();
            //}
  
            if (!msgInfo.isInvoke()) // Responses and exceptions should never expire
               expiryTimestamp = null;
           
            if (msgInfo.isRequestIdGuessed()) {
               requestId = null;
               methodName = null;
            }
         }
        
         messageId = EmailData.createMessageId(sessionId,
            requestId, methodName, expiryTimestamp);
      }
      return messageId;
   }
  
   /**
    * Extends RequestReplyExecutor.sendMessage
    */
   protected void sendMessage(MsgInfo msgInfo, String requestId,
         MethodName methodName, boolean udp) throws XmlBlasterException,
         IOException {

      String subject = this.subjectTemplate;
     
      Timestamp expiryTimestamp = getExpiryTimestamp(methodName);
      String messageId = createMessageId(msgInfo, requestId, methodName, expiryTimestamp);

      if (subject != null && subject.length() > 0) {
         // Transport messageId in subject if token "${xmlBlaster/email/messageId}" is present:
         // The responseQos gets it already replaced and the messageId remains same (which we must assure)
         subject = ReplaceVariable.replaceFirst(subject, SUBJECT_MESSAGEID_TOKEN, messageId);
         // and for testing as attachment, for example this.messageIdFileName="messageId.mid"
      }

      // Serialize the pay load
      byte[] origAttachment = msgInfo.getMsgInfoParser(getMsgInfoParserClassName(), pluginConfig).createRawMsg(msgInfo);
      boolean isCompressed = false;
      byte[] payload = origAttachment;
      if (isCompressZlib()) {
         if (origAttachment.length > getMinSizeForCompression()) { // > 0
            this.compressor.reset();
            this.compressor.setInput(origAttachment, 0, origAttachment.length);
            this.compressor.finish();
            payload = new byte[origAttachment.length+64];
            int compSize = compressor.deflate(payload);
            if (compSize <= 0) {
               throw new IOException("Compression exception, got 0 bytes output: " + MsgInfo.toLiteral(msgInfo.createRawMsg(getMsgInfoParserClassName())));
            }
            if (compSize >= origAttachment.length) { // Compression didn't help
               payload = origAttachment; 
               if (log.isLoggable(Level.FINE)) log.fine("No compression of attachment as size increased from " + origAttachment.length + " to " + compSize + ", we leave it uncompressed");
            }
            else {
               isCompressed = true;
               byte[] tmp = new byte[compSize];
               System.arraycopy(payload, 0, tmp, 0, compSize);
               payload = tmp;
               if (log.isLoggable(Level.FINE)) log.fine("Compressing attachment of size " + origAttachment.length + " to length " + payload.length);
            }
         }
         else {
            if (log.isLoggable(Level.FINE)) log.fine("Compressing of attachment skipped as size of " + origAttachment.length + " is smaller minSize " + getMinSizeForCompression());
         }
      }
     
      // The real message blob, for example "xmlBlasterMessage.xbf"
      String payloadFileName = this.payloadFileNamePrefix +
         msgInfo.getMsgInfoParser(getMsgInfoParserClassName(), pluginConfig).getExtension(isCompressed);

      InternetAddress toAddr = this.toAddress;
      String to = (String)msgInfo.getBounceObject(BOUNCE_MAILTO_KEY);
      if (to != null) {
         try { // The EmailDriver has different destinations for each client
            toAddr = new InternetAddress(to);
         } catch (AddressException e) {
            log.warning("Illegal 'to' address '" + to + "'");
         }
      }
      if (toAddr == null) {
         Thread.dumpStack();
         throw new IllegalArgumentException("No 'toAddress' email address is given, can't send mail: " + MsgInfo.toLiteral(msgInfo.createRawMsg(getMsgInfoParserClassName())));
      }

      EmailData emailData = new EmailData(toAddr, this.fromAddress, subject);
      emailData.setCc(this.cc);
      emailData.setBcc(this.bcc);
      emailData.setExpiryTime(expiryTimestamp);
      String payloadMimetype = msgInfo.getMsgInfoParser(getMsgInfoParserClassName(), pluginConfig).getMimetype(isCompressed);
      emailData.addAttachment(new AttachmentHolder(payloadFileName, payloadMimetype, payload));
      emailData.addAttachment(new AttachmentHolder(this.messageIdFileName, messageId));
      // Bounce all other attachments back to sender
      AttachmentHolder[] attachments = msgInfo.getBounceAttachments();
      for (int i=0; i<attachments.length; i++) {
         AttachmentHolder a = attachments[i];
         if (this.messageIdFileName.equals(a.getFileName()))
            continue; // added alread, see above
         emailData.addAttachment(a);
      }
     
      getSmtpClient().sendEmail(emailData, messageIdFileName);
      //this.smtpClient.sendEmail(this.fromAddress, toAddr, subject,
      //      attachmentName, attachment, attachmentName2, messageId,
      //      Constants.UTF8_ENCODING);

      if (log.isLoggable(Level.FINE)) log.fine("Sending email from="
            + this.fromAddress.toString() + " to=" + toAddr.toString()
            + " messageId=" + messageId
            + " done");
      if (log.isLoggable(Level.FINEST)) log.finest("MsgInfo dump: " + MsgInfo.toLiteral(msgInfo.createRawMsg(getMsgInfoParserClassName())));
   }

   /**
    * The oneway variant, without return value.
    *
    * @exception XmlBlasterException
    *               Is never from the client (oneway).
    */
   public void sendUpdateOneway(MsgUnitRaw[] msgArr) throws XmlBlasterException {
      log.warning("Email sendUpdateOneway is not supported, request ignored");
   }

   /**
    * Ping to check if callback server is alive. This ping checks the
    * availability on the application level.
    *
    * @param qos
    *           Currently an empty string ""
    * @return Currently an empty string ""
    * @exception XmlBlasterException
    *               If client not reachable
    */
   public String ping(String qos) throws XmlBlasterException {
      log.warning("Email ping is not supported, request ignored");
      return "";
   }

   /**
    * This method shuts down the driver.
    * <p />
    */
   public void shutdown() {
      this.isShutdown = true;
      synchronized (this.senderLoopProtectionMap) {
         this.senderLoopProtectionMap.clear();
      }
      try {
         getPop3Driver().deregisterForEmail(this);
      } catch (XmlBlasterException e) {
         e.printStackTrace();
      }
   }
  
   /** I_AdminPlugin#isShutodwn */
   public boolean isShutdown() {
      return this.isShutdown;
   }

   /**
    * @return true if the plugin is still alive, false otherwise
    */
   public boolean isAlive() {
      return true;
   }

   /**
    * @return Returns the secretSessionId.
    */
   public String getSecretSessionId() {
      return this.secretSessionId;
   }

   /**
    * @param secretSessionId
    *           The secretSessionId to set.
    */
   public void setSecretSessionId(String secretSessionId) {
      this.secretSessionId = secretSessionId;
   }

   /**
    * The sessionId used to register at Pop3Driver and send in subject: of email
    * @return Usually the clients relative login sessionName "client/joe/3", never null
    */
   public String getEmailSessionId() {
      return (this.emailSessionId == null) ? "" : this.emailSessionId;
   }

   /**
    * Is overwritten for example by EmailDriver.java singleton.
    * @param msgInfo
    * @return
    */
   public String getEmailSessionId(MsgInfo msgInfo) {
      return getEmailSessionId();
   }

   public void setEmailSessionId(String emailSessionId) {
      this.emailSessionId = emailSessionId;
   }

   /**
    * Email protocol contract with server side CallbackEmailDriver.java and client side EmailCallbackImpl.java
    * We use <messageId><sessionId>joe/2</sessionId>...
    * to find our response again. If no positive sessionId
    * is given, the server generates e.g. -7 for us which we don't know
    * In this case we use <messageId><sessionId>joe</sessionId>... only
    */
   protected void setEmailSessionId(SessionName sessionName) {
      if (sessionName == null) {
         log.severe("setEmailSessionId() is called with null sessionName");
         return;
      }
      if (sessionName.isPubSessionIdUser())
         setEmailSessionId(sessionName.getRelativeName());
      else {
         log.warning("The current email callback implementation can handle max one connection per email account (like 'joe' on the POP3 server) if you don't supply a positive sessionId");
         setEmailSessionId(sessionName.getLoginName());
      }
   }

   /**
    * @return Returns the cc.
    */
   public String getCc() {
      return this.cc;
   }

   /**
    * Send all emails additionally to the given CC addresses.
    * You can pass a comma separated list of addresses.
    * @param cc The copy addresses, for example "joe@localhost,jack@localhost"
    */
   public void setCc(String cc) {
      this.cc = cc;
   }

   /** JMX */
   public void setTo(String to) throws IllegalArgumentException {
      try {
         this.toAddress = new InternetAddress(to);
      } catch (AddressException e) {
         throw new IllegalArgumentException(
               ME + " Illegal 'to' address '" + to + "'");
      }
   }

   /**
    * @return Returns the 'to' email address.
    */
   public String getTo() {
      return (this.toAddress == null) ? "" : this.toAddress.toString();
   }

   /** JMX */
   public void setFrom(String from) throws IllegalArgumentException {
      try {
         this.fromAddress = new InternetAddress(from);
      } catch (AddressException e) {
         throw new IllegalArgumentException(
               ME + " Illegal 'from' address '" + from + "'");
      }
   }

   /**
    * @return Returns the 'from' email address.
    */
   public String getFrom() {
      return (this.fromAddress == null) ? "" : this.fromAddress.toString();
   }

   /**
    * @return Returns the blind copy email addresses or null
    */
   public String getBcc() {
      return this.bcc;
   }

   /**
    * Send all emails additionally to the given bcc.
    * You can pass a comma separated list of addresses.
    * @param bcc The blind copy addresses, for example "joe@localhost,jack@localhost"
    */
   public void setBcc(String bcc) {
      this.bcc = bcc;
   }
  
   /**
    * @return a human readable usage help string
    */
   public java.lang.String usage() {
      return super.usage() + "\n" + 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) {
   }
}
TOP

Related Classes of org.xmlBlaster.util.protocol.email.EmailExecutor$LoopProtection

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.