diff --git a/migration/i2.1z/oracle/201503151604-IDEMPIERE-2244.sql b/migration/i2.1z/oracle/201503151604-IDEMPIERE-2244.sql new file mode 100644 index 0000000000..3e0a3b24fe --- /dev/null +++ b/migration/i2.1z/oracle/201503151604-IDEMPIERE-2244.sql @@ -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 +; diff --git a/migration/i2.1z/postgresql/201503151604-IDEMPIERE-2244.sql b/migration/i2.1z/postgresql/201503151604-IDEMPIERE-2244.sql new file mode 100644 index 0000000000..c5f77a6449 --- /dev/null +++ b/migration/i2.1z/postgresql/201503151604-IDEMPIERE-2244.sql @@ -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 +; diff --git a/org.adempiere.base.process/src/org/compiere/process/RequestEMailProcessor.java b/org.adempiere.base.process/src/org/compiere/process/RequestEMailProcessor.java index fc47d96420..58d5066125 100644 --- a/org.adempiere.base.process/src/org/compiere/process/RequestEMailProcessor.java +++ b/org.adempiere.base.process/src/org/compiere/process/RequestEMailProcessor.java @@ -16,26 +16,21 @@ *****************************************************************************/ package org.compiere.process; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.net.UnknownHostException; +import java.nio.charset.Charset; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; -import java.util.Enumeration; -import java.util.Properties; +import java.util.ArrayList; +import java.util.List; import java.util.logging.Level; -import javax.mail.Address; +import javax.mail.BodyPart; import javax.mail.Flags; import javax.mail.Folder; -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; @@ -43,18 +38,23 @@ import org.adempiere.exceptions.AdempiereException; import org.compiere.model.MAttachment; import org.compiere.model.MColumn; import org.compiere.model.MRequest; +import org.compiere.model.MRequestType; import org.compiere.model.MUser; -import org.compiere.util.CLogMgt; import org.compiere.util.DB; -import org.compiere.util.EMailAuthenticator; +import org.compiere.util.EmailSrv; +import org.compiere.util.EmailSrv.EmailContent; +import org.compiere.util.EmailSrv.ProcessEmailHandle; +import org.compiere.util.Trx; /** * Request Email Processor * * @author Carlos Ruiz based on initial work by Jorg Janke - sponsored by DigitalArmour * @version $Id: RequestEMailProcessor.java,v 1.2 2006/10/23 06:01:20 cruiz Exp $ + * hieplq:separate email process to other class for easy re-use and do IDEMPIERE-2244 + * */ -public class RequestEMailProcessor extends SvrProcess +public class RequestEMailProcessor extends SvrProcess implements ProcessEmailHandle { protected String p_IMAPHost = null; protected int p_IMAPPort = 143; @@ -62,6 +62,7 @@ public class RequestEMailProcessor extends SvrProcess protected String p_IMAPPwd = null; protected String p_RequestFolder = null; protected String p_InboxFolder = null; + protected Boolean p_NestInbox = true; protected String p_ErrorFolder = null; protected int C_BPartner_ID = 0; protected int AD_User_ID = 0; @@ -87,6 +88,9 @@ public class RequestEMailProcessor extends SvrProcess /** Process Delivery Confirm */ // private static final int DELIVERY = 9; + protected Folder errorFolder; + protected Folder requestFolder; + protected List lsFolderProcess = new ArrayList(); /** * Prepare - e.g., get Parameters. */ @@ -124,6 +128,8 @@ public class RequestEMailProcessor extends SvrProcess p_DefaultPriority = ((String)para[i].getParameter()); else if (name.equals("p_DefaultConfidentiality")) p_DefaultConfidentiality = ((String)para[i].getParameter()); + else if (name.equals("p_nestInbox")) + p_NestInbox = "Y".equalsIgnoreCase(para[i].getParameter().toString()); else log.log(Level.SEVERE, "prepare - Unknown Parameter: " + name); } @@ -137,6 +143,20 @@ public class RequestEMailProcessor extends SvrProcess */ protected String doIt() throws Exception { + EmailSrv emailSrv = new EmailSrv(p_IMAPHost, p_IMAPUser, p_IMAPPwd, p_IMAPPort); + + checkInputParameter (emailSrv); + + EmailSrv.readEmailFolder(emailSrv, p_InboxFolder, p_NestInbox, this); + + StringBuilder msgreturn = new StringBuilder("processInBox - Total=").append(noProcessed) + .append(" - Requests=").append(noRequest) + .append(" - Errors=").append(noError); + return msgreturn.toString(); + } // doIt + + protected void checkInputParameter (EmailSrv emailSrv) throws MessagingException, Exception { + // === check input parameter === int portStartIndex = p_IMAPHost.lastIndexOf(":"); if (portStartIndex > 0){ String strPort = p_IMAPHost.substring(portStartIndex + 1, p_IMAPHost.length()); @@ -157,227 +177,83 @@ public class RequestEMailProcessor extends SvrProcess " InboxFolder=" + p_InboxFolder + " ErrorFolder=" + p_ErrorFolder); - try - { - getSession(); - getStore(); - processInBox(); - } catch (MessagingException mailEx) { - if (mailEx.getNextException() != null && mailEx.getNextException() instanceof UnknownHostException){ - throw new AdempiereException("Error host : " + mailEx.getMessage()); - } else { - throw new AdempiereException("Error when make connect to email server : " + mailEx.getMessage()); - } - } catch (AdempiereException ae) { - throw ae; - } - catch (Exception e) { - log.log(Level.SEVERE, "processInBox", e); - throw e; - }finally { -// Cleanup - try - { - if ( m_store != null && m_store.isConnected()) - m_store.close(); - } catch(Exception ex) {} + if (R_RequestType_ID < 1 && MRequestType.getDefault(getCtx()) == null){ + throw new AdempiereException("request type: must define a default request type, or select one in request type field"); + } + + requestFolder = EmailSrv.getFolder(emailSrv.getMailStore(), p_RequestFolder, p_NestInbox, true); + errorFolder = EmailSrv.getFolder(emailSrv.getMailStore(), p_ErrorFolder, p_NestInbox, true); + + lsFolderProcess.add(requestFolder); + lsFolderProcess.add(errorFolder); + } - StringBuilder msgreturn = new StringBuilder("processInBox - Total=").append(noProcessed) - .append(" - Requests=").append(noRequest) - .append(" - Errors=").append(noError); - return msgreturn.toString(); - } // doIt + @Override + public boolean checkEmailHeader(EmailContent emailHeader, Message emailRaw) throws MessagingException { + boolean isCancel = emailHeader.fromAddress.size() == 0; + if (isCancel){ + errorFolder.appendMessages(new Message[]{emailRaw}); + noError++; + } + return isCancel; + } - /************************************************************************** - * Get Session - * @return Session - * @throws Exception - */ - protected Session getSession() throws Exception - { - if (m_session != null) - return m_session; - - // Session - Properties props = System.getProperties(); - props.put("mail.store.protocol", "imap"); - props.put("mail.transport.protocol", "imap"); - props.put("mail.host", p_IMAPHost); - props.put("mail.imap.port", p_IMAPPort); - - EMailAuthenticator auth = new EMailAuthenticator (p_IMAPUser, p_IMAPPwd); - // - m_session = Session.getInstance(props, auth); - m_session.setDebug(CLogMgt.isLevelFinest()); - if (log.isLoggable(Level.FINE)) log.fine("getSession - " + m_session); - return m_session; - } // getSession - - - /** - * Get Store - * @return Store - * @throws Exception - */ - protected Store getStore() throws Exception - { - if (m_store != null) - return m_store; - if (getSession() == null) - throw new IllegalStateException("No Session"); - - // Get IMAP Store - if (p_IMAPHost.startsWith("imap.gmail.com")) - m_store = m_session.getStore("imaps"); - else - m_store = m_session.getStore("imap"); - // Connect - m_store.connect(); - // - if (log.isLoggable(Level.FINE)) log.fine("getStore - " + m_store); - return m_store; - } // getStore - - /** - * Process InBox - * @return number of processed - * @throws Exception - */ - protected void processInBox() throws Exception - { - // Folder - Folder folder; - folder = m_store.getDefaultFolder(); - if (folder == null) - throw new IllegalStateException("No default folder"); - // Open Inbox - Folder inbox = folder.getFolder(p_InboxFolder); - if (!inbox.exists()) - throw new AdempiereException ("Wrong inbox name : " + p_InboxFolder); - inbox.open(Folder.READ_WRITE); - if (log.isLoggable(Level.FINE)) log.fine("processInBox - " + inbox.getName() - + "; Messages Total=" + inbox.getMessageCount() - + "; New=" + inbox.getNewMessageCount()); - - // Open Request - Folder requestFolder = folder.getFolder(p_RequestFolder); - if (!requestFolder.exists() && !requestFolder.create(Folder.HOLDS_MESSAGES)) - throw new AdempiereException ("Cannot create request folder : " + p_RequestFolder); - requestFolder.open(Folder.READ_WRITE); - - // Open Workflow - // Folder workflowFolder = folder.getFolder("CWorkflow"); - // if (!workflowFolder.exists() && !workflowFolder.create(Folder.HOLDS_MESSAGES)) - // throw new IllegalStateException("Cannot create Workflow Folder"); - // workflowFolder.open(Folder.READ_WRITE); - - // Open Error - Folder errorFolder = folder.getFolder(p_ErrorFolder); - if (!errorFolder.exists() && !errorFolder.create(Folder.HOLDS_MESSAGES)) - throw new AdempiereException ("Cannot create Error Folder : " + p_ErrorFolder); - errorFolder.open(Folder.READ_WRITE); - - // Messages - Message[] messages = inbox.getMessages(); - /** - FetchProfile fp = new FetchProfile(); - fp.add(FetchProfile.Item.ENVELOPE); - fp.add(FetchProfile.Item.FLAGS); - fp.add("X-Mailer"); - inbox.fetch(messages, fp); - **/ - // - for (int i = 0; i < messages.length; i++) -// for (int i = messages.length-1; i >= 0; i--) // newest first - { - Message msg = messages[i]; - int result = processMessage (msg); - if (result == REQUEST) - { - String[] hdrs = msg.getHeader("Message-ID"); - // Copy to processed - try { - if (createRequest(msg)) { - msg.setFlag(Flags.Flag.SEEN, true); - msg.setFlag(Flags.Flag.ANSWERED, true); - requestFolder.appendMessages(new Message[]{msg}); - // log movement - if (log.isLoggable(Level.INFO)) log.info("message " + hdrs[0] + " moved to " + p_RequestFolder + " folder"); - if (log.isLoggable(Level.INFO)) log.info("message info: Sent -> " + msg.getSentDate() + " From -> " + msg.getFrom()[0].toString()); - // Delete in InBox - msg.setFlag(Flags.Flag.DELETED, true); - @SuppressWarnings("unused") - Message[] deleted = inbox.expunge(); - - noRequest++; - } else { - noError++; - } - } catch (Exception e) { - if (log.isLoggable(Level.INFO)) log.info("message " + hdrs[0] + " threw error"); - e.printStackTrace(); - } + @Override + public void processEmailContent(EmailContent emailHeader, Message emailRaw, Store mailStore, Folder mailFolder) throws MessagingException, IOException { + Trx trxRequest = null; + try { + trxRequest = Trx.get(Trx.createTrxName("SvrProcess-makerequest"), true); + trxRequest.start(); + + createRequest(emailHeader, trxRequest.getTrxName()); + emailRaw.setFlag(Flags.Flag.SEEN, true); + emailRaw.setFlag(Flags.Flag.ANSWERED, true); + requestFolder.appendMessages(new Message[]{emailRaw}); + // log movement + if (log.isLoggable(Level.INFO)) log.info("message " + emailHeader.subject + " moved to " + p_RequestFolder + " folder"); + if (log.isLoggable(Level.INFO)) log.info("message info: Sent -> " + emailHeader.sentDate + " From -> " + emailHeader.fromAddress.get(0).toString()); + // Delete in InBox + emailRaw.setFlag(Flags.Flag.DELETED, true); + @SuppressWarnings("unused") + Message[] deleted = mailFolder.expunge(); + noRequest++; + trxRequest.commit(true); + } catch (Exception e) { + trxRequest.rollback(); + if (log.isLoggable(Level.INFO)) log.info("message " + emailHeader.subject + " threw error"); + e.printStackTrace(); + if (e instanceof AdempiereException){ + throw (AdempiereException)e; + }else if (e instanceof MessagingException){ + throw (MessagingException)e; + }else{ + throw new AdempiereException (e.getMessage()); } -// else if (result == WORKFLOW) -// { -// msg.setFlag(Flags.Flag.SEEN, true); -// msg.setFlag(Flags.Flag.ANSWERED, true); -// // Copy to processed -// workflowFolder.appendMessages(new Message[]{msg}); -// } -// else if (result == DELIVERY) -// { -// msg.setFlag(Flags.Flag.SEEN, true); -// msg.setFlag(Flags.Flag.ANSWERED, true); -// } - else // error - { - errorFolder.appendMessages(new Message[]{msg}); - String[] hdrs = msg.getHeader("Message-ID"); - log.warning("message " + hdrs[0] + " moved to " + p_ErrorFolder + " folder"); - log.warning("message info: Sent -> " + msg.getSentDate() + " From -> " + msg.getFrom()[0].toString()); - noError++; + }finally{ + if (trxRequest != null){ + trxRequest.close(); + trxRequest = null; } - noProcessed++; } - if (log.isLoggable(Level.INFO)) log.info("processInBox - Total=" + noProcessed + - " - Requests=" + noRequest + - " - Errors=" + noError); - // Fini - errorFolder.close(false); - requestFolder.close(false); - // workflowFolder.close(false); - // - inbox.close(true); - } // processInBox + } + /** * Create request * @param msg message * @return * @return Type of Message - * @throws MessagingException + * @throws Exception */ - protected boolean createRequest(Message msg) throws MessagingException, SQLException { + protected void createRequest(EmailContent emailContent, String trxName) throws Exception { // Assign from variable - Address[] from = msg.getFrom(); - String fromAddress; - // Carlos Ruiz - if (from[0].toString().indexOf('<')!= -1 && from[0].toString().indexOf('>')!= -1) { - fromAddress = from[0].toString().substring(from[0].toString().indexOf('<')+1, from[0].toString().indexOf('>')); - if (log.isLoggable(Level.INFO)) log.info("fromAddress stripped: "+fromAddress); - } else { - fromAddress = from[0].toString(); - } - // Message-ID as documentNo - String[] hdrs = msg.getHeader("Message-ID"); - // set DocumentNo + String fromAddress = emailContent.fromAddress.get(0); + int maxlen = MColumn.get(getCtx(), MRequest.Table_Name, MRequest.COLUMNNAME_DocumentNo).getFieldLength(); - String documentNo = hdrs[0]; - if (documentNo.startsWith("<") && documentNo.endsWith(">")) - documentNo = documentNo.substring(1, documentNo.length()-1); + String documentNo = emailContent.messageID; + if (documentNo.length() > maxlen) documentNo = documentNo.substring(0,30); @@ -394,7 +270,7 @@ public class RequestEMailProcessor extends SvrProcess pstmtdup = DB.prepareStatement (sqldup, null); pstmtdup.setInt(1, getAD_Client_ID()); pstmtdup.setString(2, documentNo); - pstmtdup.setTimestamp(3, new Timestamp(msg.getSentDate().getTime())); + pstmtdup.setTimestamp(3, new Timestamp(emailContent.sentDate.getTime())); rsdup = pstmtdup.executeQuery (); if (rsdup.next ()) retValuedup = rsdup.getInt(1); @@ -408,8 +284,8 @@ public class RequestEMailProcessor extends SvrProcess rsdup = null;pstmtdup = null; } if (retValuedup > 0) { - if (log.isLoggable(Level.INFO)) log.info("request already existed for msg -> " + hdrs[0]); - return true; + if (log.isLoggable(Level.INFO)) log.info("request already existed for msg -> " + emailContent.subject); + return; } // Analyze subject if Re: find the original request by subject + e-mail and add an action @@ -441,11 +317,11 @@ public class RequestEMailProcessor extends SvrProcess pstmtupd = DB.prepareStatement (sqlupd, null); pstmtupd.setInt(1, getAD_Client_ID()); pstmtupd.setString(2, fromAddress); - pstmtupd.setString(3, msg.getSubject()); - pstmtupd.setString(4, msg.getSubject()); - pstmtupd.setString(5, msg.getSubject()); + pstmtupd.setString(3, emailContent.subject); + pstmtupd.setString(4, emailContent.subject); + pstmtupd.setString(5, emailContent.subject); pstmtupd.setString(6, fromAddress); - pstmtupd.setString(7, msg.getSubject()); + pstmtupd.setString(7, emailContent.subject); rsupd = pstmtupd.executeQuery (); if (rsupd.next ()) request_upd = rsupd.getInt(1); @@ -460,16 +336,19 @@ public class RequestEMailProcessor extends SvrProcess rsupd = null;pstmtupd = null; } if (request_upd > 0) { - if (log.isLoggable(Level.INFO)) log.info("msg -> " + hdrs[0] + " is an answer for req " + request_upd); - return updateRequest(request_upd, msg); + if (log.isLoggable(Level.INFO)) log.info("msg -> " + emailContent.subject + " is an answer for req " + request_upd); + updateRequest(request_upd, emailContent, trxName); + return; } - MRequest req = new MRequest(getCtx(), 0, get_TrxName()); + MRequest req = new MRequest(getCtx(), 0, trxName); // Subject as summary - StringBuilder msgreq = new StringBuilder("FROM: ").append(fromAddress).append("\n").append(msg.getSubject()); + StringBuilder msgreq = new StringBuilder("FROM: ").append(fromAddress).append("\n").append(emailContent.subject); req.setSummary(msgreq.toString()); // Body as result - msgreq = new StringBuilder("FROM: ") .append(from[0].toString()).append("\n").append(getMessage(msg)); + //TODO:1. improve to when email only html content, convert it to text and set here + //TODO:2. improve to add control display html at form, no need open attach to see + msgreq = new StringBuilder("FROM: ") .append(emailContent.fromAddress.get(0)).append("\n").append(emailContent.getTextContent()); req.setResult(msgreq.toString()); // Message-ID as documentNo req.setDocumentNo(documentNo); @@ -488,45 +367,43 @@ public class RequestEMailProcessor extends SvrProcess if (AD_Role_ID > 0) req.setAD_Role_ID(AD_Role_ID); - // Look for user via e-mail - if (from != null) + // Look for user via e-mail + int retValueu = -1; + String sqlu = "SELECT ad_user_id " + + " FROM ad_user " + + " WHERE UPPER (email) = UPPER (?) " + + " AND ad_client_id = ?"; + PreparedStatement pstmtu = null; + ResultSet rsu = null; + try { - int retValueu = -1; - String sqlu = "SELECT ad_user_id " - + " FROM ad_user " - + " WHERE UPPER (email) = UPPER (?) " - + " AND ad_client_id = ?"; - PreparedStatement pstmtu = null; - ResultSet rsu = null; - try - { - pstmtu = DB.prepareStatement (sqlu, null); - pstmtu.setString(1, fromAddress); - pstmtu.setInt(2, getAD_Client_ID()); - rsu = pstmtu.executeQuery (); - if (rsu.next ()) - retValueu = rsu.getInt(1); - } - catch(SQLException e) - { - throw e; - } - finally - { - DB.close (rsu,pstmtu); - rsu = null;pstmtu = null; - } - if (retValueu > 0) { - req.setAD_User_ID(retValueu); - } else { - // set default user - if (AD_User_ID > 0) - req.setAD_User_ID(AD_User_ID); - } + pstmtu = DB.prepareStatement (sqlu, null); + pstmtu.setString(1, fromAddress); + pstmtu.setInt(2, getAD_Client_ID()); + rsu = pstmtu.executeQuery (); + if (rsu.next ()) + retValueu = rsu.getInt(1); } + catch(SQLException e) + { + throw e; + } + finally + { + DB.close (rsu,pstmtu); + rsu = null;pstmtu = null; + } + if (retValueu > 0) { + req.setAD_User_ID(retValueu); + } else { + // set default user + if (AD_User_ID > 0) + req.setAD_User_ID(AD_User_ID); + } + // Look BP if (req.getAD_User_ID() > 0) { - MUser us = new MUser(getCtx(), req.getAD_User_ID(), get_TrxName()); + MUser us = new MUser(getCtx(), req.getAD_User_ID(), trxName); if (us.getC_BPartner_ID() > 0) req.setC_BPartner_ID(us.getC_BPartner_ID()); } @@ -536,7 +413,7 @@ public class RequestEMailProcessor extends SvrProcess } // Set start date as sent date of e-mail - req.setStartDate(new Timestamp(msg.getSentDate().getTime())); + req.setStartDate(new Timestamp(emailContent.sentDate.getTime())); // defaults priority Medium, confidentiality partner if (p_DefaultConfidentiality != null) { @@ -548,512 +425,45 @@ public class RequestEMailProcessor extends SvrProcess req.setPriorityUser(p_DefaultPriority); } - if (req.save(get_TrxName())) { - if (log.isLoggable(Level.INFO)) log.info("created request " + req.getR_Request_ID() + " from msg -> " + hdrs[0]); + req.saveEx(trxName); + + if (log.isLoggable(Level.INFO)) log.info("created request " + req.getR_Request_ID() + " from msg -> " + emailContent.subject); + + String htmlContent = emailContent.getHtmlContent(true); + if (htmlContent != null){ + MAttachment attach = req.createAttachment(); - - // get simple attachments and attach to request - if ( msg.isMimeType("multipart/*" ) ) - { - try { - Multipart mp = (Multipart) msg.getContent(); - - for (int i=0, n=mp.getCount(); i -1) { - out.write(buffer, 0, bytesRead); - } - in.close(); - - byte[] bytes = out.toByteArray(); - - attach.addEntry(part.getFileName(), bytes); - attach.saveEx(get_TrxName()); - } - } - } - catch (IOException e) { - if (log.isLoggable(Level.FINE))log.log(Level.FINE, "Error extracting attachments", e); - } - } - - return true; - } else { - return false; + attach.addEntry(emailContent.subject + ".html", emailContent.getHtmlContent(true).getBytes(Charset.forName("UTF-8"))); + attach.saveEx(trxName); } + + for (BodyPart attachFile : emailContent.lsAttachPart){ + MAttachment attach = req.createAttachment(); + attach.addEntry(attachFile.getFileName(), EmailSrv.getBinaryData(attachFile)); + attach.saveEx(trxName); + } + } - - protected boolean updateRequest(int request_upd, Message msg) throws MessagingException, SQLException { - MRequest requp = new MRequest(getCtx(), request_upd, get_TrxName()); + + protected void updateRequest(int request_upd, EmailContent emailContent, String trxName) throws MessagingException, SQLException { + MRequest requp = new MRequest(getCtx(), request_upd, trxName); // Body as result - Address[] from = msg.getFrom(); - StringBuilder msgreq = new StringBuilder("FROM: ").append(from[0].toString()).append("\n").append(getMessage(msg)); + + StringBuilder msgreq = new StringBuilder("FROM: ").append(emailContent.fromAddress.get(0)).append("\n").append(emailContent.getTextContent()); requp.setResult(msgreq.toString()); - return requp.save(); + requp.saveEx(trxName); } - /** - * Process Message - * @param msg message - * @return Type of Message - * @throws Exception - */ - protected int processMessage (Message msg) throws Exception - { - dumpEnvelope (msg); - dumpBody (msg); - printOut (":::::::::::::::"); - printOut (getSubject(msg)); - printOut (":::::::::::::::"); - printOut (getMessage(msg)); - printOut (":::::::::::::::"); - String delivery = getDeliveryReport(msg); - printOut (delivery); - printOut (":::::::::::::::"); - - // if (delivery != null) - // return DELIVERY; - - Address[] from; - // FROM - if ((from = msg.getFrom()) != null) - { - // send to error messages from postmaster@CONSULTDESK - // TODO: possible enhancement - put error from accounts in a table - if (from[0].toString().equalsIgnoreCase("postmaster@CONSULTDESK")) - return ERROR; - } else { - // there is no from - return ERROR; - } - - // By now this account is to process requests - return REQUEST; - - } // processMessage - - /** - * Get Subject - * @param msg message - * @return subject or "" - */ - protected String getSubject (Message msg) - { - try - { - String str = msg.getSubject(); - if (str != null) - return str.trim(); - } - catch (MessagingException e) - { - log.log(Level.SEVERE, "getSubject", e); - } - return ""; - } // getSubject - - /** - * Get Message - * @param msg Message - * @return message or "" - */ - protected String getMessage (Part msg) - { - StringBuilder sb = new StringBuilder(); - try - { - // Text - if (msg.isMimeType("text/plain")) - { - sb.append(msg.getContent()); - } - // Other Text (e.g. html/xml) - else if (msg.isMimeType("text/*")) - { - sb.append(msg.getContent()); - } - // Nested - else if (msg.isMimeType("message/rfc822")) - { - sb.append(msg.getContent()); - } - // Multi Part Alternative - else if (msg.isMimeType("multipart/alternative")) - { - String plainText = null; - String otherStuff = null; - // - Multipart mp = (Multipart)msg.getContent(); - int count = mp.getCount(); - for (int i = 0; i < count; i++) - { - Part part = mp.getBodyPart(i); - Object content = part.getContent(); - if (content == null || content.toString().trim().length() == 0) - continue; - if (part.isMimeType("text/plain")) - plainText = content.toString(); - else - otherStuff = content.toString(); - } - if (plainText != null) - sb.append(plainText); - else if (otherStuff != null) - sb.append(otherStuff); - } - // Multi Part - else if (msg.isMimeType("multipart/*")) - { - Multipart mp = (Multipart)msg.getContent(); - int count = mp.getCount(); - for (int i = 0; i < count; i++) - { - String str = getMessage(mp.getBodyPart(i)); - if (str.length() > 0) - { - if (sb.length() > 0) - sb.append("\n-----\n"); - sb.append(str); - } - } - } - else - { - /* - * If we actually want to see the data, and it's not a - * MIME type we know, fetch it and check its Java type. - */ - Object o = msg.getContent(); - if (o instanceof String) - { - sb.append(o); - } - } - } - catch (Exception e) - { - log.log(Level.SEVERE, "getMessage", e); - } - return sb.toString().trim(); - } // getMessage + @Override + public void processEmailError(EmailContent emailHeader, Message emailRaw, + Store mailStore, Folder mailFolder) throws MessagingException { + noError++; + errorFolder.appendMessages(new Message[]{emailRaw}); + } - /** - * Get Delivery Report - * @param msg message - * @return delivery info or null - */ - protected String getDeliveryReport (Part msg) - { - try - { - if (msg.isMimeType("multipart/report")) - { - String deliveryMessage = null; - String otherStuff = null; - // - Multipart mp = (Multipart)msg.getContent(); - int count = mp.getCount(); - for (int i = 0; i < count; i++) - { - Part part = mp.getBodyPart(i); - Object content = part.getContent(); - if (content == null) - continue; - if (part.isMimeType("message/*")) - deliveryMessage = getDeliveredReportDetail (part); - else - otherStuff = content.toString().trim(); - } - if (deliveryMessage != null) - return deliveryMessage; - return otherStuff; - } - else if (msg.isMimeType("message/*")) - { - return getDeliveredReportDetail (msg); - } - } - catch (Exception e) - { - log.log(Level.SEVERE, "getDeliveryReport", e); - } - // Nothing - return null; - } // getDeliveryReport - - /** - * Get Delivered Report Detail - * @param part Mime Type message/* - * @return info or null - * @throws Exception - */ - protected String getDeliveredReportDetail (Part part) throws Exception - { - Object content = part.getContent(); - if (content == null) - return null; - - String deliveryMessage = null; - if (content instanceof InputStream) - { - StringBuilder sb = new StringBuilder(); - InputStream is = (InputStream)content; - int c; - while ((c = is.read()) != -1) - sb.append((char)c); - deliveryMessage = sb.toString().trim(); - } - else - deliveryMessage = content.toString().trim(); - // - if (deliveryMessage == null) - return null; - - // Final-Recipient: RFC822; jjanke@compiere.org - int index = deliveryMessage.indexOf("Final-Recipient:"); - if (index != -1) - { - String finalRecipient = deliveryMessage.substring(index); - int atIndex = finalRecipient.indexOf("@"); - if (atIndex != -1) - { - index = finalRecipient.lastIndexOf(' ', atIndex); - if (index != -1) - finalRecipient = finalRecipient.substring(index+1); - atIndex = finalRecipient.indexOf("@"); - if (atIndex != -1) - index = finalRecipient.indexOf(' ', atIndex); - if (index != -1) - finalRecipient = finalRecipient.substring(0, index); - index = finalRecipient.indexOf('\n'); - if (index != -1) - finalRecipient = finalRecipient.substring(0, index); - return finalRecipient.trim(); - } - } - return deliveryMessage; - } // getDeliveredReportDetail - - - /************************************************************************** - * Print Envelope - * @param m message - * @throws Exception - */ - protected void dumpEnvelope(Message m) throws Exception - { - printOut("-----------------------------------------------------------------"); - Address[] a; - StringBuilder msgout = new StringBuilder(); - // FROM - if ((a = m.getFrom()) != null) - { - for (int j = 0; j < a.length; j++){ - msgout = new StringBuilder("FROM: ").append(a[j].toString()); - printOut(msgout.toString()); - } - } - - // TO - if ((a = m.getRecipients(Message.RecipientType.TO)) != null) - { - for (int j = 0; j < a.length; j++){ - msgout = new StringBuilder("TO: ").append(a[j].toString()); - printOut(msgout.toString()); - } - } - - // SUBJECT - msgout = new StringBuilder("SUBJECT: ").append(m.getSubject()); - printOut(msgout.toString()); - - // DATE - java.util.Date d = m.getSentDate(); - msgout = new StringBuilder("SendDate: ").append((d != null ? d.toString() : "UNKNOWN")); - printOut(msgout.toString()); - - // FLAGS - Flags flags = m.getFlags(); - StringBuilder sb = new StringBuilder(); - Flags.Flag[] sf = flags.getSystemFlags(); // get the system flags - - boolean first = true; - for (int i = 0; i < sf.length; i++) - { - String s; - Flags.Flag f = sf[i]; - if (f == Flags.Flag.ANSWERED) - s = "\\Answered"; - else if (f == Flags.Flag.DELETED) - s = "\\Deleted"; - else if (f == Flags.Flag.DRAFT) - s = "\\Draft"; - else if (f == Flags.Flag.FLAGGED) - s = "\\Flagged"; - else if (f == Flags.Flag.RECENT) - s = "\\Recent"; - else if (f == Flags.Flag.SEEN) - s = "\\Seen"; - else - continue; // skip it - if (first) - first = false; - else - sb.append(' '); - sb.append(s); - } - - String[] uf = flags.getUserFlags(); // get the user flag strings - for (int i = 0; i < uf.length; i++) - { - if (first) - first = false; - else - sb.append(' '); - sb.append(uf[i]); - } - msgout = new StringBuilder("FLAGS: ").append(sb.toString()); - printOut(msgout.toString()); - - // X-MAILER - String[] hdrs = m.getHeader("X-Mailer"); - if (hdrs != null) - { - StringBuilder sb1 = new StringBuilder("X-Mailer: "); - for (int i = 0; i < hdrs.length; i++) - sb1.append(hdrs[i]).append(" "); - printOut(sb1.toString()); - } - else - printOut("X-Mailer NOT available"); - - // Message ID - hdrs = m.getHeader("Message-ID"); - if (hdrs != null) - { - StringBuilder sb1 = new StringBuilder("Message-ID: "); - for (int i = 0; i < hdrs.length; i++) - sb1.append(hdrs[i]).append(" "); - printOut(sb1.toString()); - } - else - printOut("Message-ID NOT available"); - - // All - printOut("ALL HEADERs:"); - Enumeration en = m.getAllHeaders(); - while (en.hasMoreElements()) - { - Header hdr = (Header)en.nextElement(); - msgout = new StringBuilder(" ").append(hdr.getName()).append(" = ").append(hdr.getValue()); - printOut (msgout.toString()); - } - - - printOut("-----------------------------------------------------------------"); - } // printEnvelope - - /** - * Print Body - * @param p - * @throws Exception - */ - protected void dumpBody (Part p) throws Exception - { - // http://www.iana.org/assignments/media-types/ - printOut("================================================================="); - StringBuilder msgout = new StringBuilder("CONTENT-TYPE: ").append(p.getContentType()); - printOut(msgout.toString()); - /** - Enumeration en = p.getAllHeaders(); - while (en.hasMoreElements()) - { - Header hdr = (Header)en.nextElement(); - printOut (" " + hdr.getName() + " = " + hdr.getValue()); - } - printOut("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ="); - /** **/ - - /** - * Using isMimeType to determine the content type avoids - * fetching the actual content data until we need it. - */ - if (p.isMimeType("text/plain")) - { - printOut("Plain text ---------------------------"); - printOut((String)p.getContent()); - } - else if (p.getContentType().toUpperCase().startsWith("TEXT")) - { - printOut("Other text ---------------------------"); - printOut((String)p.getContent()); - } - else if (p.isMimeType("multipart/*")) - { - printOut("Multipart ---------------------------"); - Multipart mp = (Multipart)p.getContent(); - int count = mp.getCount(); - for (int i = 0; i < count; i++) - dumpBody(mp.getBodyPart(i)); - } - else if (p.isMimeType("message/rfc822")) - { - printOut("Nested ---------------------------"); - dumpBody((Part)p.getContent()); - } - else - { - /* - * If we actually want to see the data, and it's not a - * MIME type we know, fetch it and check its Java type. - */ - Object o = p.getContent(); - if (o instanceof String) - { - printOut("This is a string ---------------------------"); - printOut((String)o); - } - else if (o instanceof InputStream) - { - printOut("This is just an input stream ---------------------------"); - // TODO: process attachment - // InputStream is = (InputStream)o; - // int c; - // while ((c = is.read()) != -1) - // System.out.write(c); // must be log. ?? - } - else - { - printOut("This is an unknown type ---------------------------"); - printOut(o.toString()); - } - } - printOut("================================================================="); - } // printBody - - /** - * Print - * @param s string - */ - protected void printOut(String s) - { - // System.out.print(indentStr.substring(0, level * 2)); - // System.out.println(s); - log.finer(s); - } + @Override + public List getListFolder() { + return lsFolderProcess; + } } // RequestEMailProcessor diff --git a/org.adempiere.base/src/org/compiere/util/EmailSrv.java b/org.adempiere.base/src/org/compiere/util/EmailSrv.java new file mode 100644 index 0000000000..a1c57ce1e9 --- /dev/null +++ b/org.adempiere.base/src/org/compiere/util/EmailSrv.java @@ -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 lsPart = new ArrayList (); + // list image name in sign + List lsImgSrc = new ArrayList (); + + // 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 def ghi + // lsPart will include "acb def 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 , 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 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 fromAddress = new ArrayList(); + + /** + * 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 lsAttachPart = new ArrayList(); + /** + * list embed file + */ + public List lsEmbedPart = new ArrayList(); + /** + * list part unknow to process + */ + public List lsUnknowPart = new ArrayList(); + + /** + * 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(); + } + } + +} \ No newline at end of file diff --git a/org.adempiere.server-feature/idempiere-server.bat b/org.adempiere.server-feature/idempiere-server.bat index 93a61dde49..4f7a3680f9 100644 --- a/org.adempiere.server-feature/idempiere-server.bat +++ b/org.adempiere.server-feature/idempiere-server.bat @@ -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 diff --git a/org.adempiere.server-feature/idempiere-server.sh b/org.adempiere.server-feature/idempiere-server.sh index 805ebfb585..1308a078a2 100644 --- a/org.adempiere.server-feature/idempiere-server.sh +++ b/org.adempiere.server-feature/idempiere-server.sh @@ -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 diff --git a/org.adempiere.server-feature/server.product b/org.adempiere.server-feature/server.product index dbe0e91935..ad011e8ac9 100644 --- a/org.adempiere.server-feature/server.product +++ b/org.adempiere.server-feature/server.product @@ -10,7 +10,7 @@ --launcher.XXMaxPermSize 192m -console 12612 --launcher.XXMaxPermSize 192m - -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 + -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 -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 -XstartOnFirstThread -Dorg.eclipse.swt.internal.carbon.smallFonts diff --git a/org.adempiere.server-feature/server.product.launch b/org.adempiere.server-feature/server.product.launch index 3053589277..acef0a4766 100644 --- a/org.adempiere.server-feature/server.product.launch +++ b/org.adempiere.server-feature/server.product.launch @@ -19,7 +19,7 @@ - +