/**
     * Handel multipart messages recursive until we find the first text/html message. Or text/plain
     * if html is not available.
     *
     * @param multipart the multipart portion
     * @param message the message
     * @param builder the email value builder
     * @throws MessagingException
     * @throws IOException
     */
    private void handleMultipart(
        Multipart multipart, Message message, ValueBuilder<EmailValue> builder)
        throws MessagingException, IOException {
      String body = "";

      String contentType = cleanContentType(multipart.getContentType());

      for (int i = 0, n = multipart.getCount(); i < n; i++) {
        BodyPart part = multipart.getBodyPart(i);

        String disposition = part.getDisposition();

        if ((disposition != null)
            && ((disposition.equalsIgnoreCase(Part.ATTACHMENT)
                || (disposition.equalsIgnoreCase(Part.INLINE))))) {
          builder
              .prototype()
              .attachments()
              .get()
              .add(createAttachedFileValue(message.getSentDate(), part));

        } else {
          if (part.isMimeType(Translator.PLAIN)) {
            // if contents is multipart mixed concatenate text plain parts
            if ("multipart/mixed".equalsIgnoreCase(contentType)) {
              body += (String) part.getContent() + "\n\r";
            } else {
              body = (String) part.getContent();
            }
            builder.prototype().content().set(body);
            builder.prototype().contentType().set(Translator.PLAIN);

          } else if (part.isMimeType(Translator.HTML)) {
            body = (String) part.getContent();
            builder.prototype().contentHtml().set(body);
            builder.prototype().contentType().set(Translator.HTML);

          } else if (part.isMimeType("image/*")) {
            builder
                .prototype()
                .attachments()
                .get()
                .add(createAttachedFileValue(message.getSentDate(), part));

          } else if (part.getContent() instanceof Multipart) {
            handleMultipart((Multipart) part.getContent(), message, builder);
          }
        }
      }
      // if contentHtml is not empty set the content type to text/html
      if (!Strings.empty(builder.prototype().contentHtml().get())) {
        builder.prototype().content().set(builder.prototype().contentHtml().get());
        builder.prototype().contentType().set(Translator.HTML);
      }
    }
    /**
     * Try to determine what address from the to address list should be used as main TO address for
     * the different EmailReceiver's. In case the references header is null we try to find a
     * matching email access point.
     *
     * @param recipients The recipients from the TO address list
     * @param references The references header
     * @return A hopefully valid email address as a string
     */
    private String toaddress(final Address[] recipients, String references) {
      String result = "";
      // No recipients found return n/a
      if (recipients == null || recipients.length == 0) return "n/a";

      if (!hasStreamflowReference(references)) {
        Organizations.Data organizations =
            module
                .unitOfWorkFactory()
                .currentUnitOfWork()
                .get(Organizations.Data.class, OrganizationsEntity.ORGANIZATIONS_ID);
        EmailAccessPoints.Data emailAccessPoints =
            (EmailAccessPoints.Data) organizations.organization().get();

        Iterable<EmailAccessPoint> possibleAccesspoints =
            Iterables.filter(
                new Specification<EmailAccessPoint>() {
                  public boolean satisfiedBy(final EmailAccessPoint accessPoint) {
                    return Iterables.matchesAny(
                        new Specification<Address>() {
                          public boolean satisfiedBy(Address address) {
                            return ((InternetAddress) address)
                                .getAddress()
                                .equalsIgnoreCase(accessPoint.getDescription());
                          }
                        },
                        Arrays.asList(recipients));
                  }
                },
                emailAccessPoints.emailAccessPoints().toList());

        if (Iterables.count(possibleAccesspoints) > 0)
          result = Iterables.first(possibleAccesspoints).getDescription();
        else result = ((InternetAddress) recipients[0]).getAddress();

      } else {
        result = ((InternetAddress) recipients[0]).getAddress();
      }

      return Strings.empty(result) ? "n/a" : result.toLowerCase();
    }
    public void run() {
      Thread.currentThread().setContextClassLoader(getClass().getClassLoader());

      if (!circuitBreaker.isOn()) return; // Don't try - circuit breaker is off

      boolean expunge = config.configuration().deleteMailOnInboxClose().get();
      if (config.configuration().debug().get()) {
        logger.info("Checking email");
        logger.info("Delete mail on close - " + expunge);
      }

      Session session = javax.mail.Session.getInstance(props, authenticator);
      session.setDebug(config.configuration().debug().get());

      Usecase usecase = newUsecase("Receive Mail");
      UnitOfWork uow = null;
      Store store = null;
      Folder inbox = null;
      Folder archive = null;
      boolean archiveExists = false;
      List<Message> copyToArchive = new ArrayList<Message>();
      MimeMessage internalMessage = null;

      try {
        store = session.getStore(url);
        store.connect();

        inbox = store.getFolder("INBOX");
        inbox.open(Folder.READ_WRITE);

        javax.mail.Message[] messages = inbox.getMessages();
        FetchProfile fp = new FetchProfile();
        //            fp.add( "In-Reply-To" );
        inbox.fetch(messages, fp);

        // check if the archive folder is configured and exists
        if (!Strings.empty(config.configuration().archiveFolder().get())
            && config.configuration().protocol().get().startsWith("imap")) {
          archive = store.getFolder(config.configuration().archiveFolder().get());

          // if not exists - create
          if (!archive.exists()) {
            archive.create(Folder.HOLDS_MESSAGES);
            archiveExists = true;
          } else {
            archiveExists = true;
          }

          archive.open(Folder.READ_WRITE);
        }

        for (javax.mail.Message message : messages) {
          int tries = 0;
          while (tries < 3) {
            uow = module.unitOfWorkFactory().newUnitOfWork(usecase);

            ValueBuilder<EmailValue> builder =
                module.valueBuilderFactory().newValueBuilder(EmailValue.class);

            try {
              // Force a complete fetch of the message by cloning it to a internal MimeMessage
              // to avoid "javax.mail.MessagingException: Unable to load BODYSTRUCTURE" problems
              // f.ex. experienced if the message contains a windows .eml file as attachment!

              // Beware that all flag and folder operations have to be made on the original message
              // and not on the internal one!!
              internalMessage = new MimeMessage((MimeMessage) message);

              Object content = internalMessage.getContent();

              // Get email fields
              builder
                  .prototype()
                  .from()
                  .set(((InternetAddress) internalMessage.getFrom()[0]).getAddress());
              builder
                  .prototype()
                  .fromName()
                  .set(((InternetAddress) internalMessage.getFrom()[0]).getPersonal());
              builder
                  .prototype()
                  .subject()
                  .set(internalMessage.getSubject() == null ? "" : internalMessage.getSubject());

              // Get headers
              for (Header header :
                  Iterables.iterable((Enumeration<Header>) internalMessage.getAllHeaders())) {
                builder.prototype().headers().get().put(header.getName(), header.getValue());
              }

              // Get all recipients in order - TO, CC, BCC
              // and provide it to the toaddress method to pick the first possible valid adress
              builder
                  .prototype()
                  .to()
                  .set(
                      toaddress(
                          internalMessage.getAllRecipients(),
                          builder.prototype().headers().get().get("References")));

              builder.prototype().messageId().set(internalMessage.getHeader("Message-ID")[0]);

              // Get body and attachments
              String body = "";
              // set content initially so it never can become null
              builder.prototype().content().set(body);

              if (content instanceof String) {
                body = content.toString();
                builder.prototype().content().set(body);
                String contentTypeString = cleanContentType(internalMessage.getContentType());
                builder.prototype().contentType().set(contentTypeString);
                if (Translator.HTML.equalsIgnoreCase(contentTypeString)) {
                  builder.prototype().contentHtml().set(body);
                }

              } else if (content instanceof Multipart) {
                handleMultipart((Multipart) content, internalMessage, builder);
              } else if (content instanceof InputStream) {
                content = new MimeMessage(session, (InputStream) content).getContent();
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                Inputs.byteBuffer((InputStream) content, 4096).transferTo(Outputs.byteBuffer(baos));

                String data = new String(baos.toByteArray(), "UTF-8");
                // Unknown content type - abort
                // and create failure case
                String subj = "Unkonwn content type: " + internalMessage.getSubject();
                builder
                    .prototype()
                    .subject()
                    .set(subj.length() > 50 ? subj.substring(0, 50) : subj);
                builder.prototype().content().set(body);
                builder.prototype().contentType().set(internalMessage.getContentType());
                systemDefaults.createCaseOnEmailFailure(builder.newInstance());
                copyToArchive.add(message);
                if (expunge) message.setFlag(Flags.Flag.DELETED, true);

                uow.discard();
                tries = 3;
                continue;
              } else {
                // Unknown content type - abort
                // and create failure case
                String subj = "Unkonwn content type: " + internalMessage.getSubject();
                builder
                    .prototype()
                    .subject()
                    .set(subj.length() > 50 ? subj.substring(0, 50) : subj);
                builder.prototype().content().set(body);
                builder.prototype().contentType().set(internalMessage.getContentType());
                systemDefaults.createCaseOnEmailFailure(builder.newInstance());
                copyToArchive.add(message);
                if (expunge) message.setFlag(Flags.Flag.DELETED, true);

                uow.discard();
                logger.error(
                    "Could not parse emails: unknown content type " + content.getClass().getName());
                tries = 3;
                continue;
              }

              // make sure mail content fit's into statistic database - truncate on 65.500
              // characters.
              if (builder.prototype().content().get().length() > 65000) {
                builder
                    .prototype()
                    .content()
                    .set(builder.prototype().content().get().substring(0, 65000));
              }

              // try to reveal if it is a smpt error we are looking at
              // X-Failed-Recipients is returned by Gmail
              // X-FC-MachineGenerated is returned by FirstClass
              // Exchange is following RFC 6522 -  The Multipart/Report Media Type for
              // the Reporting of Mail System Administrative Messages
              boolean isSmtpErrorReport =
                  !Strings.empty(builder.prototype().headers().get().get("X-Failed-Recipients"))
                      || (!Strings.empty(
                              builder.prototype().headers().get().get("X-FC-MachineGenerated"))
                          && "true"
                              .equals(
                                  builder.prototype().headers().get().get("X-FC-MachineGenerated")))
                      || !Strings.empty(
                          new ContentType(builder.prototype().headers().get().get("Content-Type"))
                              .getParameter("report-type"));

              if (isSmtpErrorReport) {
                // This is a mail bounce due to SMTP error - create support case.
                String subj = "Undeliverable mail: " + builder.prototype().subject().get();

                builder
                    .prototype()
                    .subject()
                    .set(subj.length() > 50 ? subj.substring(0, 50) : subj);
                systemDefaults.createCaseOnEmailFailure(builder.newInstance());
                copyToArchive.add(message);
                if (expunge) message.setFlag(Flags.Flag.DELETED, true);

                uow.discard();
                logger.error("Received a mail bounce reply: " + body);
                tries = 3;
                continue;
              }

              if (builder.prototype().to().get().equals("n/a")) {
                // This is a mail has no to address - create support case.

                String subj = "No TO address: " + builder.prototype().subject().get();

                builder
                    .prototype()
                    .subject()
                    .set(subj.length() > 50 ? subj.substring(0, 50) : subj);
                systemDefaults.createCaseOnEmailFailure(builder.newInstance());
                copyToArchive.add(message);
                if (expunge) message.setFlag(Flags.Flag.DELETED, true);

                uow.discard();
                logger.error("Received a mail without TO address: " + body);
                tries = 3;
                continue;
              }

              mailReceiver.receivedEmail(null, builder.newInstance());

              try {
                logger.debug("This is try " + tries);
                uow.complete();
                tries = 3;
              } catch (ConcurrentEntityModificationException ceme) {
                if (tries < 2) {
                  logger.debug("Encountered ConcurrentEntityModificationException - try again ");
                  // discard uow and try again
                  uow.discard();
                  tries++;
                  continue;

                } else {
                  logger.debug("Rethrowing ConcurrentEntityModification.Exception");
                  tries++;
                  throw ceme;
                }
              }

              copyToArchive.add(message);
              // remove mail on success if expunge is true
              if (expunge) message.setFlag(Flags.Flag.DELETED, true);

            } catch (Throwable e) {
              String subj = "Unknown error: " + internalMessage.getSubject();
              builder.prototype().subject().set(subj.length() > 50 ? subj.substring(0, 50) : subj);

              StringBuilder content = new StringBuilder();
              content.append("Error Message: " + e.getMessage());
              content.append("\n\rStackTrace:\n\r");
              for (StackTraceElement trace : Arrays.asList(e.getStackTrace())) {
                content.append(trace.toString() + "\n\r");
              }

              builder.prototype().content().set(content.toString());
              // since we create the content of the message our self it's ok to set content type
              // always to text/plain
              builder.prototype().contentType().set("text/plain");

              // Make sure to address has some value before vi create a case!!
              if (builder.prototype().to().get() == null) {
                builder.prototype().to().set("n/a");
              }
              systemDefaults.createCaseOnEmailFailure(builder.newInstance());
              copyToArchive.add(message);
              if (expunge) message.setFlag(Flags.Flag.DELETED, true);

              uow.discard();
              logger.error("Could not parse emails", e);
              tries = 3;
            }
          }
        }

        // copy message to archive if archive exists
        if (archiveExists) {
          inbox.copyMessages(copyToArchive.toArray(new Message[0]), archive);
          archive.close(false);
        }

        inbox.close(config.configuration().deleteMailOnInboxClose().get());

        store.close();

        if (config.configuration().debug().get()) {
          logger.info("Checked email");
        }

        circuitBreaker.success();
      } catch (Throwable e) {
        logger.error("Error in mail receiver: ", e);
        circuitBreaker.throwable(e);

        try {
          if (inbox != null && inbox.isOpen()) inbox.close(false);

          if (store != null && store.isConnected()) store.close();
        } catch (Throwable e1) {
          logger.error("Could not close inbox", e1);
        }
      }
    }
      public void receivedEmail(ApplicationEvent event, EmailValue email) {
        UnitOfWork uow =
            module
                .unitOfWorkFactory()
                .newUnitOfWork(UsecaseBuilder.newUsecase("Receive email in conversation"));

        try {
          String references = email.headers().get().get("References");

          if (hasStreamflowReference(references)) {
            // This is a response - handle it!

            List<String> refs =
                (List<String>)
                    Iterables.addAll(
                        (Collection<String>) new ArrayList<String>(),
                        Iterables.iterable(references.split("[ \r\n\t]")));

            // Hotmail handles refs a bit differently...
            String hotmailRefs =
                Iterables.first(
                    Iterables.filter(
                        new Specification<String>() {
                          public boolean satisfiedBy(String item) {
                            return item.contains(",") && item.endsWith("@Streamflow>");
                          }
                        },
                        refs));

            String lastRef = null;
            if (!Strings.empty(hotmailRefs)) {
              lastRef = hotmailRefs.split(",")[1];
            } else {
              Collections.reverse(refs);
              Iterable<String> filter =
                  Iterables.filter(
                      new Specification<String>() {
                        public boolean satisfiedBy(String item) {
                          return item.endsWith("@Streamflow>");
                        }
                      },
                      refs);
              lastRef = Iterables.first(filter);
            }

            if (lastRef == null) {
              ValueBuilder<EmailValue> builder =
                  module
                      .valueBuilderFactory()
                      .newValueBuilder(EmailValue.class)
                      .withPrototype(email);
              String subj = "Msg Ref missing: " + builder.prototype().subject().get();
              builder.prototype().subject().set(subj.length() > 50 ? subj.substring(0, 50) : subj);

              systemDefaults.createCaseOnEmailFailure(builder.newInstance());
              logger.error("Could not find message reference in email header:" + lastRef);
              uow.discard();
              return;
            }

            Matcher matcher = Pattern.compile("<([^/]*)/([^@]*)@[^>]*>").matcher(lastRef);

            if (matcher.find()) {
              String conversationId = matcher.group(1);
              String participantId = URLDecoder.decode(matcher.group(2), "UTF-8");

              if (!"".equals(conversationId) && !"".equals(participantId)) {
                ConversationParticipant from =
                    uow.get(ConversationParticipant.class, participantId);
                Conversation conversation = uow.get(Conversation.class, conversationId);

                CaseEntity caze = (CaseEntity) conversation.conversationOwner().get();
                String content = email.content().get();

                // If we have an assignee, ensure it is a member of the conversation first
                if (caze.isAssigned()) {
                  if (!conversation.isParticipant(
                      (ConversationParticipant) caze.assignedTo().get()))
                    conversation.addParticipant((ConversationParticipant) caze.assignedTo().get());
                }

                // Create a new role map and fill it with relevant objects
                if (RoleMap.current() == null) RoleMap.newCurrentRoleMap();
                RoleMap.current().set(from, ConversationParticipant.class);
                RoleMap.current().set(caze, CaseLoggable.Data.class);
                RoleMap.current().set(caze, Case.class);

                Message message = null;

                if (Translator.HTML.equalsIgnoreCase(email.contentType().get())) {
                  message =
                      conversation.createMessage(email.contentHtml().get(), MessageType.HTML, from);
                } else {
                  message =
                      conversation.createMessage(email.content().get(), MessageType.PLAIN, from);
                }
                // Create attachments
                for (AttachedFileValue attachedFileValue : email.attachments().get()) {
                  if (!(attachedFileValue.mimeType().get().contains("text/x-vcard")
                      || attachedFileValue.mimeType().get().contains("text/directory"))) {
                    Attachment attachment = message.createAttachment(attachedFileValue.uri().get());
                    attachment.changeDescription("New Attachment");
                    attachment.changeName(attachedFileValue.name().get());
                    attachment.changeMimeType(attachedFileValue.mimeType().get());
                    attachment.changeModificationDate(attachedFileValue.modificationDate().get());
                    attachment.changeSize(attachedFileValue.size().get());
                    attachment.changeUri(attachedFileValue.uri().get());
                  }
                }

                try {
                  if (caze.isStatus(CaseStates.CLOSED)) {
                    RoleMap.newCurrentRoleMap();
                    RoleMap.current().set(caze);
                    if (caze.assignedTo().get() != null) {
                      RoleMap.current().set(caze.assignedTo().get());
                    } else {
                      RoleMap.current()
                          .set(uow.get(UserEntity.class, UserEntity.ADMINISTRATOR_USERNAME));
                    }
                    CaseCommandsContext caseCommands =
                        module.transientBuilderFactory().newTransient(CaseCommandsContext.class);
                    caseCommands.reopen();
                    caseCommands.unassign();
                    RoleMap.clearCurrentRoleMap();
                  }
                } catch (Throwable e) {
                  ValueBuilder<EmailValue> builder =
                      module
                          .valueBuilderFactory()
                          .newValueBuilder(EmailValue.class)
                          .withPrototype(email);
                  String subj = "Create Case failed: " + builder.prototype().subject().get();
                  builder
                      .prototype()
                      .subject()
                      .set(subj.length() > 50 ? subj.substring(0, 50) : subj);

                  systemDefaults.createCaseOnEmailFailure(builder.newInstance());
                  // throw new IllegalStateException("Could not open case through new message.", e);
                }
              }
            }
          }

          uow.complete();
        } catch (Exception ex) {
          ValueBuilder<EmailValue> builder =
              module.valueBuilderFactory().newValueBuilder(EmailValue.class).withPrototype(email);
          String subj = "Conversation Response Error: " + builder.prototype().subject().get();
          builder.prototype().subject().set(subj.length() > 50 ? subj.substring(0, 50) : subj);

          StringBuilder content = new StringBuilder();
          content.append("Error Message: " + ex.getMessage());
          content.append("\n\rStackTrace:\n\r");
          for (StackTraceElement trace : Arrays.asList(ex.getStackTrace())) {
            content.append(trace.toString() + "\n\r");
          }

          builder.prototype().content().set(content.toString());
          // since we create the content of the message our self it's ok to set content type always
          // to text/plain
          builder.prototype().contentType().set("text/plain");

          // Make sure to address has some value before vi create a case!!
          if (builder.prototype().to().get() == null) {
            builder.prototype().to().set("n/a");
          }

          systemDefaults.createCaseOnEmailFailure(builder.newInstance());
          uow.discard();
          // throw new ApplicationEventReplayException(event, ex);
        }
      }