2007/10/8

秒速5公分 - 新海誠

對新海誠認識的上一步作品是「雲之彼端,約定的地方」,「秒速5公分」同樣也是一部音樂與唯美畫面完美結合的動畫電影。

多數的評價,是認為故事平庸無奇,但畫面與音樂的處理,讓這部動畫依然能站穩一席之地,的確如此,我看到了一個莫名其妙的憂鬱少年,慢慢地長大後,還是一樣憂鬱,姑且不論這樣子從頭到尾的憂鬱合不合理,但這樣的少年,確實也讓我想起自己有些時候,那種莫名其妙的陰沉感覺。

有些時候,確實就會發覺自己一直沒來由地心情低落,很像是一般人所說得低潮期,做什麼事都提不起勁,只有憂鬱再憂鬱。

三個段落的故事,分別是櫻花抄、太空人與秒速5公分,第一個段落,描述憂鬱少年遠野貴樹,跟青梅竹馬篠原明裡的分離,後來約定見面的過程,暴風雪似乎讓他們的距離隨著時間越拉越遠。第二段落,遠野貴樹沒有收件者的短訊習慣,明顯說明了,空間的距離讓他與明裡漸行漸遠,沒有聯絡了,而同學澄田花苗暗戀的過程,這個憂鬱少年一點也沒察覺,到了火箭發射的那一瞬間,澄田花苗終於明白,貴樹的眼光,一直落在遙遠的那一方。

第三個段落,明裡已經結婚了,貴樹與明裡已經是完全不再相交的兩條線,永遠存放在心理的某一個角落,交往了三年的水野,即使還想跟貴樹貼近,這個憂鬱少年還停留在對明裡的思念中,直到最後山崎正義的這首情歌「One more time One more chance」。


秒速5釐米
秒速5公分 - Wiki
山崎正義-One more time,One more chance
新海誠

2007/10/5

Using ldap api to connect MS Active Directory

當系統需要透過外部的帳號倉庫做帳號認證時,我們以Spring外接一段程式,處理系統登入與修改密碼的程序,連接外部的LDAP或AD。

當我遇到MS AD時,我必須要先把他安裝起來,這是我遇到的第一個困難,我參考了JNDI AD 備忘記的程序,先把MS Windows 2003標準版安裝在VM上,然後設定了AD,接下來設定了IIS,並安裝CA,把SSL搞定跟key store搞定,其中我以ldap browser測試過,可以連接AD成功,瀏覽整個AD Tree的資料。

後來就參考了java如何結合(呼叫)Win2000 ActiveDirectory,做出了AD認證的程式,這個認證有點奇怪,因為只要是合法的帳號,就可以瀏覽所有的使用者節點資料。

接下來就是修改密碼,參考如何使用 JNDI change win 2000 AD user password,可以處理修改密碼的程序,但前提是MS IIS export的key必須要先import到JVM的cacerts這個key store裡面,得用SSL才能運作。

搜尋所有使用者帳號的查詢,就是透過ldap的searchFilter功能,針對Users,把一些非一般使用者帳號的條件放進去,將不合用的帳號過濾掉,拉出資料的查詢結果,就完成了(查詢結果回傳的ADUser物件,只是一個包含了id與name欄位的data bean物件)。

安裝是最麻煩的地方......。

public class ADClient {
private static Logger logger = Logger.getLogger(ADClient.class.getName());

public static boolean checkAuth(String ldapserver, String userdn, String password)
throws NamingException {
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
// env.put(Context.PROVIDER_URL, "ldap://192.168.2.200");
env.put(Context.PROVIDER_URL, ldapserver);
env.put(Context.SECURITY_AUTHENTICATION, "Simple");
// env.put(Context.SECURITY_PRINCIPAL, "CN=" + uid + ",CN=Users,DC=msdiv,DC=com,DC=tw");
env.put(Context.SECURITY_PRINCIPAL, userdn);
env.put(Context.SECURITY_CREDENTIALS, password);

DirContext ctx = null;
boolean bLogin = false;
try {
ctx = new InitialDirContext(env);
bLogin = true;
} catch (javax.naming.AuthenticationException authe) {
logger.error("Error: ", authe);
bLogin = false;
} finally {
if (ctx != null) {
try {
ctx.close();
} catch (Exception Ignore) {
}
}
}
return bLogin;
}

public static void changePwd(String ldapserver, String admindn, String adminpassword,
String userdn, String newPassword, String keystorePath, String keystorePassword)
throws NamingException, UnsupportedEncodingException {
Hashtable env = new Hashtable();
// String admindn = "cn=administrator,CN=Users,DC=msdiv,DC=com,DC=tw";
// String adminpassword = "adtest1111";
// String userdn = "CN=test,CN=Users,DC=msdiv,DC=com,DC=tw";
// String newPassword = "test2222";

// System.out.println("java.home=" + System.getProperty("java.home"));
// String keystore = System.getProperty("java.home") + "\\lib\\security\\cacerts";
// String keystore = "C:\\Program Files\\Java\\jdk1.6.0\\jre\\lib\\security\\cacerts";

System.setProperty("javax.net.ssl.trustStore", keystorePath);
System.setProperty("javax.net.ssl.trustStorePassword", keystorePassword);
System.setProperty("UseSunHttpHandler", "true");

env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, admindn);
env.put(Context.SECURITY_CREDENTIALS, adminpassword);
env.put(Context.SECURITY_PROTOCOL, "ssl");
// String ldapURL = "ldaps://192.168.2.200";
env.put(Context.PROVIDER_URL, ldapserver);

LdapContext ctx = null;
try {
logger.info("AD is trying to reset Password for: " + userdn);
ctx = new InitialLdapContext(env, null);
String newQuotedPassword = "\"" + newPassword + "\"";
byte[] newUnicodePassword = newQuotedPassword.getBytes("UTF-16LE");
ModificationItem[] mods = new ModificationItem[1];
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(
"unicodePwd", newUnicodePassword));
ctx.modifyAttributes(userdn, mods);
logger.info("AD Reset Password for: " + userdn + " ok.");
} finally {
if (ctx != null) {
if (ctx != null) {
try {
ctx.close();
} catch (Exception Ignore) {
}
}
}
}

}

public static ArrayList queryAD(String ldapserver, String admindn,
String adminpassword, int searchScope, String searchBaseDn, String searchFilter) throws NamingException {
ArrayList adusers=new ArrayList();

Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, ldapserver);
env.put(Context.SECURITY_AUTHENTICATION, "Simple");
// env.put(Context.SECURITY_PRINCIPAL, "CN=Administrator,CN=Users,DC=msdiv,DC=com,DC=tw");
// env.put(Context.SECURITY_CREDENTIALS, "adtest1111");
env.put(Context.SECURITY_PRINCIPAL, admindn);
env.put(Context.SECURITY_CREDENTIALS, adminpassword);

DirContext ctx = null;
boolean bLogin = false;
try {
ctx = new InitialDirContext(env);
bLogin = true;

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

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

// specify the LDAP search filter
// String searchFilter = "(& (objectClass=user) (!(adminCount=*))
// (!(isCriticalSystemObject=*)) (userPrincipalName=*) )";

// Specify the Base for the search
// String searchBaseDn = "CN=Users,DC=msdiv,DC=com,DC=tw";

// initialize counter to total the group members
int totalResults = 0;

// Specify the attributes to return
// String returnedAtts[] = { "sAMAccountName", "givenName", "mail" };
String returnedAtts[] = { "sAMAccountName", "name", "userPrincipalName" };
searchCtls.setReturningAttributes(returnedAtts);

// Search for objects using the filter
NamingEnumeration answer = ctx.search(searchBaseDn, searchFilter, searchCtls);

// Loop through the search results

String id = "";
String name = "";
while (answer.hasMoreElements()) {
totalResults++;
SearchResult sr = (SearchResult) answer.next();

logger.info(totalResults + ">" + sr.getName());

// Print out the groups

Attributes attrs = sr.getAttributes();
if (attrs != null) {

try {
for (NamingEnumeration ae = attrs.getAll(); ae.hasMore();) {
Attribute attr = (Attribute) ae.next();
logger.debug(" Attribute: " + attr.getID());
if (attr.getID().equals("sAMAccountName")) {
for (NamingEnumeration e = attr.getAll(); e.hasMore();) {
id = (String) e.next();
logger.debug(" value: " + id);
}
} else if (attr.getID().equals("name")) {
for (NamingEnumeration e = attr.getAll(); e.hasMore();) {
name = (String) e.next();
logger.debug(" value: " + name);
}
} else {
for (NamingEnumeration e = attr.getAll(); e.hasMore();) {
logger.debug(" value: " + e.next());
}
}
}
} catch (NamingException e) {
logger.error("Can not retrieve data from AD!");
logger.error("Error: ", e);
throw e;
}
}

if( !id.equals("")) {
adusers.add(new ADUser(id, name));
}
}
logger.info("Total results: " + totalResults);
} finally {
if (ctx != null) {
try {
ctx.close();
} catch (Exception Ignore) {
}
}
}
return adusers;
}
}

2007/9/25

撲克牌的用途

有關撲克牌,大家都覺得,這是一種賭博的工具,但我們家的小朋友,卻早早在三歲時,就很熟悉用撲克牌來玩一些遊戲。我們會在家裡玩的遊戲是,抓鬼跟心臟病。

通常抓鬼是大家發牌後,先把成對的撲克牌丟出來,接下來才開始一個接著一個抓鬼牌,最後一個拿到鬼牌的就輸了,這我們會玩,但我們還會玩一些抓鬼的變形遊戲,例如,把所有的牌蓋起來撲開,接下來就一個一個流輪抽鬼牌,抽到鬼牌的就贏了。或者我們會猜拳,用布猜贏可以抽五張,剪刀可抽兩張,石頭可抽一張。抽牌的部分也可有變化,例如在抽的時候,大家可以直接猜抽到的牌是幾號,假如我先猜是9,抽到的牌也是9的時候,就贏了一次,就這樣一直猜,猜到沒有牌,看誰猜中最多次。

心臟病的遊戲,小朋友一開始是不會玩的,因為心臟病的規則是,最後一個拍的人,得把牌拿走,而手上剩下最多牌的人就輸了,小朋友一開始總是不會知道要搶先一步看到牌的數字,然後拍下去,我突然想到,把規則稍微修改一下,我們改成,最先拍下去的人,就可以把所有的牌拿走,手上拿到最多牌的人就贏了,這樣子一改,小朋友馬上很專注地,看丟出來的牌是不是跟叫牌的數字一樣。

心臟病也是有變形的遊戲,原本叫牌的順序是依照數字順序1~10,11,12,13這樣的順序,我們改成自己隨便叫一個數字,規則一改成這樣,這個遊戲馬上就變成很慢速的遊戲,因為你要仔細聽大家叫牌的數字,同時也要看丟出來的牌面數字,馬上就變成一個更好玩的遊戲。還有幾次我們改成一次丟兩張牌出來。後來我有一次看到他有很多abc字母的字卡,我就想到可以用這個字卡來玩心臟病,他也玩得意猶未盡。

今天我們又發明了一個新玩法,拿撲克牌來說故事,例如,我先抽到7,我會說,有一天小P走在路上遇到了7個小矮人,接下來換他抽一張牌,繼續講故事,雖然他講的故事沒什麼邏輯順序,但他也很高興地玩了兩次。

撲克牌是一個很好用的玩具,我想我會一直使用下去。

撲克牌 Wiki

2007/9/5

我剩下什麼?我擁有什麼?

去掉工作,
我剩下家人,與許久不見的朋友們。
去掉MSN,
我剩下家人,和一個家。
沒有家,
我剩下手機跟一點錢。
去掉電話,
我剩下電腦。
去掉鍵盤與滑鼠,
我剩下什麼?

嗯!
我有電腦、手機、家人、朋友跟一點錢。

僅此而已...

2007/7/23

Chain of Responsibility Implementation using Spring IoC

GoF中提到,Chain of Responsibility的實作方式有兩種,一種是製作successor串列(利用Composite的既有父節點指標,或是自己再另外寫一個,這個實作可以寫在Handler裡面或是定義在這些Event Handlers的外面),一種是直接連接successor。

Spring IoC提供使用者以XML定義的方式,初始化物件,假設我們先定義了一個Event Handler interface,接著先依照自己的需要實作多個物件,implements這個Handeler介面,而以Spring XML設定的方式,初始化多個物件實體。

再來就是靠Spring在ApplicationContext中提供的getBeansOfType這個method,一次將所有定義在xml裡面,實作了EventHandler介面的所有beans,然後依序將Event傳送給所有Event Handler處理。當然這種實作方式,沒有辦法彈性地設定這些EventHandler接收Event的前後順序,只能保證Event確實會傳送給每一個在XML中已經定義的EventHandlers。

如果要讓EventHandler自己決定下一個EventHandler是誰,開發人員可以調整XML定義bean的順序,讓getBeansOfType取到bean的順序,就是xml中定義的順序,但這不是一種彈性的作法。另一種作法,得在EventHandler中增加一個method,傳回下一個EventHandler的bean id,但這樣寫似乎也不是一種好方法。

Anyway我們的issue並不在意處理Event的順序,所以...就先這樣吧。