Hudson tips

Hudson的项目有三种状态,分别是failed, Success, Unstable。当单元测试未能通过时,Hudson不会fail掉整个build而是设置为ubstable,并且继续执行post build scripts和actions。这就为集成的版本带来了一些不可知因素。取消这个设置,可以通过在maven options中添加一个 -Dmaven.test.failure.ignore=false。或者在全局设置,manage hudson -> configure hudson -> Global MAVEN_OPTS。这个方法来自:http://stackoverflow.com/questions/1004540/fail-hudson-build-on-single-unit-test-failure

此外,我们借助hudson来自动完成开发环境的部署。从hudson的插件列表中安装ssh plugin和scp plugin。对打包项目新增一个post build action,使用SCP插件把打包生成的压缩文件上传到开发机上。本想同时配置一个build script,在开发环境机器上执行一个自动化部署脚本,但是使用发现ssh plugin的操作居然先于scp操作,而且这个顺序无法配置!不过不要紧,新建一个freestyle项目,项目只利用ssh插件运行远程脚本,再在打包项目配置中新增一个post build action -> build other project,填写前者的项目名,使之成为打包项目的downstream project即可。

The post is brought to you by lekhonee v0.7

Bason: A BSON Serialization Code Generator

Bason is a code generator for object to bson serialization and deserialization. Different from tranditional reflection way, bason uses an annotation processor to generate serialization manager at compile time. You just add Bason as compilation dependency and drop it in the runtime.

To use Bason, you simply add annotation to JavaBeans:

/**
 *
 */

package info.sunng.bason.example;


import java.util.Date;

import info.sunng.bason.annotations.BsonAlias;
import info.sunng.bason.annotations.BsonDocument;
import info.sunng.bason.annotations.BsonIgnore;

/**
 * @author SunNing
 *
 * @since Aug 18, 2010
 */

@BsonDocument
public class Passenger {

    private double packageWeight;

    private long ticketId;

    private String name;

    private Date createdDate;

    private Flight flight;

    /**
     * @return the packageWeight
     */

    @BsonIgnore
    public double getPackageWeight() {
        return packageWeight;
    }

    /**
     * @param packageWeight the packageWeight to set
     */

    public void setPackageWeight(double packageWeight) {
        this.packageWeight = packageWeight;
    }

    /**
     * @return the ticketId
     */

    @BsonAlias("ticket")
    public long getTicketId() {
        return ticketId;
    }

    /**
     * @param ticketId the ticketId to set
     */

    public void setTicketId(long ticketId) {
        this.ticketId = ticketId;
    }

    /**
     * @return the name
     */

    public String getName() {
        return name;
    }

    /**
     * @param name the name to set
     */

    public void setName(String name) {
        this.name = name;
    }

    /**
     * @param createdDate the createdDate to set
     */

    public void setCreatedDate(Date createdDate) {
        this.createdDate = createdDate;
    }

    /**
     * @return the createdDate
     */

    public Date getCreatedDate() {
        return createdDate;
    }

    /**
     * @param flight the flight to set
     */

    public void setFlight(Flight flight) {
        this.flight = flight;
    }

    /**
     * @return the flight
     */

    public Flight getFlight() {
        return flight;
    }

}
  • @BsonDocument marks this bean to be processed by bason processor. Serialization and deserialization support for this bean will be added to the manager. The bean must follow the Java Bean specification that has a getter and a setter for each property.
  • @BsonAlias on the getter allows user the specify a name for bson document instead of the default java bean property name.
  • @BsonIgnore on the getter marks a property to be transient when serialization and deserialization.

Then you need a bason.properties at the root of classpath which looks like

bason.managerClassName=info.sunng.bason.BasonManager

You specify the manager class name here. This name can not be duplicated if you use Bason in multiple modules.

Take maven configuration as an example:

    <dependencies>
        <dependency>
            <groupId>info.sunng.bason</groupId>
            <artifactId>bason-annotation</artifactId>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>info.sunng.bason</groupId>
            <artifactId>bason-internal</artifactId>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.mongodb</groupId>
            <artifactId>mongo-java-driver</artifactId>
        </dependency>
    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>

        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>

                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>

                    <compilerArguments>
                        <processor>info.sunng.bason.internal.BasonProcessor</processor>
                    </compilerArguments>
                </configuration>
            </plugin>
        </plugins>
    </build>

when everything is ready, run mvn compile to generate the manager source file. By default, in a standard maven project, the generated file will be placed at:
/bason-example/target/generated-sources/annotations/

package info.sunng.bason;
import org.bson.*;
import javax.annotation.Generated;
@Generated({"info.sunng.bason.BasonManager"})
public final class BasonManager{
    public static final BSONObject toBson(info.sunng.bason.example.Passenger o){
        if (o == null) {
            throw new NullPointerException();
        }
        BSONObject bson = new BasicBSONObject();
        bson.put("ticket",o.getTicketId());
        bson.put("name",o.getName());
        bson.put("createdDate",o.getCreatedDate());
        bson.put("flight",toBson(o.getFlight()));
        return bson;
    }
    public static final info.sunng.bason.example.Passenger fromBson(info.sunng.bason.example.Passenger o, BSONObject bson){
        if (o == null || bson == null) {
            throw new NullPointerException();
        }
        o.setTicketId((java.lang.Long)bson.get("ticket"));
        o.setName((java.lang.String)bson.get("name"));
        o.setCreatedDate((java.util.Date)bson.get("createdDate"));
        o.setFlight(fromBson(new info.sunng.bason.example.Flight(), (BSONObject)bson.get("flight")));
        return o;
    }
    public static final BSONObject toBson(info.sunng.bason.example.Flight o){
        if (o == null) {
            throw new NullPointerException();
        }
        BSONObject bson = new BasicBSONObject();
        bson.put("company",o.getCompany());
        bson.put("flightId",o.getFlightId());
        return bson;
    }
    public static final info.sunng.bason.example.Flight fromBson(info.sunng.bason.example.Flight o, BSONObject bson){
        if (o == null || bson == null) {
            throw new NullPointerException();
        }
        o.setCompany((java.lang.String)bson.get("company"));
        o.setFlightId((java.lang.String)bson.get("flightId"));
        return o;
    }
}

The project is hosted at
http://github.com/sunng87/bason

If you have any ideas, just let me know.

The post is brought to you by lekhonee v0.7

Maven recipe #1: Test Java code with groovy

问题: 受够了,不想写Java了,写个含有数据的map还要new出来一个一个put进去,想用groovy解决单元测试
解决: gmaven+groovy eclipse插件可以解决这个需求
在pom.xml中添加gmaven的依赖,注意,仅用来测试。老大不让生产代码里有不可靠的东西。

...
        <dependency>
            <groupId>org.codehaus.groovy.maven.runtime</groupId>
            <artifactId>gmaven-runtime-default</artifactId>
            <version>1.0-rc-3</version>
            <scope>test</scope>
        </dependency>
...
            <plugin>
                <groupId>org.codehaus.groovy.maven</groupId>
                <artifactId>gmaven-plugin</artifactId>
                <version>1.0-rc-3</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>generateStubs</goal>
                            <goal>compile</goal>
                            <goal>generateTestStubs</goal>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

在eclipse中新建source folder, src/test/groovy,在其中创建groovy test case即可

Maven recipe #0

问题: 多个root pom的dependencyManagement有重复的内容,希望统一管理。
解决:
新建一个空pom.xml,在dependencyManagement中指定这些依赖,如

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>info.sunng</groupId>
    <artifactId>root</artifactId>
    <packaging>pom</packaging>
    <version>0.0.1-SNAPSHOT</version>
    <dependencyManagement>

        <dependencies>
            <dependency>
                <groupId>info.sunng</groupId>
                <artifactId>X</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>

        </dependencies>
    </dependencyManagement>

</project>

将这个pom.xml部署到你的Maven私服。

在其他root pom中添加这样的dependencyManagement

....
            <dependency>
                <groupId>info.sunng</groupId>
                <artifactId>root</artifactId>
                <version>0.0.1-SNAPSHOT</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
....

We are using Hudson for continuous integration

经历了持续两周的人肉集成,今天上午抢得一台Linux机器,终于尝试用hudson来替代人肉构建。

Hudson的安装和配置远比想象的简单,只要下载发布的war包,在相应的目录执行
nohup java -jar hudson.war > hudson.log 2>&1 &
即可启动到后台

hudson的web图形界面可以胜任几乎全部工作。我们主要使用maven来构建项目,hudson提供了非常强大的功能:邮件提醒(通过插件支持twitter/jabber/irc提醒);自动构建,除了基本的定时构建以外,hudson还会自动解析其管理的项目之间的依赖关系,从而实现级联的构建,这个功能非常震撼。

刚刚上手以后我还安装了两个插件。一个是build-timeout插件,可以之间一次构建的超时时间:我们的项目中有老大写的交互式的maven配置插件,一旦这个插件在自动构建时运行会阻塞构建的流程。另一个是scp发布插件,可以自动scp一个文件到远程服务器上,我用这个插件来把构建版本发布到运行环境中,只要在适当的时候重启一下运行环境的服务器就可以实现部署了。不过scp插件由于上游依赖的问题貌似不支持putty生成的privatekey,这是暂时的美中不足。

配置了整整一天,终于有了CI工具,我就彻底解放出来可以做其他事了。