2014年8月22日 星期五

JAVA POP3 Server 信件內文中斷符號問題修正


接續著上一篇 JAVA POP3 Server 實作 後,
這一篇主要是修正一個很奇怪的問題。


在使用Thunderbird測試一段時間後發現一個很奇怪的問題
只要跑一段時間就會出現 "RETR 命令不成功,取回郵件時發生錯誤"

在Outlook下測試卻不會,但會有很奇怪的空白信件,
空白信件的大小甚至可以到200M,這太不尋常了

反復測試 bug track後,
發現應該是信件內有 RETR cammond 的中斷符號造成。

至於兩個 Client 對於此問題的出現不一樣的結果,
我只能說,各家對於問題處裡的方式不同。

Thunderbird遇到問題就停止收信。
Outlook則是繼續收,但有問題的信跟後續的幾封信,都會變成空白信件。

之後有試著自己處理output時遇到中斷點問題,
但後來參考了 Apache James 的 POP3Handler Source Code ,
覺得寫的比較好,就拿來用了

看程式吧~
=============================================================
為了使用 FilterOutputStream 所以要加上 BufferedOutputStream

修改前
out = new PrintWriter(mySocket.getOutputStream(), AUTOFLUSH);

修改後
outs = new BufferedOutputStream(mySocket.getOutputStream(), 1024);
out = new PrintWriter(outs, AUTOFLUSH);

=============================================================
printMail 修改,使用 Apache James 的 ExtraDotOutputStream 來處理內文斷點

修改前
private void printMail(Integer index) {
String str = "";
GridFSDBFile gfsdbf = glist.get(index - 1);
InputStream inputS = null;
BufferedReader reader = null;
try {
inputS = gfsdbf.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputS));

while ((str = reader.readLine()) != null) {
out.println(str);
}

} catch (Exception e) {
log.error(ExceptionUtils.getStackTrace(e));
} finally {
try {
if (inputS != null)
inputS.close();
if (reader != null)
reader.close();
} catch (Throwable ignore) {
}
}
}

修改後
private void printMail(Integer index) {
GridFSDBFile gfsdbf = glist.get(index - 1);
InputStream inputS = null;
try {
inputS = gfsdbf.getInputStream();

ExtraDotOutputStream edouts =
                    new ExtraDotOutputStream(outs);

int numRead;
byte[] buf = new byte[8192];
while ((numRead = inputS.read(buf)) >= 0) {
edouts.write(buf, 0, numRead);
}

edouts.checkCRLFTerminator();
            edouts.flush();
           
} catch (Exception e) {
out.println("-ERR");
log.error(ExceptionUtils.getStackTrace(e));
} finally {
try {
if (inputS != null)
inputS.close();
} catch (Throwable ignore) {
}
}
}


至於 ExtraDotOutputStream 這隻程式我就不附上了,
有興趣的可以去下載 Source Code 來看

2014年8月19日 星期二

JAVA POP3 Server 實作

※2014/09/01 JAVA POP3 Server 信件內文中斷符號問題修正

這篇可能有點長,主要是利用Java ServerSocket實作POP3 Server

當然免不了要先看一下什麼是 POP3 

接著就必須要瞭解 RFC 規範
基本上要看的就是 RFC1939RFC2449 這兩份文件

RFC1939
第三點 Basic Operation (基本操作),這是必看的,裡面有講那很多規定
例如:POP3 service by listening on TCP port 110.  Commands 不分大小寫. 參數長度不能超過40字元. 換行符號必須是CRLF.....等等

POP3 session 的三個狀態在基本操作也有說明
AUTHORIZATION State
TRANSACTION State
UPDATE State

接著就是必須實作的幾個指令
USER
PASS
STAT
LIST
RETR
DELE
NOOP
RSET
QUIT

POP Client Commands 的範例表


接下來就看程式吧!!

public class POP3Server extends Thread {
private static final Logger log = Logger.getLogger(POP3Server.class);
private static final int SERVER_PORT = 110;
private static final int MAX_CLIENT = 500;
private ServerSocket listenSocket = null;

public static void main(String[] args) {
POP3Server server = new POP3Server();
server.start();
}

public POP3Server() {
try {
listenSocket = new ServerSocket(SERVER_PORT, MAX_CLIENT);
} catch (IOException excpt) {
log.error("Sorry to open port " + SERVER_PORT + ":" + excpt);
System.exit(1);
}
}

public void run() {
Socket clientSocket = null;
try {
while (true) {
clientSocket = listenSocket.accept();
int numThreads = Thread.activeCount();
log.info(numThreads);
manageconnection newhandle = new manageconnection(clientSocket);
Thread newhandlethread = new Thread(newhandle);
newhandlethread.start();
log.info("hello");

}
} catch (IOException excpt) {
log.error("Sorry ,Failed I/O:" + excpt);
}
}
}

/* follow is manage and process command class */

class manageconnection implements Runnable {
private static final Logger log = Logger.getLogger(manageconnection.class);
private static final boolean AUTOFLUSH = true;
private Socket mySocket = null;
private PrintWriter out = null;
private BufferedReader in = null;
private long totalsize = 0;
private String username = null;
private List> mList;
private Set delMark = new HashSet();

public manageconnection(Socket newSocket) {
mySocket = newSocket;
}

public void run() {
String nextline = null;
int check = 0;
String command = null;
String arg1;
try {
Thread.currentThread();
Thread.sleep(10);
} catch (Exception e) {
log.error(e);
}
try {
mySocket.setTcpNoDelay(true);
} catch (SocketException excpt) {
log.error(excpt);
}
try {
System.setProperty("line.separator", "\r\n");
out = new PrintWriter(mySocket.getOutputStream(), AUTOFLUSH);
in = new BufferedReader(new InputStreamReader(
mySocket.getInputStream())

);
out.println("+OK" + " PoWei's pop3 server");
try {
while (true) {
if (check != 3) {
try {
nextline = in.readLine();
} catch (Exception excpt) {
log.error("sorry:" + excpt);
break;
}
log.info("C: " + nextline);

if (nextline.length() < 4) {
out.println("-ERR");
} else {
command = nextline.substring(0, 4);

switch (command.toUpperCase()) {
case "CAPA":
out.println("+OK Capability list follows");
out.println("USER");
out.println("PASS");
out.println("STAT");
out.println("QUIT");
out.println("LIST");
out.println("RETR");
out.println("DELE");
out.println("UIDL");
out.println("NOOP");
// out.println("TOP");
out.println(".");
break;
case "USER":
if (check == 0) {
check = 1;
if (nextline.length() < 5) {
out.println("-ERR");
} else {
arg1 = nextline.substring(5);
username = arg1;
out.println("+OK Password required for "
+ username);
}
} else {
out.println("-ERR");
}
break;
case "PASS":
if (check == 1) {
if (nextline.length() < 5) {
out.println("-ERR");
} else {
arg1 = nextline.substring(5);
if (check(username, arg1)) {
check = 2;
mList = readmail(username);
out.println("+OK " + username
+ " has " + mList.size()
+ " messages");
} else {
out.println("-ERR sorry auth failed");
check = 0;
}
}
} else {
out.println("-ERR");
}
break;
case "STAT":
if (check == 2) {
mList = readmail(username);
out.println("+OK" + " " + mList.size()
+ " " + totalsize);
} else {
out.println("-ERR");
}
break;
case "QUIT":
out.println("+OK BYE BYE welcome to PoWei's Mail System next time");
if (check == 2) {
if (!deleteMail(delMark)) {
out.println("-ERR some deleted messages not removed");
}
}
check = 3;
break;
case "LIST":
if (check == 2) {
out.print("+OK ");
out.println(mList.size() + " messages ("
+ totalsize + " byte)");
for (int j = 1; j <= mList.size(); j++) {
out.println(j + " "
+ mList.get(j - 1).get("size"));
}
out.println(".");
} else {
out.println("-ERR");
}
break;
case "RETR":
if (check == 2) {
if (nextline.length() < 5) {
out.println("-ERR");
} else {
arg1 = nextline.substring(5);
out.println("+OK");
log.info(arg1);
printMail(Integer.valueOf(arg1));
out.println(".");
boolean st = out.checkError();
log.info(st);
}
} else {
out.println("-ERR");
}
log.info("retr finished");
break;
case "DELE":
if (check == 2) {
if (nextline.length() < 5) {
out.println("-ERR");
} else {
arg1 = nextline.substring(5);
try {
Integer index = Integer
.valueOf(arg1) - 1;
if (!delMark.contains(index)) {
delMark.add(index);
out.println("+OK message "
+ arg1 + " deleted");
} else {
out.println("+ERR message "
+ arg1
+ " already deleted");
}

} catch (Exception e) {
out.println("-ERR");
}
}
} else {
out.println("-ERR");
}
break;
case "UIDL":
if (check == 2) {
if (nextline.length() < 5) {
out.println("+OK");
for (int j = 1; j <= mList.size(); j++) {
out.println(j
+ " "
+ mList.get(j - 1).get(
"uid"));
}
out.println(".");
} else {
Integer index = null;
try {
index = Integer.valueOf(nextline
.substring(5));
out.print("+OK ");
out.println(index
+ " "
+ mList.get(index - 1).get(
"uid"));
out.println(".");
} catch (IndexOutOfBoundsException e) {
out.println("-ERR IndexOutOfBoundsException:"
+ index);
} catch (NumberFormatException e) {
out.println("-ERR " + index);
}
}
} else {
out.println("-ERR");
}
break;
case "NOOP":
if (check == 2) {
out.println("+OK");
}
break;
case "TOP":
if (check == 2) {
out.println("+OK");
} else {
out.println("-ERR");
}
break;
default:
out.println("-ERR");
break;

}
}
} else {
out.close();
in.close();
mySocket.close();
break;
}

}
} catch (NullPointerException excpt) {
log.error("sorry " + excpt);
in.close();
out.close();
mySocket.close();
}

} catch (IOException excpt) {
log.error("Failed I/O:" + excpt);
}

log.info("bye");

}

private boolean deleteMail(Set delMark) {
boolean value = true;

for (Integer index : delMark) {
try {
Map map = mList.get(index);
if (map != null) {
//TODO remove mail
}

} catch (Exception e) {
value = false;
}
}

return value;
}

private void printMail(Integer index) {
String str = "";
GridFSDBFile gfsdbf = glist.get(index - 1);
InputStream inputS = null;
BufferedReader reader = null;
try {
inputS = gfsdbf.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputS));

while ((str = reader.readLine()) != null) {
out.println(str);
}

} catch (Exception e) {
log.error(ExceptionUtils.getStackTrace(e));
} finally {
try {
if (inputS != null)
inputS.close();
if (reader != null)
reader.close();
} catch (Throwable ignore) {
}
}
}

/* check your username and password */
private boolean check(String username, String password) {
//TODO auth

return false;
}

private List> readmail(String uname) {
//TODO load mail

List> ret = new ArrayList>();

return ret;
}

}

2014年7月30日 星期三

Java Regular Expression (URL To Html A Tag)

※這一篇是為了解決特定用途,非URL Regular expression的介紹

需求:將輸入字串 convert URL to Html A Tag,類似Gmail寄信,會自動將URL轉換為Hyperlink

剛開始先找到了這一篇 URL Regular expression
拿到裡面的 Pattern 之後,
我利用 JavaWorld 找到的測試程式進行測試做了些小修改

修改前 https?:\/\/[\w-\.]+(:\d+)?(\/[\w\/\.]*)?(\?\S*)?(#\S*)?
修改後 https?:\/\/[\w-\.]+(:\d+)?\/?([\w\/\.]*)?(\?[\w=&;]*)?(#\w*)?

主要是將 query string 與 hash 的部分改為不允許中文字
避免連結後接續著中文字會被截取
例如:http://aaa.bbb.cc/foo.html?q=bar豬八戒


接著,為了避免取代原本就已是A Tag Href 屬性內的URL,造成重複
例如:<a href="http://aaa.bbb.cc/">aaa</a>

所以在 Pattern 最前面加上判斷非等於(=)、雙引號(")、單引號(')字元的判斷,如下
([^(=\"')])https?:\/\/[\w-\.]+(:\d+)?\/?([\w\/\.-]*)?(\?[\w=&;\.-]*)?(#\w*)?

但是這樣會造成 Matcher group 0 在取得完整的比對結果時,最前面多出一個字元
不過目前沒有想到其他辦法可以直接排除掉,只好留待取代字串時處理
所以加上了小括號(),讓他成為 group 1


好~有了 Pattern ,接下來就直接看程式吧!!  (其實是懶得解說)

private static String replaceUrlToHtml_A_Tag(String patternStr, String inputStr) {
Pattern pattern = Pattern.compile(patternStr);
Matcher matcher = pattern.matcher(inputStr);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
String groupStr = matcher.group(0).trim();
String prefix = matcher.group(1);
if(StringUtils.isNotBlank(prefix)) groupStr = groupStr.replace(prefix, "");
String format = "%s<a href=\"%s\">%s</a>";
String replacement = String.format(format, prefix, groupStr, groupStr);
matcher.appendReplacement(sb, replacement);
}
matcher.appendTail(sb);
return sb.toString();
}



以下是我的測試文件
http://aaa.bbb.cc
https://aaa.bbb.cc
aaa http://aaa.bbb.cc
rrrhttp://aaa.bbb.cc
http://aaa.bbb.cc/ bbb
http://aaa.bbb.cc 八戒
http://regexr.com:8080/foo.html abc
http://regexr.com:8080/foo.html?q=bar&amp;abc=123 abc
http://regexr.com:8080/foo.html?q=bar&amp;abc=123#aaa abc
http://regexr.com:8080/#123 abc
www.demo.com
<a href="http://aaa.bbb.cc/">123</a>
<a href="http://aaa.bbb.cc/">aaa</a>
<a href="http://aaa.bbb.cc/">abc</a>
<span style="font-family: tahoma, arial, verdana, sans-serif;">http://regexr.com:8080/foo.html?q=bar&abc=123 abc</span>


其他參考文獻
http://www.regexr.com/ 
http://www.ewdna.com/2011/09/online-regular-expression-tester.html

2014年7月1日 星期二

模擬多執行續同時執行進行測試

/**
 * @author powei.chen
 * 模擬多執行續同時執行進行測試
 */
public class MultiThread {

long startTime;

public static void main(String[] args) {
new MultiThread();
}

public MultiThread() {
startTime = new Date().getTime() + 5000;
for (int i = 0; i < 5; i++) {
Test test = new Test(i);
test.start();
}
}

class Test extends Thread {
private int no;

public Test(int no) {
this.no = no;
try {
Thread.sleep(500);// 此處表示new是需要時間的
} catch (Exception e) {
e.printStackTrace();
}
}

public void run() {
try {
Thread.sleep(startTime - new Date().getTime());// 以此方式達到同步
//TODO 執行測試
System.out.println(no + " : " + new Date());
} catch (Exception e) {
e.printStackTrace();
}
}
}

}

2014年6月11日 星期三

Java Hibernate 關聯映射 inverse & cascade


有關於 Hibernate 比較有脈絡的介紹,可以參考這邊

用了 Hibernate 好幾年,
目前也只用到 基本配置、資料查詢 (Criteria, HQL, SQL)、基本的Object-Relational Mapping

以前很少,也很懶得去設定 RDBMS 的關聯
都只是用 Single Table 的 CRUD
對於需要關聯的 Table 就用程式做掉

所以為什麼說只用到基本的ORM
因為頂多只會配置繼承映射
也因為如此~
一直沒有去研究映射的屬性

最近剛好做到一個功能,想說試試看多對多關聯
順便也研究一下其他一對多、多對一、一對一的配置方式

基本上這些關聯的用法與使用時機,就不在這裏多做介紹
因為我認為這些可不是三言兩語可以瞭解的
有興趣的朋友可以回去翻大學時的 資料庫管理系統

=~=~=~=~=~=~=~=~=~=~=~=~=主題分隔線=~=~=~=~=~=~=~=~=~=~=~=~=

呼~終於進入主題了

先來說一下這篇主要介紹的兩個屬性 inverse & cascade
請不用浪費時間將它們貼去 google 翻譯
因為只看文字本身的意思根本很難理解他們的用途

inverse (true/false, default false),負責控制關係,默認為false,也就是關係的兩端都能控制
這個屬性是在雙向關聯時使用,如果設定為 true,則控制權為對方

例如:
A, B 物件為雙向多對多關聯,此時A的inverse設定為true,則控制權為B

cascde (default none/save-update/delete),設定關聯持久化對象的操作層級
比如我刪除一個對象,那麼跟它是多對一關係的對像也全部被刪除。


詳述請看此

兩個屬性的比較與舉例可以看此


本來想說自己舉幾個例子跟設定的
但真的很懶,有空再補上吧~


http://www.mkyong.com/hibernate/hibernate-cascade-example-save-update-delete-and-delete-orphan/
http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html/

2014年5月16日 星期五

Java 泛型 interface

這篇要介紹的是 interface 使用泛型

至於泛型的定義,這裡有詳細的介紹
http://openhome.cc/Gossip/Java/
http://my.oschina.net/roockee/blog/165378

好~進入主題
前陣子工作上,在寫郵件代收程式
遇上了 POP3, IMAP 的 UID (String, long) 型別不同問題
(有興趣的人可以自己去研究一下這兩個 Protocol)

又因為兩個收信方式的處理有些微的不同,所以使用 Interface 的方式處理


首先,我定義一個 MailReceiver 的 Interface

public interface MailReceiver<T>{

public void init(Folder inbox);

public void doSomething(T uid);

public T getUID(Message msg) throws MessagingException;
}


接著就分別 Implement POP3, IMAP

public class POP3 implements MailReceiver<String> {

private POP3Folder pop3Inbox;

@Override
public String getUID(Message msg) throws MessagingException {
return pop3Inbox.getUID(msg);
}

@Override
public void init(Folder inbox) {
pop3Inbox = (POP3Folder) inbox;
}

@Override
public void doSomething(String uid) {
// TODO Auto-generated method stub

}

}


public class IMAP implements MailReceiver<Long> {

private IMAPFolder imapInbox;

@Override
public Long getUID(Message msg) throws MessagingException {
return imapInbox.getUID(msg);
}

@Override
public void init(Folder inbox) {
imapInbox = (IMAPFolder) inbox;
}

@Override
public void doSomething(Long uid) {
// TODO Auto-generated method stub

}

}


OK, 依照上面的範例
可以看到我不用在像 inbox 變數一樣,要做型別 cast
也避免了會有 ClassCastException 的問題

所以,同樣寫法,我也可以將 inbox 以泛型的寫法改寫,如下

public interface MailReceiver<F,T> {

public void init(F inbox);

public void doSomething(T uid);

public T getUID(Message msg) throws MessagingException;
}


public class IMAP implements MailReceiver<IMAPFolder, Long> {

private IMAPFolder imapInbox;

@Override
public Long getUID(Message msg) throws MessagingException {
return imapInbox.getUID(msg);
}

@Override
public void init(Folder inbox) {
imapInbox =  inbox;
}

@Override
public void doSomething(Long uid) {
// TODO Auto-generated method stub

}

}


2014年4月22日 星期二

JAVA 利用 JNDI 做 LDAP 登入認證

其實要用JNDI做AD認證很簡單,只需要下面這樣


Hashtable env = new Hashtable ();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "LDAP://<LDAP Host>:<port>");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, email);
env.put(Context.SECURITY_CREDENTIALS, password);
new InitialLdapContext(env);


但這篇要寫的是針對LDAP認證,因為偉大的MIS不會設定LDAP使用自訂的認證機制
所以認證的帳號我只能使用DN,不能用Email...


好~進入正題

基本是在系統上,使用者登入絕對是打 Email,
絕對不可能要求使用者去記得那一長串又臭又長的 DN
所以程式要做的事就很明瞭了,就是將 Email 轉成 DN
其實也就是先到 LDAP 下用 Mail 這個屬性找到該名使用者
拿到 SearchResult 以後 getName


首先,還是要先 init ,連線到 LDAP
跟上面AD認證不一樣的是,
這次不用先給SECURITY_PRINCIPAL跟SECURITY_CREDENTIALS


       private String BASEDN = "dc=example,dc=com";
       private void initialData() throws NamingException {
Hashtable env = new Hashtable ();
env.put(Context.INITIAL_CONTEXT_FACTORY, FACTORY);
env.put(Context.PROVIDER_URL, "LDAP://<LDAP Host>:<port>/");
env.put(Context.SECURITY_AUTHENTICATION, "simple");

ldapContext = new InitialLdapContext(env, connCtls);
}



接著這篇最重要的 Email 轉成 DN


private String getUserDN(String email) {
String userDN = null;

// Create the search controls
SearchControls searchCtls = new SearchControls();

// Specify the search scope
searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);

// specify the LDAP search filter
// String searchFilter = "(&(objectClass=*))";
String searchFilter = "(&(ObjectClass=*)(mail=" + email + "))";

// Search for objects using the filter
NamingEnumeration answer;
try {
answer = ldapContext.search(BASEDN, searchFilter, searchCtls);

if (answer != null) {
// Loop through the search results
while (answer.hasMoreElements()) {
SearchResult sr = (SearchResult) answer.next();
userDN = "";
userDN += sr.getName();
userDN += "," + BASEDN;
}
}

} catch (Exception e) {
}

return userDN;
}



主要的登入程式,沒什麼好說明的
就是設定完帳密,重新連線


public String auth(String email, String password) {
String userDN = getUserDN(email);

if(userDN==null) return "USERNAME_NOT_EXSIT";
try {
ldapContext.addToEnvironment(Context.SECURITY_PRINCIPAL, userDN);
ldapContext.addToEnvironment(Context.SECURITY_CREDENTIALS, password);
ldapContext.reconnect(connCtls);
System.out.println(userDN + " is authenticated");
return "SUCCESS";
} catch (AuthenticationException e) {
return "AUTH_FALED";
} catch (NamingException e) {
return "AUTH_FALED";
}
}


2014年4月1日 星期二

JavaMail IMAP Exchange Server 問題

stackoverflow 這裡有一篇跟我遇到同樣的問題,解法也一樣
http://stackoverflow.com/questions/10442877/java-mail-to-exchange-server-no-login-methods-supported

需求描述:
簡單來說,我需要跟 MS  Exchange Server 測試 IMAP 連線,並做後續 Mail 處理


問題 1. 最初的連線設定與 Exception:
TestIMAP.java
Properties props = new Properties();
session = Session.getDefaultInstance(props, null);
session.setDebug(true);
store = session.getStore("imap");
store.connect("host","user","password");


Exception:
DEBUG: setDebug: JavaMail version 1.4.5
DEBUG: getProvider() returning javax.mail.Provider[STORE,imap,com.sun.mail.imap.IMAPStore,Sun Microsystems, Inc]
DEBUG: mail.imap.fetchsize: 16384
DEBUG: mail.imap.statuscachetimeout: 1000
DEBUG: mail.imap.appendbuffersize: -1
DEBUG: mail.imap.minidletime: 10
DEBUG: trying to connect to host "myHost", port 143, isSSL false
* OK The Microsoft Exchange IMAP4 service is ready.
A0 CAPABILITY
* CAPABILITY IMAP4 IMAP4rev1 LOGINDISABLED STARTTLS UIDPLUS CHILDREN IDLE NAMESPACE LITERAL+
A0 OK CAPABILITY completed.
DEBUG: protocolConnect login, host=myHost, user=myUser, password=
javax.mail.MessagingException: No login methods supported!;


根據此文章的答案,他也是參考自這裡
原因:
看這行 * CAPABILITY IMAP4 IMAP4rev1 LOGINDISABLED
Exchange Server 已回報 登入禁止

解決方法:
1. 升級JavaMail至1.4.5
2. 加上這行 props.put("mail.imap.starttls.enable", "true");


這樣就正常了嗎?? 並沒有~~


問題 2.:
Exception:
以上省略,跟問題1相同
A1 STARTTLS
A1 OK Begin TLS negotiation now.
DEBUG IMAP: STARTTLS Exception: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
messaging exception
javax.mail.MessagingException: STARTTLS failure;



這個問題就要參考這裡
原因:
我懶的翻譯了,就直接把重點貼出來吧~~XDD
MS Exchange SSL connection is not established properly by Java Mail API

解決方法:
其實我也沒很懂這個解法以及為什麼要這樣寫
猜測是去繼承覆寫掉 SSLSocketFactory 的 Method
在建構子寫一個空的信任管理物件去建立TLS SSLSocketFactory

public class ExchangeSSLSocketFactory extends SSLSocketFactory {

private SSLSocketFactory sslSocketFactory;
private SocketFactory socketFactory;

public ExchangeSSLSocketFactory() {
try {
socketFactory = SocketFactory.getDefault();

SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new TrustManager[] { new EmptyTrustManager() },
null);
sslSocketFactory = (SSLSocketFactory) context.getSocketFactory();
} catch (Exception e) {
throw new RuntimeException(e);
}
}

private final class EmptyTrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] cert, String authType)
throws CertificateException {
}

public void checkServerTrusted(X509Certificate[] cert, String authType)
throws CertificateException {
}

public X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[0];
}
}

public static SocketFactory getDefault() {
return new ExchangeSSLSocketFactory();
}

@Override
public Socket createSocket(Socket socket, String s, int i, boolean flag)
throws IOException {
return sslSocketFactory.createSocket(socket, s, i, flag);
}

@Override
public Socket createSocket(InetAddress inaddr, int i, InetAddress inaddr1,
int j) throws IOException {
return socketFactory.createSocket(inaddr, i, inaddr1, j);
}

@Override
public Socket createSocket(InetAddress inaddr, int i) throws IOException {
return socketFactory.createSocket(inaddr, i);
}

@Override
public Socket createSocket(String s, int i, InetAddress inaddr, int j)
throws IOException {
return socketFactory.createSocket(s, i, inaddr, j);
}

@Override
public Socket createSocket(String s, int i) throws IOException {
return socketFactory.createSocket(s, i);
}

@Override
public Socket createSocket() throws IOException {
return socketFactory.createSocket();
}

@Override
public String[] getDefaultCipherSuites() {
return sslSocketFactory.getSupportedCipherSuites();
}

@Override
public String[] getSupportedCipherSuites() {
return sslSocketFactory.getSupportedCipherSuites();
}

}

加上這兩個 Properties
props.setProperty("ssl.SocketFactory.provider", "your.package.name.ExchangeSSLSocketFactory");
props.setProperty("mail.imap.socketFactory.class", "your.package.name.ExchangeSSLSocketFactory");



OK, 打完收工

其實中間還有嘗試並看很多其他的解法,也有試著使用SSL imaps
但都無法成功,老實講 IMAP 的確比 POP3 麻煩與複雜多了
難怪現在還是一堆人只寫 POP3

其他參考資料
http://alvinalexander.com/blog/post/java/i-need-get-all-of-my-email-addresses-out-of-imap-mailbox
https://javamail.java.net/nonav/docs/api/

2014年3月28日 星期五

Linux - Crontab - 手動正常排程失敗

重點應該在Crontab執行SH檔案跟直接使用root執行還是有些許差異,
在於【系統環境】的配置不同,找了許多文章都教導:
 1.使用絕對路徑。
   2.使用前先執行/etc/profile系統配置檔案。
而我遇到的問題是Tomcat引入的環境變數編碼不同,造成原本正常的中文字都變亂碼
手動執行引入的編碼是UTF-8
排程執行引入的編碼卻是ANSI_X3.4-1968

所以解決方式就是在SH檔案的開頭加入。
#!/bin/ksh
source /etc/profile


2014年3月14日 星期五

Shell Script 指令置換 (命令替換)

` 反引號、 $(command) 皆可以用來做指令置換 (命令替換),將反引號、 $(command) 中的字符串做為命令來執行,我們在用shell編程時經常用的到。將系統命令的執行結果賦給一個變量。

A=`date`
echo $A 顯示的不是date而是當時的時間串
比如有一文件A的內容如下
ABCDEFG
1234456
abcdefg

B=`cat A|grep 234` # 檢索文件A中含有字符串234的行
echo $B 將顯示為1234456
echo "$B" 將顯示為什麼?
echo "\$B" 將顯示為什麼?


String contains in Shell Script

string='My string';

if [[ $string == *My* ]]
then
  echo "It's there!";
fi

Linux 運用 date 指令取得日期時間


1. 取得今天的日期時間
date '+%Y%m%d%H%M%S'

2. 取得昨天的日期時間
date -d'-1 day' 或 date -d'1 day ago'

3. 取得明天的日期
date -d'1 day'

4. 取得上個月的日期
date -d'-1 month'

5. 取得上星期的日期
date -d'-1 week'



$ date --date="now"   // 現在時間

$ date --date="yesterday"  // 昨天

$ date --date="1 days ago"  // 昨天

$ date --date="3 days ago"  // 三天前

$ date --date="tomorrow" // 明天

$ date --date="1 days" // 明天

$ date --date="3 days" // 三天後

 

格式化輸出:

date [+format]

$ date +"%Y %m-%d, %H:%M"

2014年3月6日 星期四

Java Applet 開啟本地資料夾

Java Applet 簡介與範例可以參照 Wiki
http://zh.wikipedia.org/wiki/Java_applet

import java.applet.Applet;
import java.io.File;
import java.io.IOException;

public class OpenExplorer extends Applet {
/**

*/
private static final long serialVersionUID = 1L;

public void init() {
        //Execute a job on the event-dispatching thread; creating this applet's GUI.
String url = getParameter("filepath").replace("file:", "").replaceAll("\\\\|/", "\\"+System.getProperty("file.separator"));
System.out.println(getParameter("filepath"));
File f = new File(url);
String command = "";
if(!f.isDirectory()) command = "/select,";

        try {
Process p = new ProcessBuilder("explorer.exe", command + url).start();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
        
    }

}