IDEMPIERE-2244 RequestEMailProcess : integrate message in HTML

This commit is contained in:
hieplq 2015-05-13 10:33:06 -05:00
parent b43bedeba6
commit b1576bff8b
8 changed files with 1159 additions and 768 deletions

View File

@ -0,0 +1,35 @@
SET SQLBLANKLINES ON
SET DEFINE OFF
-- I forgot to set the DICTIONARY_ID_COMMENTS System Configurator
-- Mar 15, 2015 10:28:22 PM ICT
UPDATE AD_Process_Para SET IsCentrallyMaintained='N', IsEncrypted='Y',Updated=TO_DATE('2015-03-15 22:28:22','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Process_Para_ID=50008
;
-- Mar 15, 2015 10:36:26 PM ICT
INSERT INTO AD_Process_Para (AD_Process_Para_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,Name,Description,Help,AD_Process_ID,SeqNo,AD_Reference_ID,IsRange,FieldLength,IsMandatory,DefaultValue,ColumnName,IsCentrallyMaintained,EntityType,AD_Process_Para_UU,IsEncrypted) VALUES (200121,0,0,'Y',TO_DATE('2015-03-15 22:36:25','YYYY-MM-DD HH24:MI:SS'),100,TO_DATE('2015-03-15 22:36:25','YYYY-MM-DD HH24:MI:SS'),100,'Inbox As Root Folder','True if other folder (request, error,..) occupy in inbox folder','Some email server as outlook, every folder occupy in inbox, with web mail as google, we easy create my folder outside inbox folder.
this field define where request, error,.. occupy.',50012,65,20,'N',1,'N','Y','p_NestInbox','N','D','b20ee48e-dbb5-4da2-aadb-cdfe40e02124','N')
;
-- Mar 15, 2015 10:39:37 PM ICT
UPDATE AD_Process_Para SET Description='Mail folder, where process will take email and make request', Help='folder can define as hierarchy, use "\" to separate folder. example customer\vietnam
will read email from folder vietnam under folder customer', IsCentrallyMaintained='N',Updated=TO_DATE('2015-03-15 22:39:37','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Process_Para_ID=50009
;
-- Mar 15, 2015 10:42:54 PM ICT
UPDATE AD_Process_Para SET Description='Mail folder to push unsuccess email ', Help='folder can define as hierarchy, use "\" to separate folder. example customer\error\vietnam
will puss email can''t process to folder vietnam under folder error under folder customer', IsCentrallyMaintained='N',Updated=TO_DATE('2015-03-15 22:42:54','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Process_Para_ID=50011
;
-- Mar 15, 2015 10:46:55 PM ICT
UPDATE AD_Process_Para SET Description='Mail folder, where process will take email and make request', Help='folder can define as hierarchy, use "\" to separate folder. example customer\vietnam
will read email from folder vietnam under folder customer', IsCentrallyMaintained='N',Updated=TO_DATE('2015-03-15 22:46:55','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Process_Para_ID=50010
;
-- Mar 15, 2015 10:48:26 PM ICT
UPDATE AD_Process_Para SET Description='After take email form inbox folder, and process it success, email will move to this folder', Help='folder can define as hierarchy, use "\" to separate folder. example customer\vietnam
will read email from folder vietnam under folder customer
after take email form inbox folder, and process it success, email will move to this folder',Updated=TO_DATE('2015-03-15 22:48:26','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Process_Para_ID=50009
;
SELECT register_migration_script('201503151604-IDEMPIERE-2244.sql') FROM dual
;

View File

@ -0,0 +1,32 @@
-- I forgot to set the DICTIONARY_ID_COMMENTS System Configurator
-- Mar 15, 2015 10:28:22 PM ICT
UPDATE AD_Process_Para SET IsCentrallyMaintained='N', IsEncrypted='Y',Updated=TO_TIMESTAMP('2015-03-15 22:28:22','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Process_Para_ID=50008
;
-- Mar 15, 2015 10:36:26 PM ICT
INSERT INTO AD_Process_Para (AD_Process_Para_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,Name,Description,Help,AD_Process_ID,SeqNo,AD_Reference_ID,IsRange,FieldLength,IsMandatory,DefaultValue,ColumnName,IsCentrallyMaintained,EntityType,AD_Process_Para_UU,IsEncrypted) VALUES (200121,0,0,'Y',TO_TIMESTAMP('2015-03-15 22:36:25','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2015-03-15 22:36:25','YYYY-MM-DD HH24:MI:SS'),100,'Inbox As Root Folder','True if other folder (request, error,..) occupy in inbox folder','Some email server as outlook, every folder occupy in inbox, with web mail as google, we easy create my folder outside inbox folder.
this field define where request, error,.. occupy.',50012,65,20,'N',1,'N','Y','p_NestInbox','N','D','b20ee48e-dbb5-4da2-aadb-cdfe40e02124','N')
;
-- Mar 15, 2015 10:39:37 PM ICT
UPDATE AD_Process_Para SET Description='Mail folder, where process will take email and make request', Help=E'folder can define as hierarchy, use "\\" to separate folder. example customer\\vietnam
will read email from folder vietnam under folder customer', IsCentrallyMaintained='N',Updated=TO_TIMESTAMP('2015-03-15 22:39:37','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Process_Para_ID=50009
;
-- Mar 15, 2015 10:42:54 PM ICT
UPDATE AD_Process_Para SET Description='Mail folder to push unsuccess email ', Help=E'folder can define as hierarchy, use "\\" to separate folder. example customer\\error\\vietnam
will puss email can''t process to folder vietnam under folder error under folder customer', IsCentrallyMaintained='N',Updated=TO_TIMESTAMP('2015-03-15 22:42:54','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Process_Para_ID=50011
;
-- Mar 15, 2015 10:46:55 PM ICT
UPDATE AD_Process_Para SET Description='Mail folder, where process will take email and make request', Help=E'folder can define as hierarchy, use "\\" to separate folder. example customer\\vietnam
will read email from folder vietnam under folder customer', IsCentrallyMaintained='N',Updated=TO_TIMESTAMP('2015-03-15 22:46:55','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Process_Para_ID=50010
;
-- Mar 15, 2015 10:48:26 PM ICT
UPDATE AD_Process_Para SET Description='After take email form inbox folder, and process it success, email will move to this folder', Help=E'folder can define as hierarchy, use "\\" to separate folder. example customer\\vietnam
will read email from folder vietnam under folder customer
after take email form inbox folder, and process it success, email will move to this folder',Updated=TO_TIMESTAMP('2015-03-15 22:48:26','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Process_Para_ID=50009
;
SELECT register_migration_script('201503151604-IDEMPIERE-2244.sql') FROM dual
;

View File

@ -0,0 +1,914 @@
/**********************************************************************
* This file is part of iDempiere ERP Open Source *
* http://www.idempiere.org *
* *
* Copyright (C) Contributors *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
* MA 02110-1301, USA. *
**********************************************************************/
package org.compiere.util;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.mail.Address;
import javax.mail.BodyPart;
import javax.mail.Folder;
import javax.mail.FolderClosedException;
import javax.mail.Header;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.StoreClosedException;
import javax.mail.internet.ContentType;
import javax.mail.internet.MimeUtility;
import org.adempiere.exceptions.AdempiereException;
/**
* provide function for sent, receive email in imap protocol
* current only support receive email, for sent email refer {@link org.compiere.util.EMail}
* in case internet line is slow, handle error when analysis message by fetch message part when need can complicate.
* consider to add flag fetch all message at one time (with retry when error).
* after that, analysis offline message.
* http://www.oracle.com/technetwork/java/javamail/faq/index.html#imapserverbug
* @author hieplq base in RequestEMailProcessor
*
*/
public class EmailSrv {
protected transient static CLogger log = CLogger.getCLogger (EmailSrv.class);
protected String imapHost;
protected String imapUser;
protected String imapPass;
protected int imapPort = 143;
protected boolean isGmail = false;
protected Session mailSession;
protected Store mailStore;
public EmailSrv (String imapHost, String imapUser, String imapPass, int imapPort){
this.imapHost = imapHost;
this.imapUser = imapUser;
this.imapPass = imapPass;
isGmail = this.imapHost.toLowerCase().startsWith ("imap.gmail.com");
if (isGmail && imapPort != 993){
log.warning("because imap is gmail server, force port to 993");
imapPort = 993;
}
this.imapPort = imapPort;
}
public EmailSrv (String imapHost, String imapUser, String imapPass){
this (imapHost, imapUser, imapPass, (imapHost != null && imapHost.toLowerCase().startsWith ("imap.gmail.com"))? 993 : 143);
}
public static void logMailPartInfo (Part msg, CLogger log) throws MessagingException{
StringBuilder emailPartLogInfo = new StringBuilder();
if (msg instanceof Message){
emailPartLogInfo.append ("\r\n");
emailPartLogInfo.append ("=============Analysis email:");
emailPartLogInfo.append (((Message)msg).getSubject());
emailPartLogInfo.append ("=============");
emailPartLogInfo.append ("\r\n");
}else{
emailPartLogInfo.append ("\r\n");
emailPartLogInfo.append (" ==mail part==\r\n");
}
emailPartLogInfo.append (" Content type:");
emailPartLogInfo.append (msg.getContentType());
emailPartLogInfo.append ("\r\n");
emailPartLogInfo.append (" Content type raw:");
String [] lsContentTypeRaw = msg.getHeader("Content-Type");
if (lsContentTypeRaw != null){
for (String contentType : lsContentTypeRaw){
emailPartLogInfo.append (contentType);
emailPartLogInfo.append (msg.getHeader("; "));
}
}
emailPartLogInfo.append ("\r\n");
emailPartLogInfo.append (" Disposition:");
emailPartLogInfo.append (msg.getDisposition());
emailPartLogInfo.append ("\r\n");
emailPartLogInfo.append (" ALL heads:");
emailPartLogInfo.append ("\r\n");
@SuppressWarnings("rawtypes")
Enumeration allHead = msg.getAllHeaders();
if (allHead != null){
while (allHead.hasMoreElements()){
Header head = (Header)allHead.nextElement();
emailPartLogInfo.append (" ");
emailPartLogInfo.append (head.getName());
emailPartLogInfo.append (":");
emailPartLogInfo.append (head.getValue());
emailPartLogInfo.append ("\r\n");
}
}
emailPartLogInfo.append ("\r\n");
if (EmailSrv.isBinaryPart (msg) && (msg.getDisposition() == null || msg.getDisposition().trim().equals(""))) {
log.warning("can't detect attach type");
}
if (EmailSrv.isBinaryPart (msg) && Part.INLINE.equalsIgnoreCase(msg.getDisposition()) && EmailSrv.getContentID (msg) == null){
log.warning("an inline content but has no content-id");
}
log.info(emailPartLogInfo.toString());
}
protected Session getMailSession() throws Exception
{
if (mailSession != null)
return mailSession;
// Session
Properties props = System.getProperties();
String protocol = "imap";
if (isGmail){
protocol = "imaps";
}
props.put("mail.store.protocol", protocol);
props.put("mail.host", imapHost);
props.put("mail.imap.port", imapPort);
EMailAuthenticator auth = new EMailAuthenticator(imapUser, imapPass);
mailSession = Session.getInstance(props, auth);
mailSession.setDebug(CLogMgt.isLevelAll());
return mailSession;
} // getSession
public Store getMailStore() throws Exception
{
if (mailStore != null)
return mailStore;
mailStore = getMailSession().getStore();
mailStore.connect();
return mailStore;
} // getStore
public void clearResource (){
if (mailStore != null && mailStore.isConnected()){
try {
mailStore.close();
} catch (MessagingException e) {
e.printStackTrace();
}
}
}
/**
* open a folder in read/write mode.
* @param mailStore
* @param folderName open nest folder by use format folder1/folder2/folder3
* @param isNestInbox in case true, open folder start from default inbox, other open from root folder
* @param createWhenNonExists in case true, create folder by hierarchy if not exists, other not exists will make exception
* @return folder opened in r/w model
* @throws MessagingException
*/
public static Folder getFolder (Store mailStore, String folderName, Boolean isNestInbox, boolean createWhenNonExists) throws MessagingException{
if (folderName == null || "".equals(folderName.trim())){
throw new AdempiereException("Can't open a folder with empty name");
}
char folderSeparate = '\\';
Folder openFolder = null;
if (isNestInbox){
Folder inboxFolder = mailStore.getDefaultFolder();
if (!inboxFolder.exists()){
throw new AdempiereException("This mail account hasn't an inbox folder");
}
folderSeparate = inboxFolder.getSeparator();
openFolder = inboxFolder.getFolder(folderName.replace('\\', folderSeparate));
}else{
String [] lsFolderName = folderName.split("\\\\");
if (lsFolderName.length > 0){
Folder testFolder = mailStore.getFolder(lsFolderName[0]);
folderSeparate = testFolder.getSeparator();
folderName = folderName.replace('\\', folderSeparate);
}
openFolder = mailStore.getFolder(folderName);
}
openFolder = mailStore.getFolder(folderName);
if (!openFolder.exists()){
if (createWhenNonExists){
if (!openFolder.create(Folder.HOLDS_MESSAGES)){
throw new AdempiereException("folder doesn't exist and can't create:" + folderName);
}
}else{
throw new AdempiereException("doesn't exists folder:" + folderName);
}
}
openFolder.open(Folder.READ_WRITE);
return openFolder;
}
/**
* read an email folder, with each email inject object processEmail to processing
* in case error close folder or close session (by disconnect) with retry 3 times
* when error with 5 continue message, with stop process
* @param emailSrv
* @param folderName folder name can hierarchy by use "\"
* @param isNestInbox true in case start folder from inbox
* @param processEmailHandle
* @return
*/
public static boolean readEmailFolder (EmailSrv emailSrv, String folderName, Boolean isNestInbox, ProcessEmailHandle processEmailHandle){
Message [] lsMsg = null;
Folder readerFolder = null;
Store mailStore = null;
ClassLoader tcl = null;
try{
tcl = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(javax.mail.Session.class.getClassLoader());
mailStore = emailSrv.getMailStore();
readerFolder = EmailSrv.getFolder(mailStore, folderName, isNestInbox, false);
lsMsg = readerFolder.getMessages();
} catch (MessagingException e) {
e.printStackTrace();
emailSrv.clearResource();
throw new AdempiereException(e.getMessage());
}catch (AdempiereException appEx){
throw appEx;
} catch (Exception e) {
e.printStackTrace();
emailSrv.clearResource();
throw new AdempiereException(e.getMessage());
}
int numOfTry = 0;
int numeOfContinueErrorEmail = 0;
for (int i = 0; i < lsMsg.length; ){
EmailContent processEmail = null;
Message readerMsg = lsMsg [i];
try {
// reopen store
if (!mailStore.isConnected())
mailStore.connect();
// reopen reader folder
if (!readerFolder.isOpen()){
readerFolder.open(Folder.READ_WRITE);
}
// reopen extra folder
if (processEmailHandle != null && processEmailHandle.getListFolder() != null && processEmailHandle.getListFolder().size() > 0){
for (Folder exFolder : processEmailHandle.getListFolder()){
if (!exFolder.isOpen()){
exFolder.open(Folder.READ_WRITE);
}
}
}
processEmail = EmailSrv.processMessage(readerMsg, processEmailHandle, mailStore, readerFolder);
i++;
numOfTry = 0;
numeOfContinueErrorEmail = 0;
} catch (Exception e) {
if ((e instanceof FolderClosedException || e instanceof StoreClosedException || e instanceof IOException) && numOfTry < 3){
log.warning("network disconnect, retry read email");
// by connect error, sleep for 30s before retry
try {
Thread.sleep(5000);
numOfTry++;
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}else{
numeOfContinueErrorEmail++;
i++;
// call handle error after try 3 time
try {
processEmailHandle.processEmailError(processEmail, readerMsg, mailStore, readerFolder);
} catch (MessagingException e1) {
if (processEmail == null){
log.log(Level.SEVERE, String.format("can't complete handle error when process message with exception:%1$s", e1.getMessage()));
}else{
log.log(Level.SEVERE, String.format("can't complete handle error when process message with exception:%1$s-%2$s-%3$s", processEmail.subject, processEmail.messageID, e1.getMessage()));
}
}
if (e instanceof FolderClosedException || e instanceof StoreClosedException || e instanceof IOException){
throw new AdempiereException("can't reopen email store for process after three tries");
}
// stop when has more 5 continue message error
if (numeOfContinueErrorEmail > 5){
emailSrv.clearResource();
throw new AdempiereException("have 5 email errors when process");
}
}
}
}
emailSrv.clearResource();
}finally{
Thread.currentThread().setContextClassLoader(tcl);
}
return true;
}
/**
* @see #processMessage(Message, ProcessEmailHandle, Store, Folder)
* just manipulate message
* @param msg
* @return
* @throws MessagingException
*/
public static EmailContent processMessage (Message msg) throws MessagingException, IOException{
return EmailSrv.processMessage (msg, null, null, null);
}
/**
*
* @param msg
* @param evaluateEmailHead
* @return return EmailInfo contain info of email, in case evaluateEmailHead make cancel, return null
* @throws MessagingException
*/
public static EmailContent processMessage (Message msg, ProcessEmailHandle evaluateEmailHead, Store mailStore, Folder mailFolder) throws MessagingException, IOException{
EmailContent emailInfo = new EmailContent();
// set from address
Address[] from = msg.getFrom();
if (from != null){
for (Address fromAddress : from){
String address = null;
if (fromAddress.toString().startsWith("<") && fromAddress.toString().endsWith(">")) {
address = fromAddress.toString().substring(1, fromAddress.toString().length() - 1);
} else {
address = fromAddress.toString();
}
emailInfo.fromAddress.add(address);
}
}
// get message-id
String [] lsMessageId = EmailSrv.getPartHeader(msg, "Message-ID");
if (lsMessageId != null){
emailInfo.messageID = lsMessageId[0];
}
emailInfo.subject = msg.getSubject();
emailInfo.sentDate = msg.getSentDate();
if (evaluateEmailHead != null){
if (evaluateEmailHead.checkEmailHeader(emailInfo, msg)){
return null;
}
}
EmailSrv.analysisEmailStructure(msg, emailInfo, true);
if (evaluateEmailHead != null){
evaluateEmailHead.processEmailContent(emailInfo, msg, mailStore, mailFolder);
}
return emailInfo;
}
/**
* @see #analysisEmailStructure(Part, EmailContent, boolean)
* @param msg
* @param emailContent
* @throws MessagingException
* @throws IOException
*/
public static void analysisEmailStructure (Part msg, EmailContent emailContent) throws MessagingException, IOException{
EmailSrv.analysisEmailStructure (msg, emailContent, false);
}
/**
* Analysis {@link Part} object
* get content in plan or html text.
* detect type of attach file and put in to {@link EmailContent} for late process
* @param msg mime part to analysis
* @param emailContent object contain result analysis
* @param isRoot true when part is {@link Message}
* @throws MessagingException
* @throws IOException
*/
public static void analysisEmailStructure (Part msg, EmailContent emailContent, boolean isRoot) throws MessagingException, IOException
{
logMailPartInfo (msg, log);
boolean isUnknowPart = false;
// [text/*] match with every mime of text, example: text/[plan, html, txt,..]
if (msg.isMimeType("text/*"))
{
// is a text file attach
if (Part.ATTACHMENT.equalsIgnoreCase(msg.getDisposition())){
if (msg instanceof BodyPart){
emailContent.lsAttachPart.add((BodyPart)msg);
}else{
log.warning("can't detect where this file from");
}
return;
}
// content is not cache, because in case use content many time,
// consider save it to local variable, don't call getContent many times
// http://www.oracle.com/technetwork/java/javamail/faq/index.html#cache
String txtContent = msg.getContent().toString();
if (txtContent != null && msg.isMimeType("text/html")){
emailContent.htmlContentBuild.append(EmailSrv.getTextFromMailPart (msg));
}else if (txtContent != null){
emailContent.textContentBuil.append(EmailSrv.getTextFromMailPart (msg));
}else{
log.info("has non content in this part");
}
} else if (msg.isMimeType("message/rfc822")) // Nested in multipart/digest
{
//TODO: html format will lost this content? must test
log.warning("check html content of message/rfc822");
emailContent.textContentBuil.append(msg.getContent());
} else if (msg.isMimeType("multipart/*"))
{
// when message is multipart, process each part to get content (text, embed, attach,..)
Multipart mp = (Multipart)msg.getContent();
int count = mp.getCount();
for (int i = 0; i < count; i++)
{
BodyPart part = mp.getBodyPart(i);
EmailSrv.analysisEmailStructure(part, emailContent);
}
} else if (isBinaryPart (msg)) // attach part
{
if (msg instanceof BodyPart){
BodyPart attachPart = (BodyPart)msg;
if (attachPart.getDisposition() == null || attachPart.getDisposition().equalsIgnoreCase(Part.INLINE)){
emailContent.lsEmbedPart.add(attachPart);
}else if (attachPart.getDisposition().equalsIgnoreCase(Part.ATTACHMENT)){
emailContent.lsAttachPart.add(attachPart);
}else{
isUnknowPart = true;
}
}else{
log.warning ("TODO:content type is a binary, but isn't a instance of BodyPart");
}
}else {
isUnknowPart = true;
}
if (isUnknowPart){
emailContent.lsUnknowPart.add(msg);
log.warning ("an unknown part, this content will miss");
}
} // getMessage
/**
* http://www.oracle.com/technetwork/java/javamail/faq/index.html#unsupen
* @param msg
* @return
* @throws IOException
* @throws MessagingException
*/
public static String getTextFromMailPart (Part txtPart) throws MessagingException, IOException{
String text = null;
try {
Object content = txtPart.getContent();
if (content != null)
text = content.toString();
} catch (UnsupportedEncodingException uex) {
log.info("http://www.oracle.com/technetwork/java/javamail/faq/index.html#unsupen");
log.warning(uex.getMessage());
/*
* Read the input stream into a byte array.
* Choose a charset in some heuristic manner, use
* that charset in the java.lang.String constructor
* to convert the byte array into a String.
*/
// get charset of text in email
ContentType cType = new ContentType(txtPart.getContentType());
String emailCharsetStr = cType.getParameter("charset");
String javaCharset = MimeUtility.javaCharset(emailCharsetStr);
Charset emailCharset = Charset.forName("ISO_8859_1") ;
if (Charset.isSupported(javaCharset)){
emailCharset = Charset.forName(javaCharset);
}
log.warning("try read with charset " + emailCharset.displayName() + " maybe make break text");
// read text from input stream with
String str = null;
StringBuilder sb = new StringBuilder(8192);
InputStream is = null;
try {
is = txtPart.getInputStream();
BufferedReader bufferReader = new BufferedReader(new InputStreamReader(is, emailCharset));
while ((str = bufferReader.readLine()) != null) {
sb.append(str);
}
} catch (IOException e) {
throw e;
} finally {
try{
if (is != null)
is.close();
}catch(IOException ex){}
}
text = sb.toString();
}
return text;
}
/**
* read binary from a multi-part
* @param binaryPart
* @return
* @throws IOException
* @throws MessagingException
*/
public static byte[] getBinaryData (Part binaryPart) throws IOException, MessagingException{
InputStream in = null;
try{
in = binaryPart.getInputStream();
ByteArrayOutputStream out = new ByteArrayOutputStream();
final int BUF_SIZE = 1 << 8; //1KiB buffer
byte[] buffer = new byte[BUF_SIZE];
int bytesRead = -1;
while((bytesRead = in.read(buffer)) > -1) {
out.write(buffer, 0, bytesRead);
}
return out.toByteArray();
}catch (IOException ioe){
log.warning("exception when read attach in email");
throw ioe;
}finally{
try{
if (in != null) in.close();
}catch (Exception ex){}
}
}
/**
* download attach file and convert to base64 encoding
* @param mailPart
* @return
* @throws IOException
* @throws MessagingException
*/
public static String getBinaryAsBASE64 (BodyPart mailPart) throws IOException, MessagingException{
// can improve by: when getEncode of mimeBodyPart is base64, read direct to ignore encorder, decorder
return javax.xml.bind.DatatypeConverter.printBase64Binary(EmailSrv.getBinaryData(mailPart));
}
/**
* {@link #embedImgToEmail(String, ProvideBase64Data, String)}
* use default pattern for embed image is "\\s+src\\s*=\\s*\"cid:(.*?)\"");
* @param mailContent
* @param provideBase64Data
* @return
* @throws Exception
*/
public static String embedImgToEmail (String mailContent, ProvideBase64Data provideBase64Data) throws Exception{
return EmailSrv.embedImgToEmail (mailContent, provideBase64Data, "\\s+src\\s*=\\s*\"cid:(.*?)\"");
}
/**
* find in mailContent every pattern of embed image
* with each replace cid by base64 data.
* preview in cfEditor pattern is "\\s+src\\s*=\\s*\"cid:(.*?)\""
* with embed image in gmail, pattern is "\\s+src\\s*=\\s*3D\\s*\"cid:(.*?)\""
* with embed image in other server (nmicoud), pattern is "\\s+src\\s*=\\s*\"cid:(.*?)\""
* REMEMBER:cid:(.*?) must in group 1
* @param mailContent
* @param provideBase64Data
* @param embedPattern
* @return
* @throws Exception
*/
public static String embedImgToEmail (String mailContent, ProvideBase64Data provideBase64Data, String embedPattern) throws MessagingException, IOException{
String origonSign = mailContent;
// pattern to get src value of attach image.
Pattern imgPattern = Pattern.compile(embedPattern);
// matcher object to anlysic image tab in sign
Matcher imgMatcher = imgPattern.matcher(origonSign);
// part not include "cid:imageName"
List<String> lsPart = new ArrayList<String> ();
// list image name in sign
List<String> lsImgSrc = new ArrayList<String> ();
// start index of text part not include "cid:imageName"
int startIndex = 0;
// start index of "cid:imageName"
int startIndexMatch = 0;
// end index of "cid:imageName"
int endIndexMatch = 0;
// split sign string to part
// example: acb <img src="cid:image1"/> def <img src="cid:image2"/> ghi
// lsPart will include "acb <img ", "/> def <img ", "/> ghi"
// lsImgSrc wil include "image1", "image2"
while (imgMatcher.find()){
startIndexMatch = imgMatcher.start();
endIndexMatch = imgMatcher.end();
// split text from end last matcher to start matcher
String startString = origonSign.substring(startIndex, startIndexMatch);
lsPart.add(startString);
// get image name
lsImgSrc.add(imgMatcher.group(1).trim());
startIndex = endIndexMatch;
}
// end string not include "cid:imageName"
String startString = origonSign.substring(startIndex);
lsPart.add(startString);
// no image in sign return origon
if (lsPart.size() == 0 || lsImgSrc.size() == 0){
return origonSign;
}
StringBuilder reconstructSign = new StringBuilder();
// reconstruct with image source convert to embed image by base64 encode
for (int i = 0; i < lsImgSrc.size(); i++){
if (i == 0)
reconstructSign.append(lsPart.get(0));
String imageBase64 = provideBase64Data.getBase64Data(lsImgSrc.get(i));;
if (imageBase64 == null){
// no attach map with this src value
// add server warning and return origon without src value,
// maybe can improve to remove img tag
//TODO: add server warning log
log.warning("miss data of image has id is:" + lsImgSrc.get(i));
}else{
// convert image to base64 encode and embed to img tag
reconstructSign.append(" alt=\"inline_image_").append(lsImgSrc.get(i)).append("\" src=\"data:image/jpeg;base64,").append(imageBase64).append("\"");
}
reconstructSign.append(lsPart.get(i + 1));
}
return reconstructSign.toString();
}
public static boolean isBinaryPart (Part binaryPart) throws MessagingException{
return binaryPart.isMimeType("application/*") || binaryPart.isMimeType ("image/*");
}
/**
* get contentID from header, with each inline attach, will have a contentID value
* in case value at contentID difference value at X-Attachment-Id, must manual recheck to add process
* @param attachPart
* @return
* @throws MessagingException
*/
public static String getContentID (Part attachPart) throws MessagingException{
String [] lsContentID = attachPart.getHeader("Content-Id");
String contentID = null;
// get content value from header Content-Id
if (lsContentID != null){
for (String contentValue : lsContentID){
if (contentValue != null && !"".equals(contentValue.trim())){
if (contentID != null && !contentID.equals(contentValue.trim())){
log.warning("has difference value of Content-Id");
}
contentID = contentValue;
}
}
}
// content-id in format <content-id value>, because, remove < and >
if (contentID != null){
if (contentID.startsWith("<") && contentID.endsWith(">"))
contentID = contentID.substring(1, contentID.length() - 1);
}
// get content value from header X-Attachment-Id
lsContentID = attachPart.getHeader("X-Attachment-Id");
if (lsContentID != null){
for (String contentValue : lsContentID){
if (contentValue != null && !"".equals(contentValue.trim())){
if (contentID != null && !contentID.equals(contentValue.trim())){
log.warning("value of X-Attachment-Id difference value of Content-Id");
}
if (contentID == null){
contentID = contentValue;
}
}
}
}
return contentID;
}
public static String [] getPartHeader (Part msg, String headerName) throws MessagingException{
String [] headers = msg.getHeader(headerName);
if (headers != null){
for (int i = 0; i < headers.length; i++){
String head = headers[i];
if (head.toString().startsWith("<") && head.endsWith(">")) {
head = head.substring(1, head.length() - 1);
headers [i] = head;
}
}
}
return headers;
}
//============helper class===========
/**
* when process an email content sometimes we wish embed image as base64 string to mail.
* source of image can go from many where. this interface for abstract source.
* @author hieplq
*
*/
public static interface ProvideBase64Data {
public String getBase64Data (String dataId) throws MessagingException, IOException;
}
/**
* this class inject to email reading process of function {@link EmailSrv#processMessage(Message, ProcessEmailHandle, Store, Folder)}
* @author hieplq
*
*/
public static interface ProcessEmailHandle {
/**
* after read header of email (from, subject, message_id,...),
* will call this function to evaluate will continue process or cancel this email
* at this time, in EmailInfo just has header info, content and attach is not manipulate
* @param emailHeader
* @param emailRaw
* @return
* @throws MessagingException
*/
public boolean checkEmailHeader (EmailContent emailHeader, Message emailRaw) throws MessagingException;
/**
* when read email for process, after some time try when has error, will call this function to ensure this email is can't process
* @param emailHeader
* @param emailRaw
* @param mailStore
* @param mailFolder
* @return
* @throws MessagingException
*/
public void processEmailError (EmailContent emailHeader, Message emailRaw, Store mailStore, Folder mailFolder) throws MessagingException;
/**
* main where to process email. this time, every email info is manipulate to emailContent
* @param emailContent
* @param emailRaw
* @param mailStore
* @param mailFolder
* @throws MessagingException
*/
public void processEmailContent (EmailContent emailContent, Message emailRaw, Store mailStore, Folder mailFolder) throws MessagingException, IOException;
/**
* List all folder use when process message
* this function make handle close folder and close session can reopen it.
* @return
*/
public List<Folder> getListFolder ();
}
/**
* {@docRoot}
* this class implement source of image from attach of email
* @author hieplq
*
*/
public static class EmailEmbedProvideBase64Data implements ProvideBase64Data{
private EmailContent emailContent;
public EmailEmbedProvideBase64Data(EmailContent emailContent){
this.emailContent = emailContent;
}
/**
* get image from image embed in email content by its content_id
* download it and convert to string base64
* @param contentId
* @return null when can't find attach has this contentId
*/
public String getBase64Data (String contentId) throws MessagingException, IOException{
if (contentId == null)
return null;
for (BodyPart imageEmbed : emailContent.lsEmbedPart){
if (contentId.equalsIgnoreCase(EmailSrv.getContentID(imageEmbed))){
return EmailSrv.getBinaryAsBASE64(imageEmbed);
}
}
return null;
}
}
/**
* manipulate from {@link Message}
* separate attach file to embed, attach, un-know list
* @author hieplq
*
*/
public static class EmailContent {
/**
* contain list from address.
* when @see javax.mail.Message#getFrom() return null, this list is empty
*
*/
public List<String> fromAddress = new ArrayList<String>();
/**
* unique value. has max length is 998 charater
* http://tools.ietf.org/html/rfc4130#section-5.3.3
*/
public String messageID;
public String subject;
public Date sentDate;
/**
* use to build content, to get content call {@link #getTextContent()}
*/
public StringBuilder textContentBuil = new StringBuilder();
/**
* use to build content, to get content call {@link #getHtmlContent(boolean)}
*/
public StringBuilder htmlContentBuild = new StringBuilder();
/**
* list attach file
*/
public List<BodyPart> lsAttachPart = new ArrayList<BodyPart>();
/**
* list embed file
*/
public List<BodyPart> lsEmbedPart = new ArrayList<BodyPart>();
/**
* list part unknow to process
*/
public List<Part> lsUnknowPart = new ArrayList<Part>();
/**
* get html content, when withEmbedImg = true, read embed image to base64 and embed to html content
* @param withEmbedImg
* @return return null when has empty content
* @throws Exception
*/
public String getHtmlContent (boolean withEmbedImg) throws MessagingException, IOException{
if (htmlContentBuild == null || htmlContentBuild.length() == 0)
return null;
EmailEmbedProvideBase64Data provideBase64Data = new EmailEmbedProvideBase64Data(this);
return EmailSrv.embedImgToEmail(htmlContentBuild.toString(), provideBase64Data, "\\s+src\\s*=\\s*(?:3D)?\\s*\"cid:(.*?)\"");
}
/**
* get text content
* @return return null when has no content
*/
public String getTextContent (){
//TODO: when email has only html content, consider convert to text content
return textContentBuil.toString();
}
}
}

View File

@ -17,4 +17,4 @@ goto START
@Echo Starting iDempiere Server ...
@Echo =======================================
@"%JAVA%" -Dosgi.console=localhost:12612 -Djetty.home=jettyhome -Djetty.etc.config.urls=etc/jetty.xml,etc/jetty-selector.xml,etc/jetty-ssl.xml,etc/jetty-https.xml,etc/jetty-deployer.xml -XX:MaxPermSize=192m -jar plugins/org.eclipse.equinox.launcher_1.*.jar -application org.adempiere.server.application
@"%JAVA%" -Dosgi.console=localhost:12612 -Djetty.home=jettyhome -Djetty.etc.config.urls=etc/jetty.xml,etc/jetty-selector.xml,etc/jetty-ssl.xml,etc/jetty-https.xml,etc/jetty-deployer.xml -XX:MaxPermSize=192m -Dmail.mime.encodefilename=true -Dmail.mime.decodefilename=true -Dmail.mime.encodeparameters=true -Dmail.mime.decodeparameters=true -jar plugins/org.eclipse.equinox.launcher_1.*.jar -application org.adempiere.server.application

View File

@ -19,4 +19,4 @@ echo ===================================
unset DISPLAY
BASE=`dirname $( readlink -f idempiere-server.sh )`
$JAVA ${DEBUG} -Dosgi.console=localhost:12612 -Djetty.home=$BASE/jettyhome -Djetty.etc.config.urls=etc/jetty.xml,etc/jetty-selector.xml,etc/jetty-ssl.xml,etc/jetty-https.xml,etc/jetty-deployer.xml -XX:MaxPermSize=192m -jar $BASE/plugins/org.eclipse.equinox.launcher_1.*.jar -application org.adempiere.server.application
$JAVA ${DEBUG} -Dosgi.console=localhost:12612 -Djetty.home=$BASE/jettyhome -Djetty.etc.config.urls=etc/jetty.xml,etc/jetty-selector.xml,etc/jetty-ssl.xml,etc/jetty-https.xml,etc/jetty-deployer.xml -XX:MaxPermSize=192m -Dmail.mime.encodefilename=true -Dmail.mime.decodefilename=true -Dmail.mime.encodeparameters=true -Dmail.mime.decodeparameters=true -jar $BASE/plugins/org.eclipse.equinox.launcher_1.*.jar -application org.adempiere.server.application

View File

@ -10,7 +10,7 @@
<programArgs>--launcher.XXMaxPermSize 192m
<argsX86>-console 12612 --launcher.XXMaxPermSize 192m</argsX86>
</programArgs>
<vmArgs>-Djetty.home=jettyhome -Djetty.etc.config.urls=etc/jetty.xml,etc/jetty-selector.xml,etc/jetty-ssl.xml,etc/jetty-https.xml,etc/jetty-deployer.xml -Dosgi.console=localhost:12612
<vmArgs>-Djetty.home=jettyhome -Djetty.etc.config.urls=etc/jetty.xml,etc/jetty-selector.xml,etc/jetty-ssl.xml,etc/jetty-https.xml,etc/jetty-deployer.xml -Dosgi.console=localhost:12612 -Dmail.mime.encodefilename=true -Dmail.mime.decodefilename=true -Dmail.mime.encodeparameters=true -Dmail.mime.decodeparameters=true
<argsX86>-Dosgi.noShutdown=true -Dosgi.framework.activeThreadType=normal -Dosgi.compatibility.bootdelegation=true -Djetty.home=${workspace_loc}/jettyhome -Djetty.etc.config.urls=etc/jetty.xml,etc/jetty-selector.xml,etc/jetty-ssl.xml,etc/jetty-https.xml,etc/jetty-deployer.xml</argsX86>
</vmArgs>
<vmArgsMac>-XstartOnFirstThread -Dorg.eclipse.swt.internal.carbon.smallFonts

View File

@ -19,7 +19,7 @@
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -arch ${target.arch} -nl ${target.nl} -consoleLog -console"/>
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dosgi.noShutdown=true -Dosgi.framework.activeThreadType=normal -Dosgi.compatibility.bootdelegation=true -Dosgi.console.enable.builtin=false -XX:MaxPermSize=128M -Djetty.home=${workspace_loc}/jettyhome -Djetty.etc.config.urls=etc/jetty.xml,etc/jetty-selector.xml,etc/jetty-ssl.xml,etc/jetty-https.xml,etc/jetty-deployer.xml"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dosgi.noShutdown=true -Dosgi.framework.activeThreadType=normal -Dosgi.compatibility.bootdelegation=true -Dosgi.console.enable.builtin=false -XX:MaxPermSize=128M -Djetty.home=${workspace_loc}/jettyhome -Djetty.etc.config.urls=etc/jetty.xml,etc/jetty-selector.xml,etc/jetty-ssl.xml,etc/jetty-https.xml,etc/jetty-deployer.xml -Dmail.mime.encodefilename=true -Dmail.mime.decodefilename=true -Dmail.mime.encodeparameters=true -Dmail.mime.decodeparameters=true"/>
<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${workspace_loc}"/>
<stringAttribute key="pde.version" value="3.3"/>
<stringAttribute key="product" value="org.adempiere.server.server_product"/>