Xuite.net Personal Portal 的新風貌

January 2, 2008 (Wednesday) Leave a comment

最近連上 Xuite.net 會發現首頁又有了新改變,這個 personal portal 的首頁進入 Beta II。如果有使用過 Netvibes, iGoogle 等服務的人當然不會陌生。國內對於 personal portal 的做法一向很愛用鎖國政策,portal 上的元件再怎麼設定都只讓客戶連到自己的服務,對於外面的服務大都不願提供連結方式。Xuite.net 在這方面就好多了,有 RSS component 可以連結外部,至少有達到一個 personal portal 的最基本要求。版面可以自由拖拉設定,新增 tab 等等。每個 tab 能有自己的 layout 以及背景的設定,這點是很多國外的 personal portal 也沒有的。

另一個比較特別的地方是它有一個 public 的個人首頁。 一般的 personal portal 只能由 portal 的 owner 連入去,而 Xuite 提供了一個個人首頁的機制,可以讓別人連進來看到你所設定的首面。這個算是一大特色,算是結合了國內很多個人網站以及國外 personal portal 兩方好處的一個想法。至於日後有什麼發展,就要看能夠提供多少吸引人的元件了。

Advertisements

LiquiBase – A Database Refactoring Tool

December 16, 2007 (Sunday) Leave a comment

Change is the only thing unchanged. 軟體當然也脫離不了這個道理,但改變通常是會對軟體造成一些衝擊和影響的,有名的軟體技術很多都是教導如何面對改變而出現的。Refactoring 的技術鼓勵大家勇於面對改變,只有發現有不夠好的地方,馬上藉由 refactoring 的手法讓 code 變得更好。因此我們可以經由 refactoring 讓程式一步一步的改善,但 enterprise software 一般都會有 database,如果程式能夠一直改進,但 database 的 schema 沒有辦法修改的話,在改善的程度上會有很大的限制。因此 Database refactoring 也是有被研究討論的技術,Refactoring Database 正是討論這部份的一本好書。

既然 refactoring code 的時候需要使用工具的幫忙 (Eclipse 等 IDE 都有 support source code refactoring),那麼 Database refactoring 也需要有工具才會容易使用。LiquiBase 就是做 database refactoring 的工具,由 developer 提供的 change log file,裡面寫上想要做的 change set (如:Step 2 的樣子)。在執行 liquibase 的時候,它會去看目前的 database 是在哪一個 change set version,執行還沒有 apply 的 change set。當中 change set 版本的管理也是由 liquibase 來做。來舉一個實際的例子:

如果我想要建立一個 Table PERSON, 裡面的 column 有 ID, NAME。那我把它寫成 change set 1。這時執行 liquibase,由於我的 DB 裡本來沒有任何 table  所以 liquibase 會幫我執行 change set 1,也就是建立 PERSON table,同時會在 DB 裡記錄目前是在 change set 1。

接下來我可能覺得 PERSON 需要記錄更多的東西,所以想要增加 AGE 這個 column ,我就把它寫成 change set 2。這時執行 liquibase,它會發現剛剛的 DB 是在 change set 1 這個 version,所以就執行 change set 2。如果我把 DB 砍掉再執行 liquibase,這時它就會一次執行 change set 1, 2。

有了這個工具之後,我們對於 database 的修改都可以一一被記錄下來,加上 VCS (Version Control System) 的機制,就可以回去任何一個 DB 的版本。另外,liquibase 支援 MySQL, PostgreSQL , Oracle, MS-SQL, Sybase, DB2, Derby, HSQL, H2 等等,即使使用不一樣的 DB 也可以用同樣的語法寫 change set,這也是 liquibase 的一大功能。有了 liquibase 再搭配上針對 database schema 所做的測試,就可以隨時做 database refactoring,而且就跟 refactoring source code 一樣的簡單又有保障。

Unitils — 一次滿足所有 unit test 的需求

December 2, 2007 (Sunday) Leave a comment

談到 unit test,大家馬上想到的應該會是 JUnit;又或許是比較新一點的 TestNG。有些比較有經驗的人可能就會提出,那 servlet 如何測試?Spring bean 又如何測試?DAO 又如何測試,怎麼去建立 DB 的資料?如何去 mock 一個物件?

這些問題早就出現了,也早就有解法了,只是每一個問題可能都會對應到一個  library 或小型的 framework。 一直都沒有一個一次把問題解決的方案,直到 Unitils 的出現。Unitils 發源於 Ordina J-Technologies 的一個 unit test discussion group,會中他們討論出一份 Unit testing 的 Guidelines,而 Unitils 正是這份 guideline 的實作。

在 unit testing framework 的部份,Unitils 是 JUnit3, JUnit4, TestNG 三種都支援,所以不管你最熟悉的是哪一個都可以順利的使用 Unitils。在功能方面,Unitils 主要 support 以下幾項功能:

這些功能的文件都寫得很簡單易懂,其實觀念也很簡單,歸納一下只有幾個:

  • 使用 Java 的 equals 去 compare 通常不會是我們真的要的狀況,所以提供了比較寬鬆的比較
  • Unit Testing Database 必需要能夠自動的維護 (建立與更新版本)
  • DB 的資料必需要能夠在測試中自動加入
  • Test fixture 像是 Spring bean, mock object 等等盡量使用 annotation 標註,由 framework 自動產生

另外,Unitils 本身是由一堆 module 所組成的,如果它所提供的 module 還是無法滿足你的需求的話,要擴充其實很簡單。有興趣的人看一下 Unitils 的 source code 以及 unitils-default.properties 這個設定檔應該就知道怎麼做了。其實很難得一個小小的 unit testing framework 居然在模組化上做得這麼精美。

以前在這裡討論過 full stack application framework 的興起,同樣的 Unitils 這個可以滿足所有 unit test 需求的 framework 也很有可能慢慢會變成主流。只要一種技術成熟到一個程度之後,將會有整合的環境出現。在 JUnit、TestNG 都沒有太多的突破之後,Unitils 補足了這個空缺的需求。 如果你還在煩惱要怎麼開始做 unit test,那就用看看 Unitils 吧!

當 Spring 2.5 遇上 Hibernate Annotations – 自動搜尋 classpath 內的 entity

November 24, 2007 (Saturday) Leave a comment

最近推出的 Spring 2.5 提倡使用 annotation 的方式取代 xml 的設定,這個部份幾乎完全取代了 Spring Annotations 的功能,看來這個 project 應該已經沒有什麼生存空間。但 spring annotations 的 hibernate module 所提供的 AutomaticAnnotationSessionFactoryBean 一直是我很喜歡的功能。以往使用 Hibernate Annotations 時我們只能以 fully qualified class name 或 fully qualified 的 package name 來指定 entity class。透過 AutomaticAnnotationSessionFactoryBean 我們可以自動找到所有標示為 @javax.persistence.Entity 的 classes 自動加入到 hibernate 的 mapping classes。Spring 2.5 既然本身就可以自動的搜尋到 @Component 以及相關的 bean class,那我們應該也可以做到 @Entity 的搜尋囉。

我按照 spring annotations 的做法,提供一個自製的 session factory bean:


import org.hibernate.HibernateException;
import org.hibernate.cfg.AnnotationConfiguration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean;
import org.springframework.util.ClassUtils;

import javax.persistence.Entity;
import java.io.IOException;

/**
 * Created on: 2007/11/24
 *
 * @author Alan She
 */
public class ClasspathScanningAnnotationSessionFactoryBean extends AnnotationSessionFactoryBean {

    private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
    private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
    private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);
    private final TypeFilter entityFilter = new AnnotationTypeFilter(Entity.class);
    private String resourcePattern = DEFAULT_RESOURCE_PATTERN;
    private String[] basePackages;

    public void setBasePackages(String... basePackages) {
        this.basePackages = basePackages;
    }

    protected void postProcessAnnotationConfiguration(AnnotationConfiguration config) throws HibernateException {
        for (String basePackage : basePackages) {
            try {
                String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                        ClassUtils.convertClassNameToResourcePath(basePackage) + "/" + this.resourcePattern;
                Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
                for (int i = 0; i < resources.length; i++) {
                    Resource resource = resources[i];
                    MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
                    if (isEntity(metadataReader)) {
                        String classFileFullPath = resource.getURL().getPath();
                        String basePackageResourcePath = ClassUtils.convertClassNameToResourcePath(basePackage);
                        int startIndex = classFileFullPath.indexOf(basePackageResourcePath);
                        final String classFilePath = classFileFullPath.substring(startIndex,
                                classFileFullPath.length() - ClassUtils.CLASS_FILE_SUFFIX.length());
                        Class entityClass = null;
                        try {
                            entityClass = ClassUtils.forName(ClassUtils.convertResourcePathToClassName(classFilePath));
                        } catch (ClassNotFoundException e) {
                            throw new HibernateException("Entity class not found during classpath scanning", e);
                        }
                        config.addAnnotatedClass(entityClass);
                    }
                }
            }
            catch (IOException ex) {
                throw new HibernateException("I/O failure during classpath scanning", ex);
            }
        }
    }

    private boolean isEntity(MetadataReader metadataReader) throws IOException {
        if (entityFilter.match(metadataReader, this.metadataReaderFactory)) {
            return true;
        }
        return false;
    }
}

這個 class 的內容是以 Spring 2.5 的 ClassPathBeanDefinitionScanner 為骨幹而來的。原理很簡單,以 PathMatchingResourcePatternResolver 去找到所以 basePackage 下的 classes,一一比對是否有 annotate 了 @javax.persistence.Entity,如果有就加入到 session factory 的 annotationClass。


<bean id="sessionFactory" class="package.ClasspathScanningAnnotationSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="hibernateProperties">
    <props>
        <prop key="hibernate.dialect">${hibernate.dialect}</prop>
    </props>
    </property>
    <property name="basePackages">
        <list>
            <value>package.model</value>
        </list>
    </property>
</bean>

使用上也非常簡單,跟一般 spring 宣告 session factory 一樣,只需指定 basePackage 作為搜尋的範圍即可。

自動搜尋當然有優點也有缺點,但我個人討厭一一去設定 entity。有人可能會覺得萬一如果我有在 classpath 裡的 entity 又不想加入那不就只能全都用手動設定?其實如果想要有些彈性可以加入 include / exclude pattern 等東西,讓設定更活。畢竟設定檔時代已經慢慢過去,在 convention over configuration 的大趨勢之下,想辦法制定規則以及將規則以自動化落實才是長久之計。

Mac Mini 入手

November 2, 2007 (Friday) 5 comments

As Title, 一個字 爽

雖然現在還是跟小孩子學走路一樣 …

Categories: Uncategorized

自製 Firefox 的 Search plugin

September 11, 2007 (Tuesday) 1 comment

使用 Firefox 的人一定都有在用右上方的搜尋工具,除了 Google, Wikipedia 以外,也有不少網站有提供自己的 Search plugin。但是你平常最常搜尋的網站有這樣的 plugin 嗎?國外的大站也未必都有 plugin,更別論台灣的網站了。

其實這樣的 plugin 很簡單,大概有寫過網頁程式的程度就可以做出來了。 首先,去 Firefox 的安裝目錄 (可能是 C:\Program Files\Mozilla Firefox\) 打開 searchplugins。然後你會看到 google.xml 之類的檔案,那就是一個 search plugin。我們要做的事很簡單,copy 一個出來改成自己要的 (wikipedia 的那個語法相對簡單,可以用簡單的先改)。以下大概解釋一下每個 element 的改法:

  • <SearchPlugin>: 這個 plugin 的 xml root element,不用動
  • <ShortName>: 這個 search plugin 的名稱
  • <Description>: 說明
  • <InputEncoding>: 輸入文字的編碼,請視你要搜尋的網站使用的編碼決定
  • <Image>: 16×16 的小 icon, 以 base64 編碼直接放在 tag 裡。可以使用這個網站把 ico檔轉成 base64。
  • <Url>: 相信會寫網頁程式人的對這一定不會太陌生,template 裡填入 query 的 url,裡面的 <param> tag 填入要送過去的參數。可以送一些固定的值,唯一動態的值是 {searchTerms},那就是在 search box 填入的文字。至於 query 的 url 跟 param 到底會是什麼就視網站而定了,對熟網頁程式的人一定不難。
  • <SearchForm>: 沒有輸入時按 enter 就會跳到這個 url,一般可以設定為搜尋頁。

修改好之後重開 Firefox 就會多出你剛改好的選項了,這樣就可以自製自己常用網頁的 search plugin,而不用每次都連到網站上囉。

雜記

June 21, 2007 (Thursday) Leave a comment

已經好久沒時間長篇大論了,不寫下一點東西又覺得很可惜:

  • 在 Google Code 上開一個 project 比在 SourceForge.net 要簡單,不用慢慢審查。Subversion repository 的空間是 100MB,不過這種無政府狀態的環境會變成怎樣呢?雖然 SourceForge.net 也是一堆死掉的 project 沒人跟進。
  • http://devshots.com/ 是一個 for developer 的 search engine,底下還是使用 Google,不過查出來的東西有差嗎?畢竟 developer 是世界上搜尋能力最強的一群人吧,或許對別的領域做點專用的搜尋引擎會比較有用。
  • http://softwarecreation.org/ 這個 blog 的作者 Andriy Solovey 雖然我不知道是誰,不過他的文章都是引經據典,只要在做跟 software 相關的我想多少都有點幫助。
  • ZK 己經推出 version 2.4 雖然我沒有很仔細看有什麼功能改進,不過我倒覺得功能已經不是最重要的一環了,ZK 如果要做得更好,需要有使用的 best practice。Framework 部份是很強大,不管是寫到像只有 JSP 沒有 Servlet 的 zscript 還是弄到像 Echo2 的做法,ZK 都可以 support,回到根本,Spring 跟 Hibernate 有很多人做出 best practice,後來官方也會提供指引,這是成功的 open source project 必需要走的路。再來就是整合性的 component,一堆強大的 UI 終究還是需要跟後端取得資料,這個地方會是關鍵。