split elastic-job-cloud as a independent repo
diff --git a/elastic-job-cloud-common/pom.xml b/elastic-job-cloud-common/pom.xml
new file mode 100644
index 0000000..5b6ab99
--- /dev/null
+++ b/elastic-job-cloud-common/pom.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>elastic-job-cloud</artifactId>
+        <groupId>io.elasticjob</groupId>
+        <version>3.0.0.M1-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>elastic-job-cloud-common</artifactId>
+    <name>${project.artifactId}</name>
+    
+    <dependencies>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.curator</groupId>
+            <artifactId>curator-framework</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.curator</groupId>
+            <artifactId>curator-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.curator</groupId>
+            <artifactId>curator-recipes</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.curator</groupId>
+            <artifactId>curator-test</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.quartz-scheduler</groupId>
+            <artifactId>quartz</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-exec</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.unitils</groupId>
+            <artifactId>unitils-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-inline</artifactId>
+        </dependency>
+        
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>log4j-over-slf4j</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>commons-dbcp</groupId>
+            <artifactId>commons-dbcp</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.h2database</groupId>
+            <artifactId>h2</artifactId>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/api/ElasticJob.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/api/ElasticJob.java
new file mode 100644
index 0000000..0906215
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/api/ElasticJob.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.api;
+
+/**
+ * 弹性化分布式作业标识接口.
+ * 
+ * @author zhangliang
+ */
+public interface ElasticJob {
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/api/JobType.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/api/JobType.java
new file mode 100644
index 0000000..fe9c7ba
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/api/JobType.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.api;
+
+/**
+ * 作业类型.
+ *
+ * @author caohao
+ */
+public enum JobType {
+    
+    SIMPLE, DATAFLOW, SCRIPT
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/api/ShardingContext.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/api/ShardingContext.java
new file mode 100644
index 0000000..63dacf9
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/api/ShardingContext.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.api;
+
+import io.elasticjob.cloud.executor.ShardingContexts;
+import lombok.Getter;
+import lombok.ToString;
+
+/**
+ * 分片上下文.
+ * 
+ * @author zhangliang
+ */
+@Getter
+@ToString
+public final class ShardingContext {
+    
+    /**
+     * 作业名称.
+     */
+    private final String jobName;
+    
+    /**
+     * 作业任务ID.
+     */
+    private final String taskId;
+    
+    /**
+     * 分片总数.
+     */
+    private final int shardingTotalCount;
+    
+    /**
+     * 作业自定义参数.
+     * 可以配置多个相同的作业, 但是用不同的参数作为不同的调度实例.
+     */
+    private final String jobParameter;
+    
+    /**
+     * 分配于本作业实例的分片项.
+     */
+    private final int shardingItem;
+    
+    /**
+     * 分配于本作业实例的分片参数.
+     */
+    private final String shardingParameter;
+    
+    public ShardingContext(final ShardingContexts shardingContexts, final int shardingItem) {
+        jobName = shardingContexts.getJobName();
+        taskId = shardingContexts.getTaskId();
+        shardingTotalCount = shardingContexts.getShardingTotalCount();
+        jobParameter = shardingContexts.getJobParameter();
+        this.shardingItem = shardingItem;
+        shardingParameter = shardingContexts.getShardingItemParameters().get(shardingItem);
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/api/dataflow/DataflowJob.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/api/dataflow/DataflowJob.java
new file mode 100644
index 0000000..008d0a1
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/api/dataflow/DataflowJob.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.api.dataflow;
+
+import io.elasticjob.cloud.api.ElasticJob;
+import io.elasticjob.cloud.api.ShardingContext;
+
+import java.util.List;
+
+/**
+ * 数据流分布式作业接口.
+ * 
+ * @author zhangliang
+ * 
+ * @param <T> 数据类型
+ */
+public interface DataflowJob<T> extends ElasticJob {
+    
+    /**
+     * 获取待处理数据.
+     *
+     * @param shardingContext 分片上下文
+     * @return 待处理的数据集合
+     */
+    List<T> fetchData(ShardingContext shardingContext);
+    
+    /**
+     * 处理数据.
+     *
+     * @param shardingContext 分片上下文
+     * @param data 待处理数据集合
+     */
+    void processData(ShardingContext shardingContext, List<T> data);
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/api/script/ScriptJob.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/api/script/ScriptJob.java
new file mode 100644
index 0000000..f221213
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/api/script/ScriptJob.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.api.script;
+
+import io.elasticjob.cloud.api.ElasticJob;
+
+/**
+ * 脚本分布式作业接口.
+ * 
+ * @author zhangliang
+ */
+public interface ScriptJob extends ElasticJob {
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/api/simple/SimpleJob.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/api/simple/SimpleJob.java
new file mode 100644
index 0000000..d89d0b4
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/api/simple/SimpleJob.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.api.simple;
+
+import io.elasticjob.cloud.api.ElasticJob;
+import io.elasticjob.cloud.api.ShardingContext;
+
+/**
+ * 简单分布式作业接口.
+ * 
+ * @author zhangliang
+ */
+public interface SimpleJob extends ElasticJob {
+    
+    /**
+     * 执行作业.
+     *
+     * @param shardingContext 分片上下文
+     */
+    void execute(ShardingContext shardingContext);
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/config/JobCoreConfiguration.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/config/JobCoreConfiguration.java
new file mode 100644
index 0000000..d219cd7
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/config/JobCoreConfiguration.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.config;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import io.elasticjob.cloud.executor.handler.JobProperties;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * 作业核心配置.
+ * 
+ * @author zhangliang
+ */
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+@Getter
+public final class JobCoreConfiguration {
+    
+    private final String jobName;
+    
+    private final String cron;
+    
+    private final int shardingTotalCount;
+    
+    private final String shardingItemParameters;
+    
+    private final String jobParameter;
+    
+    private final boolean failover;
+    
+    private final boolean misfire;
+    
+    private final String description;
+    
+    private final JobProperties jobProperties;
+    
+    /**
+     * 创建简单作业配置构建器.
+     *
+     * @param jobName 作业名称
+     * @param cron 作业启动时间的cron表达式
+     * @param shardingTotalCount 作业分片总数
+     * @return 简单作业配置构建器
+     */
+    public static Builder newBuilder(final String jobName, final String cron, final int shardingTotalCount) {
+        return new Builder(jobName, cron, shardingTotalCount);
+    }
+    
+    @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+    public static class Builder {
+        
+        private final String jobName;
+        
+        private final String cron;
+        
+        private final int shardingTotalCount;
+        
+        private String shardingItemParameters = "";
+        
+        private String jobParameter = "";
+        
+        private boolean failover;
+        
+        private boolean misfire = true;
+        
+        private String description = "";
+        
+        private final JobProperties jobProperties = new JobProperties();
+        
+        /**
+         * 设置分片序列号和个性化参数对照表.
+         *
+         * <p>
+         * 分片序列号和参数用等号分隔, 多个键值对用逗号分隔. 类似map.
+         * 分片序列号从0开始, 不可大于或等于作业分片总数.
+         * 如:
+         * 0=a,1=b,2=c
+         * </p>
+         *
+         * @param shardingItemParameters 分片序列号和个性化参数对照表
+         *
+         * @return 作业配置构建器
+         */
+        public Builder shardingItemParameters(final String shardingItemParameters) {
+            if (null != shardingItemParameters) {
+                this.shardingItemParameters = shardingItemParameters;
+            }
+            return this;
+        }
+        
+        /**
+         * 设置作业自定义参数.
+         *
+         * <p>
+         * 可以配置多个相同的作业, 但是用不同的参数作为不同的调度实例.
+         * </p>
+         *
+         * @param jobParameter 作业自定义参数
+         *
+         * @return 作业配置构建器
+         */
+        public Builder jobParameter(final String jobParameter) {
+            if (null != jobParameter) {
+                this.jobParameter = jobParameter;
+            }
+            return this;
+        }
+        
+        /**
+         * 设置是否开启失效转移.
+         *
+         * <p>
+         * 只有对monitorExecution的情况下才可以开启失效转移.
+         * </p> 
+         *
+         * @param failover 是否开启失效转移
+         *
+         * @return 作业配置构建器
+         */
+        public Builder failover(final boolean failover) {
+            this.failover = failover;
+            return this;
+        }
+        
+        /**
+         * 设置是否开启misfire.
+         *
+         * @param misfire 是否开启misfire
+         *
+         * @return 作业配置构建器
+         */
+        public Builder misfire(final boolean misfire) {
+            this.misfire = misfire;
+            return this;
+        }
+        
+        /**
+         * 设置作业描述信息.
+         *
+         * @param description 作业描述信息
+         *
+         * @return 作业配置构建器
+         */
+        public Builder description(final String description) {
+            if (null != description) {
+                this.description = description;
+            }
+            return this;
+        }
+        
+        /**
+         * 设置作业属性.
+         *
+         * @param key 属性键
+         * @param value 属性值
+         *
+         * @return 作业配置构建器
+         */
+        public Builder jobProperties(final String key, final String value) {
+            jobProperties.put(key, value);
+            return this;
+        }
+        
+        /**
+         * 构建作业配置对象.
+         *
+         * @return 作业配置对象
+         */
+        public final JobCoreConfiguration build() {
+            Preconditions.checkArgument(!Strings.isNullOrEmpty(jobName), "jobName can not be empty.");
+            Preconditions.checkArgument(!Strings.isNullOrEmpty(cron), "cron can not be empty.");
+            Preconditions.checkArgument(shardingTotalCount > 0, "shardingTotalCount should larger than zero.");
+            return new JobCoreConfiguration(jobName, cron, shardingTotalCount, shardingItemParameters, jobParameter, failover, misfire, description, jobProperties);
+        }
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/config/JobRootConfiguration.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/config/JobRootConfiguration.java
new file mode 100644
index 0000000..168895f
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/config/JobRootConfiguration.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.config;
+
+/**
+ * 作业配置根接口.
+ *
+ * @author zhangliang
+ */
+public interface JobRootConfiguration {
+    
+    /**
+     * 获取作业类型配置.
+     * 
+     * @return 作业类型配置
+     */
+    JobTypeConfiguration getTypeConfig();
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/config/JobTypeConfiguration.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/config/JobTypeConfiguration.java
new file mode 100644
index 0000000..41e8076
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/config/JobTypeConfiguration.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.config;
+
+import io.elasticjob.cloud.api.JobType;
+
+/**
+ * 作业类型配置.
+ * 
+ * @author caohao
+ * @author zhangliang
+ */
+public interface JobTypeConfiguration {
+    
+    /**
+     * 获取作业类型.
+     * 
+     * @return 作业类型
+     */
+    JobType getJobType();
+    
+    /**
+     * 获取作业实现类名称.
+     *
+     * @return 作业实现类名称
+     */
+    String getJobClass();
+    
+    /**
+     * 获取作业核心配置.
+     * 
+     * @return 作业核心配置
+     */
+    JobCoreConfiguration getCoreConfig();
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/config/dataflow/DataflowJobConfiguration.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/config/dataflow/DataflowJobConfiguration.java
new file mode 100644
index 0000000..46c06d6
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/config/dataflow/DataflowJobConfiguration.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.config.dataflow;
+
+import io.elasticjob.cloud.api.JobType;
+import io.elasticjob.cloud.config.JobCoreConfiguration;
+import io.elasticjob.cloud.config.JobTypeConfiguration;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * 数据流作业配置信息.
+ * 
+ * @author caohao
+ * @author zhangliang
+ */
+@RequiredArgsConstructor
+@Getter
+public final class DataflowJobConfiguration implements JobTypeConfiguration {
+    
+    private final JobCoreConfiguration coreConfig;
+    
+    private final JobType jobType = JobType.DATAFLOW;
+    
+    private final String jobClass;
+    
+    private final boolean streamingProcess;
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/config/script/ScriptJobConfiguration.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/config/script/ScriptJobConfiguration.java
new file mode 100644
index 0000000..545f187
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/config/script/ScriptJobConfiguration.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.config.script;
+
+import io.elasticjob.cloud.api.JobType;
+import io.elasticjob.cloud.api.script.ScriptJob;
+import io.elasticjob.cloud.config.JobTypeConfiguration;
+import io.elasticjob.cloud.config.JobCoreConfiguration;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * 脚本作业配置.
+ * 
+ * @author caohao
+ * @author zhangliang
+ */
+@RequiredArgsConstructor
+@Getter
+public final class ScriptJobConfiguration implements JobTypeConfiguration {
+    
+    private final JobCoreConfiguration coreConfig;
+    
+    private final JobType jobType = JobType.SCRIPT;
+    
+    private final String jobClass = ScriptJob.class.getCanonicalName();
+    
+    private final String scriptCommandLine;
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/config/simple/SimpleJobConfiguration.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/config/simple/SimpleJobConfiguration.java
new file mode 100644
index 0000000..ad63a1b
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/config/simple/SimpleJobConfiguration.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.config.simple;
+
+import io.elasticjob.cloud.api.JobType;
+import io.elasticjob.cloud.config.JobCoreConfiguration;
+import io.elasticjob.cloud.config.JobTypeConfiguration;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * 简单作业配置.
+ * 
+ * @author caohao
+ * @author zhangliang
+ */
+@RequiredArgsConstructor
+@Getter
+public final class SimpleJobConfiguration implements JobTypeConfiguration {
+    
+    private final JobCoreConfiguration coreConfig;
+    
+    private final JobType jobType = JobType.SIMPLE;
+    
+    private final String jobClass;
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/context/ExecutionType.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/context/ExecutionType.java
new file mode 100644
index 0000000..b564c0b
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/context/ExecutionType.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.context;
+
+/**
+ * 执行类型.
+ *
+ * @author zhangliang
+ */
+public enum ExecutionType {
+    
+    /**
+     * 准备执行的任务.
+     */
+    READY,
+    
+    /**
+     * 失效转移的任务.
+     */
+    FAILOVER
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/context/TaskContext.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/context/TaskContext.java
new file mode 100644
index 0000000..3394de0
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/context/TaskContext.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.context;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * 任务运行时上下文.
+ *
+ * @author zhangliang
+ * @author caohao
+ */
+@Getter
+@EqualsAndHashCode(of = "id")
+@ToString(of = "id")
+public final class TaskContext {
+    
+    private static final String DELIMITER = "@-@";
+    
+    private static final String UNASSIGNED_SLAVE_ID = "unassigned-slave";
+    
+    private String id;
+    
+    private final MetaInfo metaInfo;
+    
+    private final ExecutionType type;
+    
+    private String slaveId;
+    
+    @Setter
+    private boolean idle;
+    
+    public TaskContext(final String jobName, final List<Integer> shardingItem, final ExecutionType type) {
+        this(jobName, shardingItem, type, UNASSIGNED_SLAVE_ID);
+    }
+    
+    public TaskContext(final String jobName, final List<Integer> shardingItem, final ExecutionType type, final String slaveId) {
+        metaInfo = new MetaInfo(jobName, shardingItem);
+        this.type = type;
+        this.slaveId = slaveId;
+        id = Joiner.on(DELIMITER).join(metaInfo, type, slaveId, UUID.randomUUID().toString());
+    }
+    
+    private TaskContext(final String id, final MetaInfo metaInfo, final ExecutionType type, final String slaveId) {
+        this.id = id;
+        this.metaInfo = metaInfo;
+        this.type = type;
+        this.slaveId = slaveId;
+    }
+    
+    /**
+     * 根据任务主键获取任务上下文.
+     *
+     * @param id 任务主键
+     * @return 任务上下文
+     */
+    public static TaskContext from(final String id) {
+        String[] result = id.split(DELIMITER);
+        Preconditions.checkState(5 == result.length);
+        return new TaskContext(id, MetaInfo.from(result[0] + DELIMITER + result[1]), ExecutionType.valueOf(result[2]), result[3]);
+    }
+    
+    /**
+     * 获取未分配执行服务器前的任务主键.
+     *
+     * @param id 任务主键
+     * @return 未分配执行服务器前的任务主键
+     */
+    public static String getIdForUnassignedSlave(final String id) {
+        return id.replaceAll(TaskContext.from(id).getSlaveId(), UNASSIGNED_SLAVE_ID);
+    }
+    
+    /**
+     * 设置任务执行服务器主键.
+     * 
+     * @param slaveId 任务执行服务器主键
+     */
+    public void setSlaveId(final String slaveId) {
+        id = id.replaceAll(this.slaveId, slaveId);
+        this.slaveId = slaveId;
+    }
+    
+    /**
+     * 获取任务名称.
+     *
+     * @return 任务名称
+     */
+    public String getTaskName() {
+        return Joiner.on(DELIMITER).join(metaInfo, type, slaveId);
+    }
+    
+    /**
+     * 获取任务执行器主键.
+     * 
+     * @param appName 应用名称
+     * @return 任务执行器主键
+     */
+    public String getExecutorId(final String appName) {
+        return Joiner.on(DELIMITER).join(appName, slaveId);
+    }
+    
+    /**
+     * 任务元信息.
+     */
+    @RequiredArgsConstructor
+    @Getter
+    @EqualsAndHashCode
+    public static class MetaInfo {
+        
+        private final String jobName;
+        
+        private final List<Integer> shardingItems;
+        
+        /**
+         * 根据任务元信息字符串获取元信息对象.
+         *
+         * @param value 任务元信息字符串
+         * @return 元信息对象
+         */
+        public static MetaInfo from(final String value) {
+            String[] result = value.split(DELIMITER);
+            Preconditions.checkState(1 == result.length || 2 == result.length || 5 == result.length);
+            return new MetaInfo(result[0], 1 == result.length || "".equals(result[1]) ? Collections.<Integer>emptyList() : Lists.transform(Splitter.on(",").splitToList(result[1]), 
+                    new Function<String, Integer>() {
+                        
+                        @Override
+                        public Integer apply(final String input) {
+                            return Integer.parseInt(input);
+                        }
+                    }));
+        }
+        
+        @Override
+        public String toString() {
+            return Joiner.on(DELIMITER).join(jobName, Joiner.on(",").join(shardingItems));
+        }
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/JobEvent.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/JobEvent.java
new file mode 100644
index 0000000..df0de56
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/JobEvent.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event;
+
+/**
+ * 作业事件接口.
+ *
+ * @author zhangliang
+ */
+public interface JobEvent {
+    
+    /**
+     * 获取作业名称.
+     * 
+     * @return 作业名称
+     */
+    String getJobName();
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/JobEventBus.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/JobEventBus.java
new file mode 100644
index 0000000..398989a
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/JobEventBus.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event;
+
+import com.google.common.eventbus.AsyncEventBus;
+import com.google.common.eventbus.EventBus;
+import io.elasticjob.cloud.util.concurrent.ExecutorServiceObject;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 运行痕迹事件总线.
+ * 
+ * @author zhangliang
+ * @author caohao
+ */
+@Slf4j
+public final class JobEventBus {
+    
+    private final JobEventConfiguration jobEventConfig;
+    
+    private final ExecutorServiceObject executorServiceObject;
+    
+    private final EventBus eventBus;
+    
+    private boolean isRegistered;
+    
+    public JobEventBus() {
+        jobEventConfig = null;
+        executorServiceObject = null;
+        eventBus = null;
+    }
+    
+    public JobEventBus(final JobEventConfiguration jobEventConfig) {
+        this.jobEventConfig = jobEventConfig;
+        executorServiceObject = new ExecutorServiceObject("job-event", Runtime.getRuntime().availableProcessors() * 2);
+        eventBus = new AsyncEventBus(executorServiceObject.createExecutorService());
+        register();
+    }
+    
+    private void register() {
+        try {
+            eventBus.register(jobEventConfig.createJobEventListener());
+            isRegistered = true;
+        } catch (final JobEventListenerConfigurationException ex) {
+            log.error("Elastic job: create JobEventListener failure, error is: ", ex);
+        }
+    }
+    
+    /**
+     * 发布事件.
+     *
+     * @param event 作业事件
+     */
+    public void post(final JobEvent event) {
+        if (isRegistered && !executorServiceObject.isShutdown()) {
+            eventBus.post(event);
+        }
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/JobEventConfiguration.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/JobEventConfiguration.java
new file mode 100644
index 0000000..9f872c6
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/JobEventConfiguration.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event;
+
+/**
+ * 作业事件配置标识接口.
+ *
+ * @author caohao
+ */
+public interface JobEventConfiguration extends JobEventIdentity {
+    
+    /**
+     * 创建作业事件监听器.
+     * 
+     * @return 作业事件监听器.
+     * @throws JobEventListenerConfigurationException 作业事件监听器配置异常
+     */
+    JobEventListener createJobEventListener() throws JobEventListenerConfigurationException;
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/JobEventIdentity.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/JobEventIdentity.java
new file mode 100644
index 0000000..e897888
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/JobEventIdentity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event;
+
+/**
+ * 作业事件标识.
+ *
+ * @author zhangliang
+ */
+public interface JobEventIdentity {
+    
+    /**
+     * 获取作业事件标识.
+     * 
+     * @return 作业事件标识
+     */
+    String getIdentity();
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/JobEventListener.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/JobEventListener.java
new file mode 100644
index 0000000..7853bf6
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/JobEventListener.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event;
+
+
+import com.google.common.eventbus.AllowConcurrentEvents;
+import com.google.common.eventbus.Subscribe;
+import io.elasticjob.cloud.event.type.JobExecutionEvent;
+import io.elasticjob.cloud.event.type.JobStatusTraceEvent;
+
+/**
+ * 作业事件监听器.
+ *
+ * @author zhangliang
+ */
+public interface JobEventListener extends JobEventIdentity {
+    
+    /**
+     * 作业执行事件监听执行.
+     *
+     * @param jobExecutionEvent 作业执行事件
+     */
+    @Subscribe
+    @AllowConcurrentEvents
+    void listen(JobExecutionEvent jobExecutionEvent);
+    
+    /**
+     * 作业状态痕迹事件监听执行.
+     *
+     * @param jobStatusTraceEvent 作业状态痕迹事件
+     */
+    @Subscribe
+    @AllowConcurrentEvents
+    void listen(JobStatusTraceEvent jobStatusTraceEvent);
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/JobEventListenerConfigurationException.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/JobEventListenerConfigurationException.java
new file mode 100644
index 0000000..2aaca24
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/JobEventListenerConfigurationException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event;
+
+/**
+ * 作业事件监听器配置异常.
+ * 
+ * @author zhangliang
+ */
+public final class JobEventListenerConfigurationException extends Exception {
+    
+    private static final long serialVersionUID = 4069519372148227761L;
+    
+    public JobEventListenerConfigurationException(final Exception ex) {
+        super(ex);
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/rdb/DatabaseType.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/rdb/DatabaseType.java
new file mode 100644
index 0000000..aecffab
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/rdb/DatabaseType.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event.rdb;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterators;
+
+import java.util.Arrays;
+
+/**
+ * 支持的数据库类型.
+ * 
+ * @author caohao
+ */
+public enum DatabaseType {
+    
+    H2("H2"), MySQL("MySQL"), Oracle("Oracle"), SQLServer("Microsoft SQL Server"), DB2("DB2"), PostgreSQL("PostgreSQL");
+    
+    private final String productName;
+    
+    DatabaseType(final String productName) {
+        this.productName = productName;
+    }
+    
+    /**
+     * 获取数据库类型枚举.
+     * 
+     * @param databaseProductName 数据库类型
+     * @return 数据库类型枚举
+     */
+    public static DatabaseType valueFrom(final String databaseProductName) {
+        Optional<DatabaseType> databaseTypeOptional = Iterators.tryFind(Arrays.asList(DatabaseType.values()).iterator(), new Predicate<DatabaseType>() {
+            @Override
+            public boolean apply(final DatabaseType input) {
+                return input.productName.equals(databaseProductName);
+            }
+        });
+        if (databaseTypeOptional.isPresent()) {
+            return databaseTypeOptional.get();
+        } else {
+            throw new RuntimeException("Unsupported database:" + databaseProductName);
+        }
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/rdb/JobEventRdbConfiguration.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/rdb/JobEventRdbConfiguration.java
new file mode 100644
index 0000000..60502d7
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/rdb/JobEventRdbConfiguration.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event.rdb;
+
+import io.elasticjob.cloud.event.JobEventConfiguration;
+import io.elasticjob.cloud.event.JobEventListener;
+import io.elasticjob.cloud.event.JobEventListenerConfigurationException;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import javax.sql.DataSource;
+import java.io.Serializable;
+import java.sql.SQLException;
+
+/**
+ * 作业数据库事件配置.
+ *
+ * @author caohao
+ */
+@RequiredArgsConstructor
+@Getter
+public final class JobEventRdbConfiguration extends JobEventRdbIdentity implements JobEventConfiguration, Serializable {
+    
+    private static final long serialVersionUID = 3344410699286435226L;
+    
+    private final transient DataSource dataSource;
+    
+    @Override
+    public JobEventListener createJobEventListener() throws JobEventListenerConfigurationException {
+        try {
+            return new JobEventRdbListener(dataSource);
+        } catch (final SQLException ex) {
+            throw new JobEventListenerConfigurationException(ex);
+        }
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/rdb/JobEventRdbIdentity.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/rdb/JobEventRdbIdentity.java
new file mode 100644
index 0000000..02f3c7b
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/rdb/JobEventRdbIdentity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event.rdb;
+
+import io.elasticjob.cloud.event.JobEventIdentity;
+
+/**
+ * 关系型数据库作业事件标识.
+ *
+ * @author zhangliang
+ */
+public class JobEventRdbIdentity implements JobEventIdentity {
+    
+    @Override
+    public String getIdentity() {
+        return "rdb";
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/rdb/JobEventRdbListener.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/rdb/JobEventRdbListener.java
new file mode 100644
index 0000000..32d14e7
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/rdb/JobEventRdbListener.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event.rdb;
+
+import io.elasticjob.cloud.event.JobEventListener;
+import io.elasticjob.cloud.event.type.JobExecutionEvent;
+import io.elasticjob.cloud.event.type.JobStatusTraceEvent;
+
+import javax.sql.DataSource;
+import java.sql.SQLException;
+
+/**
+ * 运行痕迹事件数据库监听器.
+ *
+ * @author caohao
+ */
+public final class JobEventRdbListener extends JobEventRdbIdentity implements JobEventListener {
+    
+    private final JobEventRdbStorage repository;
+    
+    public JobEventRdbListener(final DataSource dataSource) throws SQLException {
+        repository = new JobEventRdbStorage(dataSource);
+    }
+    
+    @Override
+    public void listen(final JobExecutionEvent executionEvent) {
+        repository.addJobExecutionEvent(executionEvent);
+    }
+    
+    @Override
+    public void listen(final JobStatusTraceEvent jobStatusTraceEvent) {
+        repository.addJobStatusTraceEvent(jobStatusTraceEvent);
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/rdb/JobEventRdbSearch.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/rdb/JobEventRdbSearch.java
new file mode 100644
index 0000000..d7f1637
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/rdb/JobEventRdbSearch.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event.rdb;
+
+import com.google.common.base.CaseFormat;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import io.elasticjob.cloud.context.ExecutionType;
+import io.elasticjob.cloud.event.type.JobExecutionEvent;
+import io.elasticjob.cloud.event.type.JobExecutionEventThrowable;
+import io.elasticjob.cloud.event.type.JobStatusTraceEvent;
+import io.elasticjob.cloud.event.type.JobStatusTraceEvent.Source;
+import io.elasticjob.cloud.event.type.JobStatusTraceEvent.State;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.Collection;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 运行痕迹事件数据库检索.
+ *
+ * @author liguangyun
+ */
+@RequiredArgsConstructor
+@Slf4j
+public final class JobEventRdbSearch {
+    
+    private static final String TABLE_JOB_EXECUTION_LOG = "JOB_EXECUTION_LOG";
+    
+    private static final String TABLE_JOB_STATUS_TRACE_LOG = "JOB_STATUS_TRACE_LOG";
+    
+    private static final List<String> FIELDS_JOB_EXECUTION_LOG = 
+            Lists.newArrayList("id", "hostname", "ip", "task_id", "job_name", "execution_source", "sharding_item", "start_time", "complete_time", "is_success", "failure_cause");
+    
+    private static final List<String> FIELDS_JOB_STATUS_TRACE_LOG = 
+            Lists.newArrayList("id", "job_name", "original_task_id", "task_id", "slave_id", "source", "execution_type", "sharding_item", "state", "message", "creation_time");
+    
+    private final DataSource dataSource;
+    
+    /**
+     * 检索作业运行执行轨迹.
+     * 
+     * @param condition 查询条件
+     * @return 作业执行轨迹检索结果
+     */
+    public Result<JobExecutionEvent> findJobExecutionEvents(final Condition condition) {
+        return new Result<>(getEventCount(TABLE_JOB_EXECUTION_LOG, FIELDS_JOB_EXECUTION_LOG, condition), getJobExecutionEvents(condition));
+    }
+    
+    /**
+     * 检索作业运行状态轨迹.
+     * 
+     * @param condition 查询条件
+     * @return 作业状态轨迹检索结果
+     */
+    public Result<JobStatusTraceEvent> findJobStatusTraceEvents(final Condition condition) {
+        return new Result<>(getEventCount(TABLE_JOB_STATUS_TRACE_LOG, FIELDS_JOB_STATUS_TRACE_LOG, condition), getJobStatusTraceEvents(condition));
+    }
+    
+    private List<JobExecutionEvent> getJobExecutionEvents(final Condition condition) {
+        List<JobExecutionEvent> result = new LinkedList<>();
+        try (
+                Connection conn = dataSource.getConnection();
+                PreparedStatement preparedStatement = createDataPreparedStatement(conn, TABLE_JOB_EXECUTION_LOG, FIELDS_JOB_EXECUTION_LOG, condition);
+                ResultSet resultSet = preparedStatement.executeQuery()
+                ) {
+            while (resultSet.next()) {
+                JobExecutionEvent jobExecutionEvent = new JobExecutionEvent(resultSet.getString(1), resultSet.getString(2), resultSet.getString(3), resultSet.getString(4),
+                        resultSet.getString(5), JobExecutionEvent.ExecutionSource.valueOf(resultSet.getString(6)), Integer.valueOf(resultSet.getString(7)), 
+                        new Date(resultSet.getTimestamp(8).getTime()), resultSet.getTimestamp(9) == null ? null : new Date(resultSet.getTimestamp(9).getTime()), 
+                        resultSet.getBoolean(10), new JobExecutionEventThrowable(null, resultSet.getString(11)) 
+                        );
+                result.add(jobExecutionEvent);
+            }
+        } catch (final SQLException ex) {
+            // TODO 记录失败直接输出日志,未来可考虑配置化
+            log.error("Fetch JobExecutionEvent from DB error:", ex);
+        }
+        return result;
+    }
+    
+    private List<JobStatusTraceEvent> getJobStatusTraceEvents(final Condition condition) {
+        List<JobStatusTraceEvent> result = new LinkedList<>();
+        try (
+                Connection conn = dataSource.getConnection();
+                PreparedStatement preparedStatement = createDataPreparedStatement(conn, TABLE_JOB_STATUS_TRACE_LOG, FIELDS_JOB_STATUS_TRACE_LOG, condition);
+                ResultSet resultSet = preparedStatement.executeQuery()
+                ) {
+            while (resultSet.next()) {
+                JobStatusTraceEvent jobStatusTraceEvent = new JobStatusTraceEvent(resultSet.getString(1), resultSet.getString(2), resultSet.getString(3), resultSet.getString(4),
+                        resultSet.getString(5), Source.valueOf(resultSet.getString(6)), ExecutionType.valueOf(resultSet.getString(7)), resultSet.getString(8),
+                        State.valueOf(resultSet.getString(9)), resultSet.getString(10), new Date(resultSet.getTimestamp(11).getTime()));
+                result.add(jobStatusTraceEvent);
+            }
+        } catch (final SQLException ex) {
+            // TODO 记录失败直接输出日志,未来可考虑配置化
+            log.error("Fetch JobStatusTraceEvent from DB error:", ex);
+        }
+        return result;
+    }
+    
+    private int getEventCount(final String tableName, final Collection<String> tableFields, final Condition condition) {
+        int result = 0;
+        try (
+                Connection conn = dataSource.getConnection();
+                PreparedStatement preparedStatement = createCountPreparedStatement(conn, tableName, tableFields, condition);
+                ResultSet resultSet = preparedStatement.executeQuery()
+                ) {
+            resultSet.next();
+            result = resultSet.getInt(1);
+        } catch (final SQLException ex) {
+            // TODO 记录失败直接输出日志,未来可考虑配置化
+            log.error("Fetch EventCount from DB error:", ex);
+        }
+        return result;
+    }
+    
+    private PreparedStatement createDataPreparedStatement(final Connection conn, final String tableName, final Collection<String> tableFields, final Condition condition) throws SQLException {
+        String sql = buildDataSql(tableName, tableFields, condition);
+        PreparedStatement preparedStatement = conn.prepareStatement(sql);
+        setBindValue(preparedStatement, tableFields, condition);
+        return preparedStatement;
+    }
+    
+    private PreparedStatement createCountPreparedStatement(final Connection conn, final String tableName, final Collection<String> tableFields, final Condition condition) throws SQLException {
+        String sql = buildCountSql(tableName, tableFields, condition);
+        PreparedStatement preparedStatement = conn.prepareStatement(sql);
+        setBindValue(preparedStatement, tableFields, condition);
+        return preparedStatement;
+    }
+    
+    private String buildDataSql(final String tableName, final Collection<String> tableFields, final Condition condition) {
+        StringBuilder sqlBuilder = new StringBuilder();
+        String selectSql = buildSelect(tableName, tableFields);
+        String whereSql = buildWhere(tableName, tableFields, condition);
+        String orderSql = buildOrder(tableFields, condition.getSort(), condition.getOrder());
+        String limitSql = buildLimit(condition.getPage(), condition.getPerPage());
+        sqlBuilder.append(selectSql).append(whereSql).append(orderSql).append(limitSql);
+        return sqlBuilder.toString();
+    }
+    
+    private String buildCountSql(final String tableName, final Collection<String> tableFields, final Condition condition) {
+        StringBuilder sqlBuilder = new StringBuilder();
+        String selectSql = buildSelectCount(tableName);
+        String whereSql = buildWhere(tableName, tableFields, condition);
+        sqlBuilder.append(selectSql).append(whereSql);
+        return sqlBuilder.toString();
+    }
+    
+    private String buildSelectCount(final String tableName) {
+        return String.format("SELECT COUNT(1) FROM %s", tableName);
+    }
+    
+    private String buildSelect(final String tableName, final Collection<String> tableFields) {
+        StringBuilder sqlBuilder = new StringBuilder();
+        sqlBuilder.append("SELECT ");
+        for (String each : tableFields) {
+            sqlBuilder.append(each).append(",");
+        }
+        sqlBuilder.deleteCharAt(sqlBuilder.length() - 1);
+        sqlBuilder.append(" FROM ").append(tableName);
+        return sqlBuilder.toString();
+    }
+    
+    private String buildWhere(final String tableName, final Collection<String> tableFields, final Condition condition) {
+        StringBuilder sqlBuilder = new StringBuilder();
+        sqlBuilder.append(" WHERE 1=1");
+        if (null != condition.getFields() && !condition.getFields().isEmpty()) {
+            for (Map.Entry<String, Object> entry : condition.getFields().entrySet()) {
+                String lowerUnderscore = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, entry.getKey());
+                if (null != entry.getValue() && tableFields.contains(lowerUnderscore)) {
+                    sqlBuilder.append(" AND ").append(lowerUnderscore).append("=?");
+                }
+            }
+        }
+        if (null != condition.getStartTime()) {
+            sqlBuilder.append(" AND ").append(getTableTimeField(tableName)).append(">=?");
+        }
+        if (null != condition.getEndTime()) {
+            sqlBuilder.append(" AND ").append(getTableTimeField(tableName)).append("<=?");
+        }
+        return sqlBuilder.toString();
+    }
+    
+    private void setBindValue(final PreparedStatement preparedStatement, final Collection<String> tableFields, final Condition condition) throws SQLException {
+        int index = 1;
+        if (null != condition.getFields() && !condition.getFields().isEmpty()) {
+            for (Map.Entry<String, Object> entry : condition.getFields().entrySet()) {
+                String lowerUnderscore = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, entry.getKey());
+                if (null != entry.getValue() && tableFields.contains(lowerUnderscore)) {
+                    preparedStatement.setString(index++, String.valueOf(entry.getValue()));
+                }
+            }
+        }
+        if (null != condition.getStartTime()) {
+            preparedStatement.setTimestamp(index++, new Timestamp(condition.getStartTime().getTime()));
+        }
+        if (null != condition.getEndTime()) {
+            preparedStatement.setTimestamp(index, new Timestamp(condition.getEndTime().getTime()));
+        }
+    }
+    
+    private String getTableTimeField(final String tableName) {
+        String result = "";
+        if (TABLE_JOB_EXECUTION_LOG.equals(tableName)) {
+            result = "start_time";
+        } else if (TABLE_JOB_STATUS_TRACE_LOG.equals(tableName)) {
+            result = "creation_time";
+        }
+        return result;
+    }
+    
+    private String buildOrder(final Collection<String> tableFields, final String sortName, final String sortOrder) {
+        if (Strings.isNullOrEmpty(sortName)) {
+            return "";
+        }
+        String lowerUnderscore = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, sortName);
+        if (!tableFields.contains(lowerUnderscore)) {
+            return "";
+        }
+        StringBuilder sqlBuilder = new StringBuilder();
+        sqlBuilder.append(" ORDER BY ").append(lowerUnderscore);
+        switch (sortOrder.toUpperCase()) {
+            case "ASC":
+                sqlBuilder.append(" ASC");
+                break;
+            case "DESC":
+                sqlBuilder.append(" DESC");
+                break;
+            default :
+                sqlBuilder.append(" ASC");
+        }
+        return sqlBuilder.toString();
+    }
+    
+    private String buildLimit(final int page, final int perPage) {
+        StringBuilder sqlBuilder = new StringBuilder();
+        if (page > 0 && perPage > 0) {
+            sqlBuilder.append(" LIMIT ").append((page - 1) * perPage).append(",").append(perPage);
+        } else {
+            sqlBuilder.append(" LIMIT ").append(Condition.DEFAULT_PAGE_SIZE);
+        }
+        return sqlBuilder.toString();
+    }
+    
+    /**
+     * 查询条件对象.
+     * 
+     * @author liguangyun
+     */
+    @RequiredArgsConstructor
+    @Getter
+    public static class Condition {
+        
+        private static final int DEFAULT_PAGE_SIZE = 10;
+        
+        private final int perPage;
+        
+        private final int page;
+        
+        private final String sort;
+        
+        private final String order;
+        
+        private final Date startTime;
+        
+        private final Date endTime;
+        
+        private final Map<String, Object> fields;
+    }
+    
+    @RequiredArgsConstructor
+    @Getter
+    public static class Result<T> {
+        
+        private final Integer total;
+        
+        private final List<T> rows;
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/rdb/JobEventRdbStorage.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/rdb/JobEventRdbStorage.java
new file mode 100644
index 0000000..c9f1d9a
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/rdb/JobEventRdbStorage.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event.rdb;
+
+import com.google.common.base.Strings;
+import io.elasticjob.cloud.context.ExecutionType;
+import io.elasticjob.cloud.event.type.JobExecutionEvent;
+import io.elasticjob.cloud.event.type.JobStatusTraceEvent;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * 运行痕迹事件数据库存储.
+ *
+ * @author caohao
+ */
+@Slf4j
+final class JobEventRdbStorage {
+    
+    private static final String TABLE_JOB_EXECUTION_LOG = "JOB_EXECUTION_LOG";
+    
+    private static final String TABLE_JOB_STATUS_TRACE_LOG = "JOB_STATUS_TRACE_LOG";
+    
+    private static final String TASK_ID_STATE_INDEX = "TASK_ID_STATE_INDEX";
+    
+    private final DataSource dataSource;
+    
+    private DatabaseType databaseType;
+    
+    JobEventRdbStorage(final DataSource dataSource) throws SQLException {
+        this.dataSource = dataSource;
+        initTablesAndIndexes();
+    }
+    
+    private void initTablesAndIndexes() throws SQLException {
+        try (Connection conn = dataSource.getConnection()) {
+            createJobExecutionTableAndIndexIfNeeded(conn);
+            createJobStatusTraceTableAndIndexIfNeeded(conn);
+            databaseType = DatabaseType.valueFrom(conn.getMetaData().getDatabaseProductName());
+        }
+    }
+    
+    private void createJobExecutionTableAndIndexIfNeeded(final Connection conn) throws SQLException {
+        DatabaseMetaData dbMetaData = conn.getMetaData();
+        try (ResultSet resultSet = dbMetaData.getTables(null, null, TABLE_JOB_EXECUTION_LOG, new String[]{"TABLE"})) {
+            if (!resultSet.next()) {
+                createJobExecutionTable(conn);
+            }
+        }
+    }
+    
+    private void createJobStatusTraceTableAndIndexIfNeeded(final Connection conn) throws SQLException {
+        DatabaseMetaData dbMetaData = conn.getMetaData();
+        try (ResultSet resultSet = dbMetaData.getTables(null, null, TABLE_JOB_STATUS_TRACE_LOG, new String[]{"TABLE"})) {
+            if (!resultSet.next()) {
+                createJobStatusTraceTable(conn);
+            }
+        }
+        createTaskIdIndexIfNeeded(conn, TABLE_JOB_STATUS_TRACE_LOG, TASK_ID_STATE_INDEX);
+    }
+    
+    private void createTaskIdIndexIfNeeded(final Connection conn, final String tableName, final String indexName) throws SQLException {
+        DatabaseMetaData dbMetaData = conn.getMetaData();
+        try (ResultSet resultSet = dbMetaData.getIndexInfo(null, null, tableName, false, false)) {
+            boolean hasTaskIdIndex = false;
+            while (resultSet.next()) {
+                if (indexName.equals(resultSet.getString("INDEX_NAME"))) {
+                    hasTaskIdIndex = true;    
+                }
+            }
+            if (!hasTaskIdIndex) {
+                createTaskIdAndStateIndex(conn, tableName);
+            }
+        }
+    }
+    
+    private void createJobExecutionTable(final Connection conn) throws SQLException {
+        String dbSchema = "CREATE TABLE `" + TABLE_JOB_EXECUTION_LOG + "` ("
+                + "`id` VARCHAR(40) NOT NULL, "
+                + "`job_name` VARCHAR(100) NOT NULL, "
+                + "`task_id` VARCHAR(255) NOT NULL, "
+                + "`hostname` VARCHAR(255) NOT NULL, "
+                + "`ip` VARCHAR(50) NOT NULL, "
+                + "`sharding_item` INT NOT NULL, "
+                + "`execution_source` VARCHAR(20) NOT NULL, "
+                + "`failure_cause` VARCHAR(4000) NULL, "
+                + "`is_success` INT NOT NULL, "
+                + "`start_time` TIMESTAMP NULL, "
+                + "`complete_time` TIMESTAMP NULL, "
+                + "PRIMARY KEY (`id`));";
+        try (PreparedStatement preparedStatement = conn.prepareStatement(dbSchema)) {
+            preparedStatement.execute();
+        }
+    }
+    
+    private void createJobStatusTraceTable(final Connection conn) throws SQLException {
+        String dbSchema = "CREATE TABLE `" + TABLE_JOB_STATUS_TRACE_LOG + "` ("
+                + "`id` VARCHAR(40) NOT NULL, "
+                + "`job_name` VARCHAR(100) NOT NULL, "
+                + "`original_task_id` VARCHAR(255) NOT NULL, "
+                + "`task_id` VARCHAR(255) NOT NULL, "
+                + "`slave_id` VARCHAR(50) NOT NULL, "
+                + "`source` VARCHAR(50) NOT NULL, "
+                + "`execution_type` VARCHAR(20) NOT NULL, "
+                + "`sharding_item` VARCHAR(100) NOT NULL, "
+                + "`state` VARCHAR(20) NOT NULL, "
+                + "`message` VARCHAR(4000) NULL, "
+                + "`creation_time` TIMESTAMP NULL, "
+                + "PRIMARY KEY (`id`));";
+        try (PreparedStatement preparedStatement = conn.prepareStatement(dbSchema)) {
+            preparedStatement.execute();
+        }
+    }
+    
+    private void createTaskIdAndStateIndex(final Connection conn, final String tableName) throws SQLException {
+        String sql = "CREATE INDEX " + TASK_ID_STATE_INDEX + " ON " + tableName + " (`task_id`, `state`);";
+        try (PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
+            preparedStatement.execute();
+        }
+    }
+    
+    boolean addJobExecutionEvent(final JobExecutionEvent jobExecutionEvent) {
+        if (null == jobExecutionEvent.getCompleteTime()) {
+            return insertJobExecutionEvent(jobExecutionEvent);
+        } else {
+            if (jobExecutionEvent.isSuccess()) {
+                return updateJobExecutionEventWhenSuccess(jobExecutionEvent);
+            } else {
+                return updateJobExecutionEventFailure(jobExecutionEvent);
+            }
+        }
+    }
+    
+    private boolean insertJobExecutionEvent(final JobExecutionEvent jobExecutionEvent) {
+        boolean result = false;
+        String sql = "INSERT INTO `" + TABLE_JOB_EXECUTION_LOG + "` (`id`, `job_name`, `task_id`, `hostname`, `ip`, `sharding_item`, `execution_source`, `is_success`, `start_time`) "
+                + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);";
+        try (
+                Connection conn = dataSource.getConnection();
+                PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
+            preparedStatement.setString(1, jobExecutionEvent.getId());
+            preparedStatement.setString(2, jobExecutionEvent.getJobName());
+            preparedStatement.setString(3, jobExecutionEvent.getTaskId());
+            preparedStatement.setString(4, jobExecutionEvent.getHostname());
+            preparedStatement.setString(5, jobExecutionEvent.getIp());
+            preparedStatement.setInt(6, jobExecutionEvent.getShardingItem());
+            preparedStatement.setString(7, jobExecutionEvent.getSource().toString());
+            preparedStatement.setBoolean(8, jobExecutionEvent.isSuccess());
+            preparedStatement.setTimestamp(9, new Timestamp(jobExecutionEvent.getStartTime().getTime()));
+            preparedStatement.execute();
+            result = true;
+        } catch (final SQLException ex) {
+            if (!isDuplicateRecord(ex)) {
+                // TODO 记录失败直接输出日志,未来可考虑配置化
+                log.error(ex.getMessage());    
+            }
+        }
+        return result;
+    }
+    
+    private boolean isDuplicateRecord(final SQLException ex) {
+        return DatabaseType.MySQL.equals(databaseType) && 1062 == ex.getErrorCode() || DatabaseType.H2.equals(databaseType) && 23505 == ex.getErrorCode() 
+                || DatabaseType.SQLServer.equals(databaseType) && 1 == ex.getErrorCode() || DatabaseType.DB2.equals(databaseType) && -803 == ex.getErrorCode()
+                || DatabaseType.PostgreSQL.equals(databaseType) && 0 == ex.getErrorCode() || DatabaseType.Oracle.equals(databaseType) && 1 == ex.getErrorCode();
+    }
+    
+    private boolean updateJobExecutionEventWhenSuccess(final JobExecutionEvent jobExecutionEvent) {
+        boolean result = false;
+        String sql = "UPDATE `" + TABLE_JOB_EXECUTION_LOG + "` SET `is_success` = ?, `complete_time` = ? WHERE id = ?";
+        try (
+                Connection conn = dataSource.getConnection();
+                PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
+            preparedStatement.setBoolean(1, jobExecutionEvent.isSuccess());
+            preparedStatement.setTimestamp(2, new Timestamp(jobExecutionEvent.getCompleteTime().getTime()));
+            preparedStatement.setString(3, jobExecutionEvent.getId());
+            if (0 == preparedStatement.executeUpdate()) {
+                return insertJobExecutionEventWhenSuccess(jobExecutionEvent);
+            }
+            result = true;
+        } catch (final SQLException ex) {
+            // TODO 记录失败直接输出日志,未来可考虑配置化
+            log.error(ex.getMessage());
+        }
+        return result;
+    }
+    
+    private boolean insertJobExecutionEventWhenSuccess(final JobExecutionEvent jobExecutionEvent) {
+        boolean result = false;
+        String sql = "INSERT INTO `" + TABLE_JOB_EXECUTION_LOG + "` (`id`, `job_name`, `task_id`, `hostname`, `ip`, `sharding_item`, `execution_source`, `is_success`, `start_time`, `complete_time`) "
+                + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);";
+        try (
+                Connection conn = dataSource.getConnection();
+                PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
+            preparedStatement.setString(1, jobExecutionEvent.getId());
+            preparedStatement.setString(2, jobExecutionEvent.getJobName());
+            preparedStatement.setString(3, jobExecutionEvent.getTaskId());
+            preparedStatement.setString(4, jobExecutionEvent.getHostname());
+            preparedStatement.setString(5, jobExecutionEvent.getIp());
+            preparedStatement.setInt(6, jobExecutionEvent.getShardingItem());
+            preparedStatement.setString(7, jobExecutionEvent.getSource().toString());
+            preparedStatement.setBoolean(8, jobExecutionEvent.isSuccess());
+            preparedStatement.setTimestamp(9, new Timestamp(jobExecutionEvent.getStartTime().getTime()));
+            preparedStatement.setTimestamp(10, new Timestamp(jobExecutionEvent.getCompleteTime().getTime()));
+            preparedStatement.execute();
+            result = true;
+        } catch (final SQLException ex) {
+            if (isDuplicateRecord(ex)) {
+                return updateJobExecutionEventWhenSuccess(jobExecutionEvent);
+            }
+            // TODO 记录失败直接输出日志,未来可考虑配置化
+            log.error(ex.getMessage());
+        }
+        return result;
+    }
+    
+    private boolean updateJobExecutionEventFailure(final JobExecutionEvent jobExecutionEvent) {
+        boolean result = false;
+        String sql = "UPDATE `" + TABLE_JOB_EXECUTION_LOG + "` SET `is_success` = ?, `complete_time` = ?, `failure_cause` = ? WHERE id = ?";
+        try (
+                Connection conn = dataSource.getConnection();
+                PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
+            preparedStatement.setBoolean(1, jobExecutionEvent.isSuccess());
+            preparedStatement.setTimestamp(2, new Timestamp(jobExecutionEvent.getCompleteTime().getTime()));
+            preparedStatement.setString(3, truncateString(jobExecutionEvent.getFailureCause()));
+            preparedStatement.setString(4, jobExecutionEvent.getId());
+            if (0 == preparedStatement.executeUpdate()) {
+                return insertJobExecutionEventWhenFailure(jobExecutionEvent);
+            }
+            result = true;
+        } catch (final SQLException ex) {
+            // TODO 记录失败直接输出日志,未来可考虑配置化
+            log.error(ex.getMessage());
+        }
+        return result;
+    }
+    
+    private boolean insertJobExecutionEventWhenFailure(final JobExecutionEvent jobExecutionEvent) {
+        boolean result = false;
+        String sql = "INSERT INTO `" + TABLE_JOB_EXECUTION_LOG + "` (`id`, `job_name`, `task_id`, `hostname`, `ip`, `sharding_item`, `execution_source`, `failure_cause`, `is_success`, `start_time`) "
+                + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);";
+        try (
+                Connection conn = dataSource.getConnection();
+                PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
+            preparedStatement.setString(1, jobExecutionEvent.getId());
+            preparedStatement.setString(2, jobExecutionEvent.getJobName());
+            preparedStatement.setString(3, jobExecutionEvent.getTaskId());
+            preparedStatement.setString(4, jobExecutionEvent.getHostname());
+            preparedStatement.setString(5, jobExecutionEvent.getIp());
+            preparedStatement.setInt(6, jobExecutionEvent.getShardingItem());
+            preparedStatement.setString(7, jobExecutionEvent.getSource().toString());
+            preparedStatement.setString(8, truncateString(jobExecutionEvent.getFailureCause()));
+            preparedStatement.setBoolean(9, jobExecutionEvent.isSuccess());
+            preparedStatement.setTimestamp(10, new Timestamp(jobExecutionEvent.getStartTime().getTime()));
+            preparedStatement.execute();
+            result = true;
+        } catch (final SQLException ex) {
+            if (isDuplicateRecord(ex)) {
+                return updateJobExecutionEventFailure(jobExecutionEvent);
+            }
+            // TODO 记录失败直接输出日志,未来可考虑配置化
+            log.error(ex.getMessage());
+        }
+        return result;
+    }
+    
+    boolean addJobStatusTraceEvent(final JobStatusTraceEvent jobStatusTraceEvent) {
+        String originalTaskId = jobStatusTraceEvent.getOriginalTaskId();
+        if (JobStatusTraceEvent.State.TASK_STAGING != jobStatusTraceEvent.getState()) {
+            originalTaskId = getOriginalTaskId(jobStatusTraceEvent.getTaskId());
+        }
+        boolean result = false;
+        String sql = "INSERT INTO `" + TABLE_JOB_STATUS_TRACE_LOG + "` (`id`, `job_name`, `original_task_id`, `task_id`, `slave_id`, `source`, `execution_type`, `sharding_item`,  " 
+                + "`state`, `message`, `creation_time`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);";
+        try (
+                Connection conn = dataSource.getConnection();
+                PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
+            preparedStatement.setString(1, UUID.randomUUID().toString());
+            preparedStatement.setString(2, jobStatusTraceEvent.getJobName());
+            preparedStatement.setString(3, originalTaskId);
+            preparedStatement.setString(4, jobStatusTraceEvent.getTaskId());
+            preparedStatement.setString(5, jobStatusTraceEvent.getSlaveId());
+            preparedStatement.setString(6, jobStatusTraceEvent.getSource().toString());
+            preparedStatement.setString(7, jobStatusTraceEvent.getExecutionType().name());
+            preparedStatement.setString(8, jobStatusTraceEvent.getShardingItems());
+            preparedStatement.setString(9, jobStatusTraceEvent.getState().toString());
+            preparedStatement.setString(10, truncateString(jobStatusTraceEvent.getMessage()));
+            preparedStatement.setTimestamp(11, new Timestamp(jobStatusTraceEvent.getCreationTime().getTime()));
+            preparedStatement.execute();
+            result = true;
+        } catch (final SQLException ex) {
+            // TODO 记录失败直接输出日志,未来可考虑配置化
+            log.error(ex.getMessage());
+        }
+        return result;
+    }
+    
+    private String getOriginalTaskId(final String taskId) {
+        String sql = String.format("SELECT original_task_id FROM %s WHERE task_id = '%s' and state='%s'", TABLE_JOB_STATUS_TRACE_LOG, taskId, JobStatusTraceEvent.State.TASK_STAGING);
+        String result = "";
+        try (
+                Connection conn = dataSource.getConnection();
+                PreparedStatement preparedStatement = conn.prepareStatement(sql);
+                ResultSet resultSet = preparedStatement.executeQuery()
+        ) {
+            if (resultSet.next()) {
+                return resultSet.getString("original_task_id");
+            }
+        } catch (final SQLException ex) {
+            // TODO 记录失败直接输出日志,未来可考虑配置化
+            log.error(ex.getMessage());
+        }
+        return result;
+    }
+    
+    private String truncateString(final String str) {
+        return !Strings.isNullOrEmpty(str) && str.length() > 4000 ? str.substring(0, 4000) : str;
+    }
+    
+    List<JobStatusTraceEvent> getJobStatusTraceEvents(final String taskId) {
+        String sql = String.format("SELECT * FROM %s WHERE task_id = '%s'", TABLE_JOB_STATUS_TRACE_LOG, taskId);
+        List<JobStatusTraceEvent> result = new ArrayList<>();
+        try (
+                Connection conn = dataSource.getConnection();
+                PreparedStatement preparedStatement = conn.prepareStatement(sql);
+                ResultSet resultSet = preparedStatement.executeQuery()
+                ) {
+            while (resultSet.next()) {
+                JobStatusTraceEvent jobStatusTraceEvent = new JobStatusTraceEvent(resultSet.getString(1), resultSet.getString(2), resultSet.getString(3), resultSet.getString(4),
+                        resultSet.getString(5), JobStatusTraceEvent.Source.valueOf(resultSet.getString(6)), ExecutionType.valueOf(resultSet.getString(7)), resultSet.getString(8),
+                        JobStatusTraceEvent.State.valueOf(resultSet.getString(9)), resultSet.getString(10), new SimpleDateFormat("yyyy-mm-dd HH:MM:SS").parse(resultSet.getString(11)));
+                result.add(jobStatusTraceEvent);
+            }
+        } catch (final SQLException | ParseException ex) {
+            // TODO 记录失败直接输出日志,未来可考虑配置化
+            log.error(ex.getMessage());
+        }
+        return result;
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/type/JobExecutionEvent.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/type/JobExecutionEvent.java
new file mode 100644
index 0000000..5e34dac
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/type/JobExecutionEvent.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event.type;
+
+import io.elasticjob.cloud.exception.ExceptionUtil;
+import io.elasticjob.cloud.event.JobEvent;
+import io.elasticjob.cloud.util.env.IpUtils;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+
+import java.util.Date;
+import java.util.UUID;
+
+/**
+ * 作业执行事件.
+ *
+ * @author zhangliang
+ */
+@RequiredArgsConstructor
+@AllArgsConstructor
+@Getter
+public final class JobExecutionEvent implements JobEvent {
+    
+    private String id = UUID.randomUUID().toString();
+    
+    private String hostname = IpUtils.getHostName();
+    
+    private String ip = IpUtils.getIp();
+    
+    private final String taskId;
+    
+    private final String jobName;
+    
+    private final ExecutionSource source;
+    
+    private final int shardingItem;
+    
+    private Date startTime = new Date();
+    
+    @Setter
+    private Date completeTime;
+    
+    @Setter
+    private boolean success;
+    
+    @Setter
+    private JobExecutionEventThrowable failureCause;
+    
+    /**
+     * 作业执行成功.
+     * 
+     * @return 作业执行事件
+     */
+    public JobExecutionEvent executionSuccess() {
+        JobExecutionEvent result = new JobExecutionEvent(id, hostname, ip, taskId, jobName, source, shardingItem, startTime, completeTime, success, failureCause);
+        result.setCompleteTime(new Date());
+        result.setSuccess(true);
+        return result;
+    }
+    
+    /**
+     * 作业执行失败.
+     * 
+     * @param failureCause 失败原因
+     * @return 作业执行事件
+     */
+    public JobExecutionEvent executionFailure(final Throwable failureCause) {
+        JobExecutionEvent result = new JobExecutionEvent(id, hostname, ip, taskId, jobName, source, shardingItem, startTime, completeTime, success, new JobExecutionEventThrowable(failureCause));
+        result.setCompleteTime(new Date());
+        result.setSuccess(false);
+        return result;
+    }
+    
+    /**
+     * 获取失败原因.
+     * 
+     * @return 失败原因
+     */
+    public String getFailureCause() {
+        return ExceptionUtil.transform(failureCause == null ? null : failureCause.getThrowable());
+    }
+    
+    /**
+     * 执行来源.
+     */
+    public enum ExecutionSource {
+        NORMAL_TRIGGER, MISFIRE, FAILOVER
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/type/JobExecutionEventThrowable.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/type/JobExecutionEventThrowable.java
new file mode 100644
index 0000000..c05c2a0
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/type/JobExecutionEventThrowable.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event.type;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.ToString;
+
+/**
+ * 作业执行事件Throwable.
+ *
+ * @author liguangyun
+ */
+@RequiredArgsConstructor
+@AllArgsConstructor
+@Getter
+@ToString(of = "plainText")
+public final class JobExecutionEventThrowable {
+    
+    private final Throwable throwable;
+    
+    private String plainText;
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/type/JobStatusTraceEvent.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/type/JobStatusTraceEvent.java
new file mode 100644
index 0000000..4a80937
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/event/type/JobStatusTraceEvent.java
@@ -0,0 +1,53 @@
+package io.elasticjob.cloud.event.type;
+
+import io.elasticjob.cloud.context.ExecutionType;
+import io.elasticjob.cloud.event.JobEvent;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+
+import java.util.Date;
+import java.util.UUID;
+
+/**
+ * 作业状态痕迹事件.
+ *
+ * @author caohao
+ */
+@RequiredArgsConstructor
+@AllArgsConstructor
+@Getter
+public final class JobStatusTraceEvent implements JobEvent {
+    
+    private String id = UUID.randomUUID().toString();
+    
+    private final String jobName;
+    
+    @Setter
+    private String originalTaskId = "";
+    
+    private final String taskId;
+    
+    private final String slaveId;
+    
+    private final Source source;
+    
+    private final ExecutionType executionType;
+    
+    private final String shardingItems;
+    
+    private final State state;
+    
+    private final String message;
+    
+    private Date creationTime = new Date();
+    
+    public enum State {
+        TASK_STAGING, TASK_RUNNING, TASK_FINISHED, TASK_KILLED, TASK_LOST, TASK_FAILED, TASK_ERROR, TASK_DROPPED, TASK_GONE, TASK_GONE_BY_OPERATOR, TASK_UNREACHABLE, TASK_UNKNOWN
+    }
+    
+    public enum Source {
+        CLOUD_SCHEDULER, CLOUD_EXECUTOR, LITE_EXECUTOR
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/exception/AppConfigurationException.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/exception/AppConfigurationException.java
new file mode 100644
index 0000000..85b201c
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/exception/AppConfigurationException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.exception;
+
+/**
+ * 作业App配置异常.
+ * 
+ * @author caohao
+ */
+public final class AppConfigurationException extends RuntimeException {
+    
+    private static final long serialVersionUID = -1466479389299512371L;
+    
+    public AppConfigurationException(final String errorMessage, final Object... args) {
+        super(String.format(errorMessage, args));
+    }
+    
+    public AppConfigurationException(final Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/exception/ExceptionUtil.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/exception/ExceptionUtil.java
new file mode 100644
index 0000000..c044e26
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/exception/ExceptionUtil.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.exception;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * 异常处理工具类.
+ *
+ * @author caohao
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class ExceptionUtil {
+    
+    /**
+     * 将Throwable异常转换为字符串.
+     *
+     * @param cause 需要转换的异常
+     * @return 转换后的异常字符串
+     */
+    public static String transform(final Throwable cause) {
+        if (null == cause) {
+            return "";
+        }
+        StringWriter result = new StringWriter();
+        try (PrintWriter writer = new PrintWriter(result)) {
+            cause.printStackTrace(writer);
+        }
+        return result.toString();
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/exception/JobConfigurationException.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/exception/JobConfigurationException.java
new file mode 100644
index 0000000..c49b907
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/exception/JobConfigurationException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.exception;
+
+/**
+ * 作业配置异常.
+ * 
+ * @author zhangliang
+ */
+public final class JobConfigurationException extends RuntimeException {
+    
+    private static final long serialVersionUID = 3244988974343209468L;
+    
+    public JobConfigurationException(final String errorMessage, final Object... args) {
+        super(String.format(errorMessage, args));
+    }
+    
+    public JobConfigurationException(final Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/exception/JobExecutionEnvironmentException.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/exception/JobExecutionEnvironmentException.java
new file mode 100644
index 0000000..d0f7955
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/exception/JobExecutionEnvironmentException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.exception;
+
+/**
+ * 作业执行环境异常.
+ * 
+ * @author zhangliang
+ */
+public final class JobExecutionEnvironmentException extends Exception {
+    
+    private static final long serialVersionUID = -6670738108926897433L;
+    
+    public JobExecutionEnvironmentException(final String errorMessage, final Object... args) {
+        super(String.format(errorMessage, args));
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/exception/JobStatisticException.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/exception/JobStatisticException.java
new file mode 100644
index 0000000..f55f105
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/exception/JobStatisticException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.exception;
+
+/**
+ * 作业统计异常.
+ * 
+ * @author liguangyun
+ */
+public final class JobStatisticException extends RuntimeException {
+    
+    private static final long serialVersionUID = -2502533914008085601L;
+    
+    public JobStatisticException(final Exception ex) {
+        super(ex);
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/exception/JobSystemException.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/exception/JobSystemException.java
new file mode 100644
index 0000000..432fa92
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/exception/JobSystemException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.exception;
+
+/**
+ * 作业系统异常.
+ * 
+ * @author zhangliang
+ */
+public final class JobSystemException extends RuntimeException {
+    
+    private static final long serialVersionUID = 5018901344199973515L;
+    
+    public JobSystemException(final String errorMessage, final Object... args) {
+        super(String.format(errorMessage, args));
+    }
+    
+    public JobSystemException(final Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/AbstractElasticJobExecutor.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/AbstractElasticJobExecutor.java
new file mode 100644
index 0000000..32d8e6e
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/AbstractElasticJobExecutor.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.executor;
+
+import io.elasticjob.cloud.api.ShardingContext;
+import io.elasticjob.cloud.exception.ExceptionUtil;
+import io.elasticjob.cloud.executor.handler.JobExceptionHandler;
+import io.elasticjob.cloud.config.JobRootConfiguration;
+import io.elasticjob.cloud.event.type.JobExecutionEvent;
+import io.elasticjob.cloud.event.type.JobStatusTraceEvent;
+import io.elasticjob.cloud.exception.JobExecutionEnvironmentException;
+import io.elasticjob.cloud.exception.JobSystemException;
+import io.elasticjob.cloud.executor.handler.ExecutorServiceHandler;
+import io.elasticjob.cloud.executor.handler.ExecutorServiceHandlerRegistry;
+import io.elasticjob.cloud.executor.handler.JobProperties;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * 弹性化分布式作业执行器.
+ *
+ * @author zhangliang
+ */
+@Slf4j
+public abstract class AbstractElasticJobExecutor {
+    
+    @Getter(AccessLevel.PROTECTED)
+    private final JobFacade jobFacade;
+    
+    @Getter(AccessLevel.PROTECTED)
+    private final JobRootConfiguration jobRootConfig;
+    
+    private final String jobName;
+    
+    private final ExecutorService executorService;
+    
+    private final JobExceptionHandler jobExceptionHandler;
+    
+    private final Map<Integer, String> itemErrorMessages;
+    
+    protected AbstractElasticJobExecutor(final JobFacade jobFacade) {
+        this.jobFacade = jobFacade;
+        jobRootConfig = jobFacade.loadJobRootConfiguration(true);
+        jobName = jobRootConfig.getTypeConfig().getCoreConfig().getJobName();
+        executorService = ExecutorServiceHandlerRegistry.getExecutorServiceHandler(jobName, (ExecutorServiceHandler) getHandler(JobProperties.JobPropertiesEnum.EXECUTOR_SERVICE_HANDLER));
+        jobExceptionHandler = (JobExceptionHandler) getHandler(JobProperties.JobPropertiesEnum.JOB_EXCEPTION_HANDLER);
+        itemErrorMessages = new ConcurrentHashMap<>(jobRootConfig.getTypeConfig().getCoreConfig().getShardingTotalCount(), 1);
+    }
+    
+    private Object getHandler(final JobProperties.JobPropertiesEnum jobPropertiesEnum) {
+        String handlerClassName = jobRootConfig.getTypeConfig().getCoreConfig().getJobProperties().get(jobPropertiesEnum);
+        try {
+            Class<?> handlerClass = Class.forName(handlerClassName);
+            if (jobPropertiesEnum.getClassType().isAssignableFrom(handlerClass)) {
+                return handlerClass.newInstance();
+            }
+            return getDefaultHandler(jobPropertiesEnum, handlerClassName);
+        } catch (final ReflectiveOperationException ex) {
+            return getDefaultHandler(jobPropertiesEnum, handlerClassName);
+        }
+    }
+    
+    private Object getDefaultHandler(final JobProperties.JobPropertiesEnum jobPropertiesEnum, final String handlerClassName) {
+        log.warn("Cannot instantiation class '{}', use default '{}' class.", handlerClassName, jobPropertiesEnum.getKey());
+        try {
+            return Class.forName(jobPropertiesEnum.getDefaultValue()).newInstance();
+        } catch (final ClassNotFoundException | InstantiationException | IllegalAccessException e) {
+            throw new JobSystemException(e);
+        }
+    }
+    
+    /**
+     * 执行作业.
+     */
+    public final void execute() {
+        try {
+            jobFacade.checkJobExecutionEnvironment();
+        } catch (final JobExecutionEnvironmentException cause) {
+            jobExceptionHandler.handleException(jobName, cause);
+        }
+        ShardingContexts shardingContexts = jobFacade.getShardingContexts();
+        if (shardingContexts.isAllowSendJobEvent()) {
+            jobFacade.postJobStatusTraceEvent(shardingContexts.getTaskId(), JobStatusTraceEvent.State.TASK_STAGING, String.format("Job '%s' execute begin.", jobName));
+        }
+        if (jobFacade.misfireIfRunning(shardingContexts.getShardingItemParameters().keySet())) {
+            if (shardingContexts.isAllowSendJobEvent()) {
+                jobFacade.postJobStatusTraceEvent(shardingContexts.getTaskId(), JobStatusTraceEvent.State.TASK_FINISHED, String.format(
+                        "Previous job '%s' - shardingItems '%s' is still running, misfired job will start after previous job completed.", jobName, 
+                        shardingContexts.getShardingItemParameters().keySet()));
+            }
+            return;
+        }
+        try {
+            jobFacade.beforeJobExecuted(shardingContexts);
+            //CHECKSTYLE:OFF
+        } catch (final Throwable cause) {
+            //CHECKSTYLE:ON
+            jobExceptionHandler.handleException(jobName, cause);
+        }
+        execute(shardingContexts, JobExecutionEvent.ExecutionSource.NORMAL_TRIGGER);
+        while (jobFacade.isExecuteMisfired(shardingContexts.getShardingItemParameters().keySet())) {
+            jobFacade.clearMisfire(shardingContexts.getShardingItemParameters().keySet());
+            execute(shardingContexts, JobExecutionEvent.ExecutionSource.MISFIRE);
+        }
+        jobFacade.failoverIfNecessary();
+        try {
+            jobFacade.afterJobExecuted(shardingContexts);
+            //CHECKSTYLE:OFF
+        } catch (final Throwable cause) {
+            //CHECKSTYLE:ON
+            jobExceptionHandler.handleException(jobName, cause);
+        }
+    }
+    
+    private void execute(final ShardingContexts shardingContexts, final JobExecutionEvent.ExecutionSource executionSource) {
+        if (shardingContexts.getShardingItemParameters().isEmpty()) {
+            if (shardingContexts.isAllowSendJobEvent()) {
+                jobFacade.postJobStatusTraceEvent(shardingContexts.getTaskId(), JobStatusTraceEvent.State.TASK_FINISHED, String.format("Sharding item for job '%s' is empty.", jobName));
+            }
+            return;
+        }
+        jobFacade.registerJobBegin(shardingContexts);
+        String taskId = shardingContexts.getTaskId();
+        if (shardingContexts.isAllowSendJobEvent()) {
+            jobFacade.postJobStatusTraceEvent(taskId, JobStatusTraceEvent.State.TASK_RUNNING, "");
+        }
+        try {
+            process(shardingContexts, executionSource);
+        } finally {
+            // TODO 考虑增加作业失败的状态,并且考虑如何处理作业失败的整体回路
+            jobFacade.registerJobCompleted(shardingContexts);
+            if (itemErrorMessages.isEmpty()) {
+                if (shardingContexts.isAllowSendJobEvent()) {
+                    jobFacade.postJobStatusTraceEvent(taskId, JobStatusTraceEvent.State.TASK_FINISHED, "");
+                }
+            } else {
+                if (shardingContexts.isAllowSendJobEvent()) {
+                    jobFacade.postJobStatusTraceEvent(taskId, JobStatusTraceEvent.State.TASK_ERROR, itemErrorMessages.toString());
+                }
+            }
+        }
+    }
+    
+    private void process(final ShardingContexts shardingContexts, final JobExecutionEvent.ExecutionSource executionSource) {
+        Collection<Integer> items = shardingContexts.getShardingItemParameters().keySet();
+        if (1 == items.size()) {
+            int item = shardingContexts.getShardingItemParameters().keySet().iterator().next();
+            JobExecutionEvent jobExecutionEvent =  new JobExecutionEvent(shardingContexts.getTaskId(), jobName, executionSource, item);
+            process(shardingContexts, item, jobExecutionEvent);
+            return;
+        }
+        final CountDownLatch latch = new CountDownLatch(items.size());
+        for (final int each : items) {
+            final JobExecutionEvent jobExecutionEvent = new JobExecutionEvent(shardingContexts.getTaskId(), jobName, executionSource, each);
+            if (executorService.isShutdown()) {
+                return;
+            }
+            executorService.submit(new Runnable() {
+                
+                @Override
+                public void run() {
+                    try {
+                        process(shardingContexts, each, jobExecutionEvent);
+                    } finally {
+                        latch.countDown();
+                    }
+                }
+            });
+        }
+        try {
+            latch.await();
+        } catch (final InterruptedException ex) {
+            Thread.currentThread().interrupt();
+        }
+    }
+    
+    private void process(final ShardingContexts shardingContexts, final int item, final JobExecutionEvent startEvent) {
+        if (shardingContexts.isAllowSendJobEvent()) {
+            jobFacade.postJobExecutionEvent(startEvent);
+        }
+        log.trace("Job '{}' executing, item is: '{}'.", jobName, item);
+        JobExecutionEvent completeEvent;
+        try {
+            process(new ShardingContext(shardingContexts, item));
+            completeEvent = startEvent.executionSuccess();
+            log.trace("Job '{}' executed, item is: '{}'.", jobName, item);
+            if (shardingContexts.isAllowSendJobEvent()) {
+                jobFacade.postJobExecutionEvent(completeEvent);
+            }
+            // CHECKSTYLE:OFF
+        } catch (final Throwable cause) {
+            // CHECKSTYLE:ON
+            completeEvent = startEvent.executionFailure(cause);
+            jobFacade.postJobExecutionEvent(completeEvent);
+            itemErrorMessages.put(item, ExceptionUtil.transform(cause));
+            jobExceptionHandler.handleException(jobName, cause);
+        }
+    }
+    
+    protected abstract void process(ShardingContext shardingContext);
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/JobExecutorFactory.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/JobExecutorFactory.java
new file mode 100644
index 0000000..116ae51
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/JobExecutorFactory.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.executor;
+
+import io.elasticjob.cloud.api.ElasticJob;
+import io.elasticjob.cloud.api.dataflow.DataflowJob;
+import io.elasticjob.cloud.api.simple.SimpleJob;
+import io.elasticjob.cloud.executor.type.DataflowJobExecutor;
+import io.elasticjob.cloud.exception.JobConfigurationException;
+import io.elasticjob.cloud.executor.type.ScriptJobExecutor;
+import io.elasticjob.cloud.executor.type.SimpleJobExecutor;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+/**
+ * 作业执行器工厂.
+ *
+ * @author zhangliang
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class JobExecutorFactory {
+    
+    /**
+     * 获取作业执行器.
+     *
+     * @param elasticJob 分布式弹性作业
+     * @param jobFacade 作业内部服务门面服务
+     * @return 作业执行器
+     */
+    @SuppressWarnings("unchecked")
+    public static AbstractElasticJobExecutor getJobExecutor(final ElasticJob elasticJob, final JobFacade jobFacade) {
+        if (null == elasticJob) {
+            return new ScriptJobExecutor(jobFacade);
+        }
+        if (elasticJob instanceof SimpleJob) {
+            return new SimpleJobExecutor((SimpleJob) elasticJob, jobFacade);
+        }
+        if (elasticJob instanceof DataflowJob) {
+            return new DataflowJobExecutor((DataflowJob) elasticJob, jobFacade);
+        }
+        throw new JobConfigurationException("Cannot support job type '%s'", elasticJob.getClass().getCanonicalName());
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/JobFacade.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/JobFacade.java
new file mode 100644
index 0000000..081e629
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/JobFacade.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.executor;
+
+import io.elasticjob.cloud.config.JobRootConfiguration;
+import io.elasticjob.cloud.event.type.JobExecutionEvent;
+import io.elasticjob.cloud.event.type.JobStatusTraceEvent;
+import io.elasticjob.cloud.exception.JobExecutionEnvironmentException;
+
+import java.util.Collection;
+
+/**
+ * 作业内部服务门面服务.
+ * 
+ * @author zhangliang
+ */
+public interface JobFacade {
+    
+    /**
+     * 读取作业配置.
+     * 
+     * @param fromCache 是否从缓存中读取
+     * @return 作业配置
+     */
+    JobRootConfiguration loadJobRootConfiguration(boolean fromCache);
+    
+    /**
+     * 检查作业执行环境.
+     * 
+     * @throws JobExecutionEnvironmentException 作业执行环境异常
+     */
+    void checkJobExecutionEnvironment() throws JobExecutionEnvironmentException;
+    
+    /**
+     * 如果需要失效转移, 则执行作业失效转移.
+     */
+    void failoverIfNecessary();
+    
+    /**
+     * 注册作业启动信息.
+     *
+     * @param shardingContexts 分片上下文
+     */
+    void registerJobBegin(ShardingContexts shardingContexts);
+    
+    /**
+     * 注册作业完成信息.
+     *
+     * @param shardingContexts 分片上下文
+     */
+    void registerJobCompleted(ShardingContexts shardingContexts);
+    
+    /**
+     * 获取当前作业服务器的分片上下文.
+     *
+     * @return 分片上下文
+     */
+    ShardingContexts getShardingContexts();
+    
+    /**
+     * 设置任务被错过执行的标记.
+     *
+     * @param shardingItems 需要设置错过执行的任务分片项
+     * @return 是否满足misfire条件
+     */
+    boolean misfireIfRunning(Collection<Integer> shardingItems);
+    
+    /**
+     * 清除任务被错过执行的标记.
+     *
+     * @param shardingItems 需要清除错过执行的任务分片项
+     */
+    void clearMisfire(Collection<Integer> shardingItems);
+    
+    /**
+     * 判断作业是否需要执行错过的任务.
+     * 
+     * @param shardingItems 任务分片项集合
+     * @return 作业是否需要执行错过的任务
+     */
+    boolean isExecuteMisfired(Collection<Integer> shardingItems);
+    
+    /**
+     * 判断作业是否符合继续运行的条件.
+     * 
+     * <p>如果作业停止或需要重分片或非流式处理则作业将不会继续运行.</p>
+     * 
+     * @return 作业是否符合继续运行的条件
+     */
+    boolean isEligibleForJobRunning();
+    
+    /**判断是否需要重分片.
+     *
+     * @return 是否需要重分片
+     */
+    boolean isNeedSharding();
+    
+    /**
+     * 作业执行前的执行的方法.
+     *
+     * @param shardingContexts 分片上下文
+     */
+    void beforeJobExecuted(ShardingContexts shardingContexts);
+    
+    /**
+     * 作业执行后的执行的方法.
+     *
+     * @param shardingContexts 分片上下文
+     */
+    void afterJobExecuted(ShardingContexts shardingContexts);
+    
+    /**
+     * 发布执行事件.
+     *
+     * @param jobExecutionEvent 作业执行事件
+     */
+    void postJobExecutionEvent(JobExecutionEvent jobExecutionEvent);
+    
+    /**
+     * 发布作业状态追踪事件.
+     *
+     * @param taskId 作业Id
+     * @param state 作业执行状态
+     * @param message 作业执行消息
+     */
+    void postJobStatusTraceEvent(String taskId, JobStatusTraceEvent.State state, String message);
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/ShardingContexts.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/ShardingContexts.java
new file mode 100644
index 0000000..ce72f6b
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/ShardingContexts.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.executor;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * 分片上下文集合.
+ * 
+ * @author zhangliang
+ */
+@RequiredArgsConstructor
+@Getter
+@ToString
+public final class ShardingContexts implements Serializable {
+    
+    private static final long serialVersionUID = -4585977349142082152L;
+    
+    /**
+     * 作业任务ID.
+     */
+    private final String taskId;
+    
+    /**
+     * 作业名称.
+     */
+    private final String jobName;
+    
+    /**
+     * 分片总数.
+     */
+    private final int shardingTotalCount;
+    
+    /**
+     * 作业自定义参数.
+     * 可以配置多个相同的作业, 但是用不同的参数作为不同的调度实例.
+     */
+    private final String jobParameter;
+    
+    /**
+     * 分配于本作业实例的分片项和参数的Map.
+     */
+    private final Map<Integer, String> shardingItemParameters;
+    
+    /**
+     * 作业事件采样统计数.
+     */
+    private int jobEventSamplingCount;
+    
+    /**
+     * 当前作业事件采样统计数.
+     */
+    @Setter
+    private int currentJobEventSamplingCount;
+    
+    /**
+     * 是否允许可以发送作业事件.
+     */
+    @Setter
+    private boolean allowSendJobEvent = true;
+    
+    public ShardingContexts(final String taskId, final String jobName, final int shardingTotalCount, final String jobParameter, 
+                            final Map<Integer, String> shardingItemParameters, final int jobEventSamplingCount) {
+        this.taskId = taskId;
+        this.jobName = jobName;
+        this.shardingTotalCount = shardingTotalCount;
+        this.jobParameter = jobParameter;
+        this.shardingItemParameters = shardingItemParameters;
+        this.jobEventSamplingCount = jobEventSamplingCount;
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/handler/ExecutorServiceHandler.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/handler/ExecutorServiceHandler.java
new file mode 100644
index 0000000..8e15785
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/handler/ExecutorServiceHandler.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.executor.handler;
+
+import java.util.concurrent.ExecutorService;
+
+/**
+ * 线程池服务处理器.
+ * 
+ * <p>用于作业内部的线程池处理使用.</p>
+ *
+ * @author zhangliang
+ */
+public interface ExecutorServiceHandler {
+    
+    /**
+     * 创建线程池服务对象.
+     * 
+     * @param jobName 作业名
+     * 
+     * @return 线程池服务对象
+     */
+    ExecutorService createExecutorService(final String jobName);
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/handler/ExecutorServiceHandlerRegistry.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/handler/ExecutorServiceHandlerRegistry.java
new file mode 100644
index 0000000..b2549ce
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/handler/ExecutorServiceHandlerRegistry.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.executor.handler;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * 线程池服务处理器注册表.
+ * 
+ * @author zhangliang
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class ExecutorServiceHandlerRegistry {
+    
+    private static final Map<String, ExecutorService> REGISTRY = new HashMap<>();
+    
+    /**
+     * 获取线程池服务.
+     * 
+     * @param jobName 作业名称
+     * @param executorServiceHandler 线程池服务处理器
+     * @return 线程池服务
+     */
+    public static synchronized ExecutorService getExecutorServiceHandler(final String jobName, final ExecutorServiceHandler executorServiceHandler) {
+        if (!REGISTRY.containsKey(jobName)) {
+            REGISTRY.put(jobName, executorServiceHandler.createExecutorService(jobName));
+        }
+        return REGISTRY.get(jobName);
+    }
+    
+    /**
+     * 从注册表中删除该作业线程池服务.
+     *
+     * @param jobName 作业名称
+     */
+    public static synchronized void remove(final String jobName) {
+        REGISTRY.remove(jobName);
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/handler/JobExceptionHandler.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/handler/JobExceptionHandler.java
new file mode 100644
index 0000000..f60e1a9
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/handler/JobExceptionHandler.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.executor.handler;
+
+/**
+ * 作业异常处理器.
+ *
+ * @author zhangliang
+ */
+public interface JobExceptionHandler {
+    
+    /**
+     * 处理作业异常.
+     * 
+     * @param jobName 作业名称
+     * @param cause 异常原因
+     */
+    void handleException(String jobName, Throwable cause);
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/handler/JobProperties.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/handler/JobProperties.java
new file mode 100644
index 0000000..fd8bed2
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/handler/JobProperties.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.executor.handler;
+
+import io.elasticjob.cloud.executor.handler.impl.DefaultJobExceptionHandler;
+import io.elasticjob.cloud.util.json.GsonFactory;
+import io.elasticjob.cloud.executor.handler.impl.DefaultExecutorServiceHandler;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.RequiredArgsConstructor;
+
+import java.util.EnumMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * 作业属性配置.
+ *
+ * @author zhangliang
+ */
+@AllArgsConstructor
+@NoArgsConstructor
+public final class JobProperties {
+    
+    private EnumMap<JobPropertiesEnum, String> map = new EnumMap<>(JobPropertiesEnum.class);
+    
+    /**
+     * 设置作业属性.
+     * 
+     * @param key 属性键
+     * @param value 属性值
+     */
+    public void put(final String key, final String value) {
+        JobPropertiesEnum jobPropertiesEnum = JobPropertiesEnum.from(key);
+        if (null == jobPropertiesEnum || null == value) {
+            return;
+        }
+        map.put(jobPropertiesEnum, value);
+    }
+    
+    /**
+     * 获取作业属性.
+     * 
+     * @param jobPropertiesEnum 作业属性枚举
+     * @return 属性值
+     */
+    public String get(final JobPropertiesEnum jobPropertiesEnum) {
+        return map.containsKey(jobPropertiesEnum) ? map.get(jobPropertiesEnum) : jobPropertiesEnum.getDefaultValue();
+    }
+    
+    /**
+     * 获取所有键.
+     * 
+     * @return 键集合
+     */
+    public String json() {
+        Map<String, String> jsonMap = new LinkedHashMap<>(JobPropertiesEnum.values().length, 1);
+        for (JobPropertiesEnum each : JobPropertiesEnum.values()) {
+            jsonMap.put(each.getKey(), get(each));
+        }
+        return GsonFactory.getGson().toJson(jsonMap);
+    }
+    
+    /**
+     * 作业属性枚举.
+     */
+    @RequiredArgsConstructor
+    @Getter
+    public enum JobPropertiesEnum {
+        
+        /**
+         * 作业异常处理器.
+         */
+        JOB_EXCEPTION_HANDLER("job_exception_handler", JobExceptionHandler.class, DefaultJobExceptionHandler.class.getCanonicalName()),
+        
+        /**
+         * 线程池服务处理器.
+         */
+        EXECUTOR_SERVICE_HANDLER("executor_service_handler", ExecutorServiceHandler.class, DefaultExecutorServiceHandler.class.getCanonicalName());
+        
+        private final String key;
+    
+        private final Class<?> classType;
+        
+        private final String defaultValue;
+        
+        /**
+         * 通过属性键获取枚举.
+         * 
+         * @param key 属性键
+         * @return 枚举
+         */
+        public static JobPropertiesEnum from(final String key) {
+            for (JobPropertiesEnum each : JobPropertiesEnum.values()) {
+                if (each.getKey().equals(key)) {
+                    return each;
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/handler/impl/DefaultExecutorServiceHandler.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/handler/impl/DefaultExecutorServiceHandler.java
new file mode 100644
index 0000000..000e934
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/handler/impl/DefaultExecutorServiceHandler.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.executor.handler.impl;
+
+import io.elasticjob.cloud.util.concurrent.ExecutorServiceObject;
+import io.elasticjob.cloud.executor.handler.ExecutorServiceHandler;
+
+import java.util.concurrent.ExecutorService;
+
+/**
+ * 默认线程池服务处理器.
+ * 
+ * @author zhangliang
+ */
+public final class DefaultExecutorServiceHandler implements ExecutorServiceHandler {
+    
+    @Override
+    public ExecutorService createExecutorService(final String jobName) {
+        return new ExecutorServiceObject("inner-job-" + jobName, Runtime.getRuntime().availableProcessors() * 2).createExecutorService();
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/handler/impl/DefaultJobExceptionHandler.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/handler/impl/DefaultJobExceptionHandler.java
new file mode 100644
index 0000000..43c56b6
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/handler/impl/DefaultJobExceptionHandler.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.executor.handler.impl;
+
+import io.elasticjob.cloud.executor.handler.JobExceptionHandler;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 默认作业异常处理器.
+ *
+ * @author zhangliang
+ */
+@Slf4j
+public final class DefaultJobExceptionHandler implements JobExceptionHandler {
+    
+    @Override
+    public void handleException(final String jobName, final Throwable cause) {
+        log.error(String.format("Job '%s' exception occur in job processing", jobName), cause);
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/type/DataflowJobExecutor.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/type/DataflowJobExecutor.java
new file mode 100644
index 0000000..d45ea87
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/type/DataflowJobExecutor.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.executor.type;
+
+import io.elasticjob.cloud.api.ShardingContext;
+import io.elasticjob.cloud.api.dataflow.DataflowJob;
+import io.elasticjob.cloud.config.dataflow.DataflowJobConfiguration;
+import io.elasticjob.cloud.executor.AbstractElasticJobExecutor;
+import io.elasticjob.cloud.executor.JobFacade;
+
+import java.util.List;
+
+/**
+ * 数据流作业执行器.
+ * 
+ * @author zhangliang
+ */
+public final class DataflowJobExecutor extends AbstractElasticJobExecutor {
+    
+    private final DataflowJob<Object> dataflowJob;
+    
+    public DataflowJobExecutor(final DataflowJob<Object> dataflowJob, final JobFacade jobFacade) {
+        super(jobFacade);
+        this.dataflowJob = dataflowJob;
+    }
+    
+    @Override
+    protected void process(final ShardingContext shardingContext) {
+        DataflowJobConfiguration dataflowConfig = (DataflowJobConfiguration) getJobRootConfig().getTypeConfig();
+        if (dataflowConfig.isStreamingProcess()) {
+            streamingExecute(shardingContext);
+        } else {
+            oneOffExecute(shardingContext);
+        }
+    }
+    
+    private void streamingExecute(final ShardingContext shardingContext) {
+        List<Object> data = fetchData(shardingContext);
+        while (null != data && !data.isEmpty()) {
+            processData(shardingContext, data);
+            if (!getJobFacade().isEligibleForJobRunning()) {
+                break;
+            }
+            data = fetchData(shardingContext);
+        }
+    }
+    
+    private void oneOffExecute(final ShardingContext shardingContext) {
+        List<Object> data = fetchData(shardingContext);
+        if (null != data && !data.isEmpty()) {
+            processData(shardingContext, data);
+        }
+    }
+    
+    private List<Object> fetchData(final ShardingContext shardingContext) {
+        return dataflowJob.fetchData(shardingContext);
+    }
+    
+    private void processData(final ShardingContext shardingContext, final List<Object> data) {
+        dataflowJob.processData(shardingContext, data);
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/type/ScriptJobExecutor.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/type/ScriptJobExecutor.java
new file mode 100644
index 0000000..27f177f
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/type/ScriptJobExecutor.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.executor.type;
+
+import com.google.common.base.Strings;
+import io.elasticjob.cloud.api.ShardingContext;
+import io.elasticjob.cloud.config.script.ScriptJobConfiguration;
+import io.elasticjob.cloud.exception.JobConfigurationException;
+import io.elasticjob.cloud.executor.AbstractElasticJobExecutor;
+import io.elasticjob.cloud.executor.JobFacade;
+import io.elasticjob.cloud.util.json.GsonFactory;
+import org.apache.commons.exec.CommandLine;
+import org.apache.commons.exec.DefaultExecutor;
+
+import java.io.IOException;
+
+/**
+ * 脚本作业执行器.
+ * 
+ * @author zhangliang
+ * @author caohao
+ */
+public final class ScriptJobExecutor extends AbstractElasticJobExecutor {
+    
+    public ScriptJobExecutor(final JobFacade jobFacade) {
+        super(jobFacade);
+    }
+    
+    @Override
+    protected void process(final ShardingContext shardingContext) {
+        final String scriptCommandLine = ((ScriptJobConfiguration) getJobRootConfig().getTypeConfig()).getScriptCommandLine();
+        if (Strings.isNullOrEmpty(scriptCommandLine)) {
+            throw new JobConfigurationException("Cannot find script command line for job '%s', job is not executed.", shardingContext.getJobName());
+        }
+        executeScript(shardingContext, scriptCommandLine);
+    }
+    
+    private void executeScript(final ShardingContext shardingContext, final String scriptCommandLine) {
+        CommandLine commandLine = CommandLine.parse(scriptCommandLine);
+        commandLine.addArgument(GsonFactory.getGson().toJson(shardingContext), false);
+        try {
+            new DefaultExecutor().execute(commandLine);
+        } catch (final IOException ex) {
+            throw new JobConfigurationException("Execute script failure.", ex);
+        }
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/type/SimpleJobExecutor.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/type/SimpleJobExecutor.java
new file mode 100644
index 0000000..cd49601
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/executor/type/SimpleJobExecutor.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.executor.type;
+
+import io.elasticjob.cloud.api.ShardingContext;
+import io.elasticjob.cloud.api.simple.SimpleJob;
+import io.elasticjob.cloud.executor.AbstractElasticJobExecutor;
+import io.elasticjob.cloud.executor.JobFacade;
+
+/**
+ * 简单作业执行器.
+ * 
+ * @author zhangliang
+ */
+public final class SimpleJobExecutor extends AbstractElasticJobExecutor {
+    
+    private final SimpleJob simpleJob;
+    
+    public SimpleJobExecutor(final SimpleJob simpleJob, final JobFacade jobFacade) {
+        super(jobFacade);
+        this.simpleJob = simpleJob;
+    }
+    
+    @Override
+    protected void process(final ShardingContext shardingContext) {
+        simpleJob.execute(shardingContext);
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/reg/base/CoordinatorRegistryCenter.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/reg/base/CoordinatorRegistryCenter.java
new file mode 100644
index 0000000..564e710
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/reg/base/CoordinatorRegistryCenter.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.reg.base;
+
+import java.util.List;
+
+/**
+ * 用于协调分布式服务的注册中心.
+ * 
+ * @author zhangliang
+ */
+public interface CoordinatorRegistryCenter extends RegistryCenter {
+    
+    /**
+     * 直接从注册中心而非本地缓存获取数据.
+     * 
+     * @param key 键
+     * @return 值
+     */
+    String getDirectly(String key);
+    
+    /**
+     * 获取子节点名称集合.
+     * 
+     * @param key 键
+     * @return 子节点名称集合
+     */
+    List<String> getChildrenKeys(String key);
+    
+    /**
+     * 获取子节点数量.
+     *
+     * @param key 键
+     * @return 子节点数量
+     */
+    int getNumChildren(String key);
+    
+    /**
+     * 持久化临时注册数据.
+     * 
+     * @param key 键
+     * @param value 值
+     */
+    void persistEphemeral(String key, String value);
+    
+    /**
+     * 持久化顺序注册数据.
+     *
+     * @param key 键
+     * @param value 值
+     * @return 包含10位顺序数字的znode名称
+     */
+    String persistSequential(String key, String value);
+    
+    /**
+     * 持久化临时顺序注册数据.
+     * 
+     * @param key 键
+     */
+    void persistEphemeralSequential(String key);
+    
+    /**
+     * 添加本地缓存.
+     * 
+     * @param cachePath 需加入缓存的路径
+     */
+    void addCacheData(String cachePath);
+    
+    /**
+     * 释放本地缓存.
+     *
+     * @param cachePath 需释放缓存的路径
+     */
+    void evictCacheData(String cachePath);
+    
+    /**
+     * 获取注册中心数据缓存对象.
+     * 
+     * @param cachePath 缓存的节点路径
+     * @return 注册中心数据缓存对象
+     */
+    Object getRawCache(String cachePath);
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/reg/base/ElectionCandidate.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/reg/base/ElectionCandidate.java
new file mode 100644
index 0000000..689850a
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/reg/base/ElectionCandidate.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.reg.base;
+
+/**
+ * 选举候选人.
+ * 保证{@link #startLeadership()}与{@link #stopLeadership()}方法在同一个线程内交替运行,
+ * 且不会出现并发执行的情况.
+ * 
+ * @author gaohongtao
+ */
+public interface ElectionCandidate {
+    
+    /**
+     * 开始领导状态.
+     * @throws Exception 抛出的异常
+     */
+    void startLeadership() throws Exception;
+    
+    /**
+     * 终止领导状态.
+     * 实现该方法时不应该抛出任何异常
+     */
+    void stopLeadership();
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/reg/base/RegistryCenter.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/reg/base/RegistryCenter.java
new file mode 100644
index 0000000..30492f9
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/reg/base/RegistryCenter.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.reg.base;
+
+/**
+ * 注册中心.
+ * 
+ * @author zhangliang
+ */
+public interface RegistryCenter {
+    
+    /**
+     * 初始化注册中心.
+     */
+    void init();
+    
+    /**
+     * 关闭注册中心.
+     */
+    void close();
+    
+    /**
+     * 获取注册数据.
+     * 
+     * @param key 键
+     * @return 值
+     */
+    String get(String key);
+    
+    /**
+     * 获取数据是否存在.
+     * 
+     * @param key 键
+     * @return 数据是否存在
+     */
+    boolean isExisted(String key);
+    
+    /**
+     * 持久化注册数据.
+     * 
+     * @param key 键
+     * @param value 值
+     */
+    void persist(String key, String value);
+    
+    /**
+     * 更新注册数据.
+     * 
+     * @param key 键
+     * @param value 值
+     */
+    void update(String key, String value);
+    
+    /**
+     * 删除注册数据.
+     * 
+     * @param key 键
+     */
+    void remove(String key);
+    
+    /**
+     * 获取注册中心当前时间.
+     * 
+     * @param key 用于获取时间的键
+     * @return 注册中心当前时间
+     */
+    long getRegistryCenterTime(String key);
+    
+    /**
+     * 直接获取操作注册中心的原生客户端.
+     * 如:Zookeeper或Redis等原生客户端.
+     * 
+     * @return 注册中心的原生客户端
+     */
+    Object getRawClient();
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/reg/exception/RegException.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/reg/exception/RegException.java
new file mode 100644
index 0000000..6ee1f41
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/reg/exception/RegException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.reg.exception;
+
+/**
+ * 注册中心异常.
+ * 
+ * @author zhangliang
+ */
+public final class RegException extends RuntimeException {
+    
+    private static final long serialVersionUID = -6417179023552012152L;
+    
+    public RegException(final String errorMessage, final Object... args) {
+        super(String.format(errorMessage, args));
+    }
+    
+    public RegException(final Exception cause) {
+        super(cause);
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/reg/exception/RegExceptionHandler.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/reg/exception/RegExceptionHandler.java
new file mode 100644
index 0000000..d8882c0
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/reg/exception/RegExceptionHandler.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.reg.exception;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.zookeeper.KeeperException.ConnectionLossException;
+import org.apache.zookeeper.KeeperException.NoNodeException;
+import org.apache.zookeeper.KeeperException.NodeExistsException;
+
+/**
+ * 注册中心异常处理类.
+ * 
+ * @author zhangliang
+ */
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class RegExceptionHandler {
+    
+    /**
+     * 处理异常.
+     * 
+     * <p>处理掉中断和连接失效异常并继续抛注册中心.</p>
+     * 
+     * @param cause 待处理异常.
+     */
+    public static void handleException(final Exception cause) {
+        if (null == cause) {
+            return;
+        }
+        if (isIgnoredException(cause) || null != cause.getCause() && isIgnoredException(cause.getCause())) {
+            log.debug("Elastic job: ignored exception for: {}", cause.getMessage());
+        } else if (cause instanceof InterruptedException) {
+            Thread.currentThread().interrupt();
+        } else {
+            throw new RegException(cause);
+        }
+    }
+    
+    private static boolean isIgnoredException(final Throwable cause) {
+        return cause instanceof ConnectionLossException || cause instanceof NoNodeException || cause instanceof NodeExistsException;
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperConfiguration.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperConfiguration.java
new file mode 100644
index 0000000..20f802b
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperConfiguration.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.reg.zookeeper;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+
+/**
+ * 基于Zookeeper的注册中心配置.
+ * 
+ * @author zhangliang
+ * @author caohao
+ */
+@Getter
+@Setter
+@RequiredArgsConstructor
+public final class ZookeeperConfiguration {
+    
+    /**
+     * 连接Zookeeper服务器的列表.
+     * 包括IP地址和端口号.
+     * 多个地址用逗号分隔.
+     * 如: host1:2181,host2:2181
+     */
+    private final String serverLists;
+    
+    /**
+     * 命名空间.
+     */
+    private final String namespace;
+    
+    /**
+     * 等待重试的间隔时间的初始值.
+     * 单位毫秒.
+     */
+    private int baseSleepTimeMilliseconds = 1000;
+    
+    /**
+     * 等待重试的间隔时间的最大值.
+     * 单位毫秒.
+     */
+    private int maxSleepTimeMilliseconds = 3000;
+    
+    /**
+     * 最大重试次数.
+     */
+    private int maxRetries = 3;
+    
+    /**
+     * 会话超时时间.
+     * 单位毫秒.
+     */
+    private int sessionTimeoutMilliseconds;
+    
+    /**
+     * 连接超时时间.
+     * 单位毫秒.
+     */
+    private int connectionTimeoutMilliseconds;
+    
+    /**
+     * 连接Zookeeper的权限令牌.
+     * 缺省为不需要权限验证.
+     */
+    private String digest;
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperElectionService.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperElectionService.java
new file mode 100644
index 0000000..cbb68b1
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperElectionService.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.reg.zookeeper;
+
+import io.elasticjob.cloud.reg.base.ElectionCandidate;
+import io.elasticjob.cloud.exception.JobSystemException;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.curator.framework.CuratorFramework;
+import org.apache.curator.framework.recipes.leader.LeaderSelector;
+import org.apache.curator.framework.recipes.leader.LeaderSelectorListenerAdapter;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * 使用{@link LeaderSelector}实现选举服务.
+ * 
+ * @author gaohongtao
+ * @author caohao
+ */
+@Slf4j
+public final class ZookeeperElectionService {
+    
+    private final CountDownLatch leaderLatch = new CountDownLatch(1);
+    
+    private final LeaderSelector leaderSelector;
+    
+    public ZookeeperElectionService(final String identity, final CuratorFramework client, final String electionPath, final ElectionCandidate electionCandidate) {
+        leaderSelector = new LeaderSelector(client, electionPath, new LeaderSelectorListenerAdapter() {
+            
+            @Override
+            public void takeLeadership(final CuratorFramework client) throws Exception {
+                log.info("Elastic job: {} has leadership", identity);
+                try {
+                    electionCandidate.startLeadership();
+                    leaderLatch.await();
+                    log.warn("Elastic job: {} lost leadership.", identity);
+                    electionCandidate.stopLeadership();
+                } catch (final JobSystemException exception) {
+                    log.error("Elastic job: Starting error", exception);
+                    System.exit(1);  
+                }
+            }
+        });
+        leaderSelector.autoRequeue();
+        leaderSelector.setId(identity);
+    }
+    
+    /**
+     * 开始选举.
+     */
+    public void start() {
+        log.debug("Elastic job: {} start to elect leadership", leaderSelector.getId());
+        leaderSelector.start();
+    }
+    
+    /**
+     * 停止选举.
+     */
+    public void stop() {
+        log.info("Elastic job: stop leadership election");
+        leaderLatch.countDown();
+        try {
+            leaderSelector.close();
+            // CHECKSTYLE:OFF
+        } catch (final Exception ignored) {
+        }
+        // CHECKSTYLE:ON
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperRegistryCenter.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperRegistryCenter.java
new file mode 100644
index 0000000..90d6609
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperRegistryCenter.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.reg.zookeeper;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import io.elasticjob.cloud.reg.base.CoordinatorRegistryCenter;
+import io.elasticjob.cloud.reg.exception.RegExceptionHandler;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.curator.framework.CuratorFramework;
+import org.apache.curator.framework.CuratorFrameworkFactory;
+import org.apache.curator.framework.api.ACLProvider;
+import org.apache.curator.framework.recipes.cache.ChildData;
+import org.apache.curator.framework.recipes.cache.TreeCache;
+import org.apache.curator.retry.ExponentialBackoffRetry;
+import org.apache.curator.utils.CloseableUtils;
+import org.apache.zookeeper.CreateMode;
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.ZooDefs;
+import org.apache.zookeeper.data.ACL;
+import org.apache.zookeeper.data.Stat;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 基于Zookeeper的注册中心.
+ * 
+ * @author zhangliang
+ */
+@Slf4j
+public final class ZookeeperRegistryCenter implements CoordinatorRegistryCenter {
+    
+    @Getter(AccessLevel.PROTECTED)
+    private ZookeeperConfiguration zkConfig;
+    
+    private final Map<String, TreeCache> caches = new HashMap<>();
+    
+    @Getter
+    private CuratorFramework client;
+    
+    public ZookeeperRegistryCenter(final ZookeeperConfiguration zkConfig) {
+        this.zkConfig = zkConfig;
+    }
+    
+    @Override
+    public void init() {
+        log.debug("Elastic job: zookeeper registry center init, server lists is: {}.", zkConfig.getServerLists());
+        CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder()
+                .connectString(zkConfig.getServerLists())
+                .retryPolicy(new ExponentialBackoffRetry(zkConfig.getBaseSleepTimeMilliseconds(), zkConfig.getMaxRetries(), zkConfig.getMaxSleepTimeMilliseconds()))
+                .namespace(zkConfig.getNamespace());
+        if (0 != zkConfig.getSessionTimeoutMilliseconds()) {
+            builder.sessionTimeoutMs(zkConfig.getSessionTimeoutMilliseconds());
+        }
+        if (0 != zkConfig.getConnectionTimeoutMilliseconds()) {
+            builder.connectionTimeoutMs(zkConfig.getConnectionTimeoutMilliseconds());
+        }
+        if (!Strings.isNullOrEmpty(zkConfig.getDigest())) {
+            builder.authorization("digest", zkConfig.getDigest().getBytes(Charsets.UTF_8))
+                    .aclProvider(new ACLProvider() {
+                    
+                        @Override
+                        public List<ACL> getDefaultAcl() {
+                            return ZooDefs.Ids.CREATOR_ALL_ACL;
+                        }
+                    
+                        @Override
+                        public List<ACL> getAclForPath(final String path) {
+                            return ZooDefs.Ids.CREATOR_ALL_ACL;
+                        }
+                    });
+        }
+        client = builder.build();
+        client.start();
+        try {
+            if (!client.blockUntilConnected(zkConfig.getMaxSleepTimeMilliseconds() * zkConfig.getMaxRetries(), TimeUnit.MILLISECONDS)) {
+                client.close();
+                throw new KeeperException.OperationTimeoutException();
+            }
+            //CHECKSTYLE:OFF
+        } catch (final Exception ex) {
+            //CHECKSTYLE:ON
+            RegExceptionHandler.handleException(ex);
+        }
+    }
+    
+    @Override
+    public void close() {
+        for (Entry<String, TreeCache> each : caches.entrySet()) {
+            each.getValue().close();
+        }
+        waitForCacheClose();
+        CloseableUtils.closeQuietly(client);
+    }
+    
+    /* TODO 等待500ms, cache先关闭再关闭client, 否则会抛异常
+     * 因为异步处理, 可能会导致client先关闭而cache还未关闭结束.
+     * 等待Curator新版本解决这个bug.
+     * BUG地址:https://issues.apache.org/jira/browse/CURATOR-157
+     */
+    private void waitForCacheClose() {
+        try {
+            Thread.sleep(500L);
+        } catch (final InterruptedException ex) {
+            Thread.currentThread().interrupt();
+        }
+    }
+    
+    @Override
+    public String get(final String key) {
+        TreeCache cache = findTreeCache(key);
+        if (null == cache) {
+            return getDirectly(key);
+        }
+        ChildData resultInCache = cache.getCurrentData(key);
+        if (null != resultInCache) {
+            return null == resultInCache.getData() ? null : new String(resultInCache.getData(), Charsets.UTF_8);
+        }
+        return getDirectly(key);
+    }
+    
+    private TreeCache findTreeCache(final String key) {
+        for (Entry<String, TreeCache> entry : caches.entrySet()) {
+            if (key.startsWith(entry.getKey())) {
+                return entry.getValue();
+            }
+        }
+        return null;
+    }
+    
+    @Override
+    public String getDirectly(final String key) {
+        try {
+            return new String(client.getData().forPath(key), Charsets.UTF_8);
+        //CHECKSTYLE:OFF
+        } catch (final Exception ex) {
+        //CHECKSTYLE:ON
+            RegExceptionHandler.handleException(ex);
+            return null;
+        }
+    }
+    
+    @Override
+    public List<String> getChildrenKeys(final String key) {
+        try {
+            List<String> result = client.getChildren().forPath(key);
+            Collections.sort(result, new Comparator<String>() {
+                
+                @Override
+                public int compare(final String o1, final String o2) {
+                    return o2.compareTo(o1);
+                }
+            });
+            return result;
+         //CHECKSTYLE:OFF
+        } catch (final Exception ex) {
+        //CHECKSTYLE:ON
+            RegExceptionHandler.handleException(ex);
+            return Collections.emptyList();
+        }
+    }
+    
+    @Override
+    public int getNumChildren(final String key) {
+        try {
+            Stat stat = client.checkExists().forPath(key);
+            if (null != stat) {
+                return stat.getNumChildren();
+            }
+            //CHECKSTYLE:OFF
+        } catch (final Exception ex) {
+            //CHECKSTYLE:ON
+            RegExceptionHandler.handleException(ex);
+        }
+        return 0;
+    }
+
+    @Override
+    public boolean isExisted(final String key) {
+        try {
+            return null != client.checkExists().forPath(key);
+        //CHECKSTYLE:OFF
+        } catch (final Exception ex) {
+        //CHECKSTYLE:ON
+            RegExceptionHandler.handleException(ex);
+            return false;
+        }
+    }
+    
+    @Override
+    public void persist(final String key, final String value) {
+        try {
+            if (!isExisted(key)) {
+                client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(key, value.getBytes(Charsets.UTF_8));
+            } else {
+                update(key, value);
+            }
+        //CHECKSTYLE:OFF
+        } catch (final Exception ex) {
+        //CHECKSTYLE:ON
+            RegExceptionHandler.handleException(ex);
+        }
+    }
+    
+    @Override
+    public void update(final String key, final String value) {
+        try {
+            client.inTransaction().check().forPath(key).and().setData().forPath(key, value.getBytes(Charsets.UTF_8)).and().commit();
+        //CHECKSTYLE:OFF
+        } catch (final Exception ex) {
+        //CHECKSTYLE:ON
+            RegExceptionHandler.handleException(ex);
+        }
+    }
+    
+    @Override
+    public void persistEphemeral(final String key, final String value) {
+        try {
+            if (isExisted(key)) {
+                client.delete().deletingChildrenIfNeeded().forPath(key);
+            }
+            client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(key, value.getBytes(Charsets.UTF_8));
+        //CHECKSTYLE:OFF
+        } catch (final Exception ex) {
+        //CHECKSTYLE:ON
+            RegExceptionHandler.handleException(ex);
+        }
+    }
+    
+    @Override
+    public String persistSequential(final String key, final String value) {
+        try {
+            return client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT_SEQUENTIAL).forPath(key, value.getBytes(Charsets.UTF_8));
+        //CHECKSTYLE:OFF
+        } catch (final Exception ex) {
+        //CHECKSTYLE:ON
+            RegExceptionHandler.handleException(ex);
+        }
+        return null;
+    }
+    
+    @Override
+    public void persistEphemeralSequential(final String key) {
+        try {
+            client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(key);
+        //CHECKSTYLE:OFF
+        } catch (final Exception ex) {
+        //CHECKSTYLE:ON
+            RegExceptionHandler.handleException(ex);
+        }
+    }
+    
+    @Override
+    public void remove(final String key) {
+        try {
+            client.delete().deletingChildrenIfNeeded().forPath(key);
+        //CHECKSTYLE:OFF
+        } catch (final Exception ex) {
+        //CHECKSTYLE:ON
+            RegExceptionHandler.handleException(ex);
+        }
+    }
+    
+    @Override
+    public long getRegistryCenterTime(final String key) {
+        long result = 0L;
+        try {
+            persist(key, "");
+            result = client.checkExists().forPath(key).getMtime();
+        //CHECKSTYLE:OFF
+        } catch (final Exception ex) {
+        //CHECKSTYLE:ON
+            RegExceptionHandler.handleException(ex);
+        }
+        Preconditions.checkState(0L != result, "Cannot get registry center time.");
+        return result;
+    }
+    
+    @Override
+    public Object getRawClient() {
+        return client;
+    }
+    
+    @Override
+    public void addCacheData(final String cachePath) {
+        TreeCache cache = new TreeCache(client, cachePath);
+        try {
+            cache.start();
+        //CHECKSTYLE:OFF
+        } catch (final Exception ex) {
+        //CHECKSTYLE:ON
+            RegExceptionHandler.handleException(ex);
+        }
+        caches.put(cachePath + "/", cache);
+    }
+    
+    @Override
+    public void evictCacheData(final String cachePath) {
+        TreeCache cache = caches.remove(cachePath + "/");
+        if (null != cache) {
+            cache.close();
+        }
+    }
+    
+    @Override
+    public Object getRawCache(final String cachePath) {
+        return caches.get(cachePath + "/");
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/statistics/StatisticInterval.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/statistics/StatisticInterval.java
new file mode 100644
index 0000000..b438ade
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/statistics/StatisticInterval.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.statistics;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * 统计时间间隔.
+ *
+ * @author liguangyun
+ */
+@Getter
+@RequiredArgsConstructor
+public enum StatisticInterval {
+    
+    MINUTE("0 * * * * ?"),
+    
+    HOUR("0 0 * * * ?"), 
+    
+    DAY("0 0 0 * * ?");
+    
+    private final String cron;
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/statistics/rdb/StatisticRdbRepository.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/statistics/rdb/StatisticRdbRepository.java
new file mode 100644
index 0000000..e4d1572
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/statistics/rdb/StatisticRdbRepository.java
@@ -0,0 +1,503 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.statistics.rdb;
+
+import com.google.common.base.Optional;
+import io.elasticjob.cloud.statistics.StatisticInterval;
+import io.elasticjob.cloud.statistics.type.job.JobRegisterStatistics;
+import io.elasticjob.cloud.statistics.type.job.JobRunningStatistics;
+import io.elasticjob.cloud.statistics.type.task.TaskResultStatistics;
+import io.elasticjob.cloud.statistics.type.task.TaskRunningStatistics;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * 统计信息数据仓库,基于数据库.
+ *
+ * @author liguangyun
+ */
+@Slf4j
+public class StatisticRdbRepository {
+    
+    private static final String TABLE_TASK_RESULT_STATISTICS = "TASK_RESULT_STATISTICS";
+    
+    private static final String TABLE_TASK_RUNNING_STATISTICS = "TASK_RUNNING_STATISTICS";
+
+    private static final String TABLE_JOB_RUNNING_STATISTICS = "JOB_RUNNING_STATISTICS";
+    
+    private static final String TABLE_JOB_REGISTER_STATISTICS = "JOB_REGISTER_STATISTICS";
+    
+    private final DataSource dataSource;
+    
+    /**
+     * 构造函数.
+     * 
+     * @param dataSource 数据源
+     * @throws SQLException SQL异常
+     */
+    public StatisticRdbRepository(final DataSource dataSource) throws SQLException {
+        this.dataSource = dataSource;
+        initTables();
+    }
+    
+    private void initTables() throws SQLException {
+        try (Connection conn = dataSource.getConnection()) {
+            createTaskResultTableIfNeeded(conn);
+            createTaskRunningTableIfNeeded(conn);
+            createJobRunningTableIfNeeded(conn);
+            createJobRegisterTableIfNeeded(conn);
+        }
+    }
+    
+    private void createTaskResultTableIfNeeded(final Connection conn) throws SQLException {
+        DatabaseMetaData dbMetaData = conn.getMetaData();
+        for (StatisticInterval each : StatisticInterval.values()) {
+            try (ResultSet resultSet = dbMetaData.getTables(null, null, TABLE_TASK_RESULT_STATISTICS + "_" + each, new String[]{"TABLE"})) {
+                if (!resultSet.next()) {
+                    createTaskResultTable(conn, each);
+                }
+            }
+        }
+    }
+    
+    private void createTaskResultTable(final Connection conn, final StatisticInterval statisticInterval) throws SQLException {
+        String dbSchema = "CREATE TABLE `" + TABLE_TASK_RESULT_STATISTICS + "_" + statisticInterval + "` ("
+                + "`id` BIGINT NOT NULL AUTO_INCREMENT, "
+                + "`success_count` INT(11),"
+                + "`failed_count` INT(11),"
+                + "`statistics_time` TIMESTAMP NOT NULL,"
+                + "`creation_time` TIMESTAMP NOT NULL,"
+                + "PRIMARY KEY (`id`));";
+        try (PreparedStatement preparedStatement = conn.prepareStatement(dbSchema)) {
+            preparedStatement.execute();
+        }
+    }
+    
+    private void createTaskRunningTableIfNeeded(final Connection conn) throws SQLException {
+        DatabaseMetaData dbMetaData = conn.getMetaData();
+        try (ResultSet resultSet = dbMetaData.getTables(null, null, TABLE_TASK_RUNNING_STATISTICS, new String[]{"TABLE"})) {
+            if (!resultSet.next()) {
+                createTaskRunningTable(conn);
+            }
+        }
+    }
+    
+    private void createTaskRunningTable(final Connection conn) throws SQLException {
+        String dbSchema = "CREATE TABLE `" + TABLE_TASK_RUNNING_STATISTICS + "` ("
+                + "`id` BIGINT NOT NULL AUTO_INCREMENT, "
+                + "`running_count` INT(11),"
+                + "`statistics_time` TIMESTAMP NOT NULL,"
+                + "`creation_time` TIMESTAMP NOT NULL,"
+                + "PRIMARY KEY (`id`));";
+        try (PreparedStatement preparedStatement = conn.prepareStatement(dbSchema)) {
+            preparedStatement.execute();
+        }
+    }
+    
+    private void createJobRunningTableIfNeeded(final Connection conn) throws SQLException {
+        DatabaseMetaData dbMetaData = conn.getMetaData();
+        try (ResultSet resultSet = dbMetaData.getTables(null, null, TABLE_JOB_RUNNING_STATISTICS, new String[]{"TABLE"})) {
+            if (!resultSet.next()) {
+                createJobRunningTable(conn);
+            }
+        }
+    }
+    
+    private void createJobRunningTable(final Connection conn) throws SQLException {
+        String dbSchema = "CREATE TABLE `" + TABLE_JOB_RUNNING_STATISTICS + "` ("
+                + "`id` BIGINT NOT NULL AUTO_INCREMENT, "
+                + "`running_count` INT(11),"
+                + "`statistics_time` TIMESTAMP NOT NULL,"
+                + "`creation_time` TIMESTAMP NOT NULL,"
+                + "PRIMARY KEY (`id`));";
+        try (PreparedStatement preparedStatement = conn.prepareStatement(dbSchema)) {
+            preparedStatement.execute();
+        }
+    }
+    
+    private void createJobRegisterTableIfNeeded(final Connection conn) throws SQLException {
+        DatabaseMetaData dbMetaData = conn.getMetaData();
+        try (ResultSet resultSet = dbMetaData.getTables(null, null, TABLE_JOB_REGISTER_STATISTICS, new String[]{"TABLE"})) {
+            if (!resultSet.next()) {
+                createJobRegisterTable(conn);
+            }
+        }
+    }
+    
+    private void createJobRegisterTable(final Connection conn) throws SQLException {
+        String dbSchema = "CREATE TABLE `" + TABLE_JOB_REGISTER_STATISTICS + "` ("
+                + "`id` BIGINT NOT NULL AUTO_INCREMENT, "
+                + "`registered_count` INT(11),"
+                + "`statistics_time` TIMESTAMP NOT NULL,"
+                + "`creation_time` TIMESTAMP NOT NULL,"
+                + "PRIMARY KEY (`id`));";
+        try (PreparedStatement preparedStatement = conn.prepareStatement(dbSchema)) {
+            preparedStatement.execute();
+        }
+    }
+    
+    /**
+     * 添加任务运行结果统计数据.
+     * 
+     * @param taskResultStatistics 任务运行结果统计数据对象
+     * @return 添加操作是否成功
+     */
+    public boolean add(final TaskResultStatistics taskResultStatistics) {
+        boolean result = false;
+        String sql = "INSERT INTO `" + TABLE_TASK_RESULT_STATISTICS + "_" + taskResultStatistics.getStatisticInterval()
+                + "` (`success_count`, `failed_count`, `statistics_time`, `creation_time`) VALUES (?, ?, ?, ?);";
+        try (
+                Connection conn = dataSource.getConnection();
+                PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
+            preparedStatement.setInt(1, taskResultStatistics.getSuccessCount());
+            preparedStatement.setInt(2, taskResultStatistics.getFailedCount());
+            preparedStatement.setTimestamp(3, new Timestamp(taskResultStatistics.getStatisticsTime().getTime()));
+            preparedStatement.setTimestamp(4, new Timestamp(taskResultStatistics.getCreationTime().getTime()));
+            preparedStatement.execute();
+            result = true;
+        } catch (final SQLException ex) {
+            // TODO 记录失败直接输出日志,未来可考虑配置化
+            log.error("Insert taskResultStatistics to DB error:", ex);
+        }
+        return result;
+    }
+    
+    /**
+     * 添加运行中的任务统计数据.
+     * 
+     * @param taskRunningStatistics 运行中的任务统计数据对象
+     * @return 添加操作是否成功
+     */
+    public boolean add(final TaskRunningStatistics taskRunningStatistics) {
+        boolean result = false;
+        String sql = "INSERT INTO `" + TABLE_TASK_RUNNING_STATISTICS + "` (`running_count`, `statistics_time`, `creation_time`) VALUES (?, ?, ?);";
+        try (
+                Connection conn = dataSource.getConnection();
+                PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
+            preparedStatement.setInt(1, taskRunningStatistics.getRunningCount());
+            preparedStatement.setTimestamp(2, new Timestamp(taskRunningStatistics.getStatisticsTime().getTime()));
+            preparedStatement.setTimestamp(3, new Timestamp(taskRunningStatistics.getCreationTime().getTime()));
+            preparedStatement.execute();
+            result = true;
+        } catch (final SQLException ex) {
+            // TODO 记录失败直接输出日志,未来可考虑配置化
+            log.error("Insert taskRunningStatistics to DB error:", ex);
+        }
+        return result;
+    }
+    
+    /**
+     * 添加运行中的作业统计数据.
+     * 
+     * @param jobRunningStatistics 运行中的作业统计数据对象
+     * @return 添加操作是否成功
+     */
+    public boolean add(final JobRunningStatistics jobRunningStatistics) {
+        boolean result = false;
+        String sql = "INSERT INTO `" + TABLE_JOB_RUNNING_STATISTICS + "` (`running_count`, `statistics_time`, `creation_time`) VALUES (?, ?, ?);";
+        try (
+                Connection conn = dataSource.getConnection();
+                PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
+            preparedStatement.setInt(1, jobRunningStatistics.getRunningCount());
+            preparedStatement.setTimestamp(2, new Timestamp(jobRunningStatistics.getStatisticsTime().getTime()));
+            preparedStatement.setTimestamp(3, new Timestamp(jobRunningStatistics.getCreationTime().getTime()));
+            preparedStatement.execute();
+            result = true;
+        } catch (final SQLException ex) {
+            // TODO 记录失败直接输出日志,未来可考虑配置化
+            log.error("Insert jobRunningStatistics to DB error:", ex);
+        }
+        return result;
+    }
+    
+    /**
+     * 添加作业注册统计数据.
+     * 
+     * @param jobRegisterStatistics 作业注册统计数据对象
+     * @return 添加操作是否成功
+     */
+    public boolean add(final JobRegisterStatistics jobRegisterStatistics) {
+        boolean result = false;
+        String sql = "INSERT INTO `" + TABLE_JOB_REGISTER_STATISTICS + "` (`registered_count`, `statistics_time`, `creation_time`) VALUES (?, ?, ?);";
+        try (
+                Connection conn = dataSource.getConnection();
+                PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
+            preparedStatement.setInt(1, jobRegisterStatistics.getRegisteredCount());
+            preparedStatement.setTimestamp(2, new Timestamp(jobRegisterStatistics.getStatisticsTime().getTime()));
+            preparedStatement.setTimestamp(3, new Timestamp(jobRegisterStatistics.getCreationTime().getTime()));
+            preparedStatement.execute();
+            result = true;
+        } catch (final SQLException ex) {
+            // TODO 记录失败直接输出日志,未来可考虑配置化
+            log.error("Insert jobRegisterStatistics to DB error:", ex);
+        }
+        return result;
+    }
+    
+    /**
+     * 获取任务运行结果统计数据集合.
+     * 
+     * @param from 统计开始时间
+     * @param statisticInterval 统计时间间隔
+     * @return 任务运行结果统计数据集合
+     */
+    public List<TaskResultStatistics> findTaskResultStatistics(final Date from, final StatisticInterval statisticInterval) {
+        List<TaskResultStatistics> result = new LinkedList<>();
+        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        String sql = String.format("SELECT id, success_count, failed_count, statistics_time, creation_time FROM %s WHERE statistics_time >= '%s' order by id ASC", 
+                TABLE_TASK_RESULT_STATISTICS + "_" + statisticInterval, formatter.format(from));
+        try (
+                Connection conn = dataSource.getConnection();
+                PreparedStatement preparedStatement = conn.prepareStatement(sql);
+                ResultSet resultSet = preparedStatement.executeQuery()
+                ) {
+            while (resultSet.next()) {
+                TaskResultStatistics taskResultStatistics = new TaskResultStatistics(resultSet.getLong(1), resultSet.getInt(2), resultSet.getInt(3), 
+                        statisticInterval, new Date(resultSet.getTimestamp(4).getTime()), new Date(resultSet.getTimestamp(5).getTime()));
+                result.add(taskResultStatistics);
+            }
+        } catch (final SQLException ex) {
+            // TODO 记录失败直接输出日志,未来可考虑配置化
+            log.error("Fetch taskResultStatistics from DB error:", ex);
+        }
+        return result;
+    }
+    
+    /**
+     * 获取合计后的任务运行结果统计数据.
+     * 
+     * @param from 统计开始时间
+     * @param statisticInterval 统计时间间隔
+     * @return 合计后的任务运行结果统计数据对象
+     */
+    public TaskResultStatistics getSummedTaskResultStatistics(final Date from, final StatisticInterval statisticInterval) {
+        TaskResultStatistics result = new TaskResultStatistics(0, 0, statisticInterval, new Date());
+        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        String sql = String.format("SELECT sum(success_count), sum(failed_count) FROM %s WHERE statistics_time >= '%s'", 
+                TABLE_TASK_RESULT_STATISTICS + "_" + statisticInterval, formatter.format(from));
+        try (
+                Connection conn = dataSource.getConnection();
+                PreparedStatement preparedStatement = conn.prepareStatement(sql);
+                ResultSet resultSet = preparedStatement.executeQuery()
+                ) {
+            while (resultSet.next()) {
+                result = new TaskResultStatistics(resultSet.getInt(1), resultSet.getInt(2), statisticInterval, new Date());
+            }
+        } catch (final SQLException ex) {
+            // TODO 记录失败直接输出日志,未来可考虑配置化
+            log.error("Fetch summed taskResultStatistics from DB error:", ex);
+        }
+        return result;
+    }
+    
+    /**
+     * 获取最近一条任务运行结果统计数据.
+     * 
+     * @param statisticInterval 统计时间间隔
+     * @return 任务运行结果统计数据对象
+     */
+    public Optional<TaskResultStatistics> findLatestTaskResultStatistics(final StatisticInterval statisticInterval) {
+        TaskResultStatistics result = null;
+        String sql = String.format("SELECT id, success_count, failed_count, statistics_time, creation_time FROM %s order by id DESC LIMIT 1", 
+                TABLE_TASK_RESULT_STATISTICS + "_" + statisticInterval);
+        try (
+                Connection conn = dataSource.getConnection();
+                PreparedStatement preparedStatement = conn.prepareStatement(sql);
+                ResultSet resultSet = preparedStatement.executeQuery()
+                ) {
+            while (resultSet.next()) {
+                result = new TaskResultStatistics(resultSet.getLong(1), resultSet.getInt(2), resultSet.getInt(3), 
+                        statisticInterval, new Date(resultSet.getTimestamp(4).getTime()), new Date(resultSet.getTimestamp(5).getTime()));
+            }
+        } catch (final SQLException ex) {
+            // TODO 记录失败直接输出日志,未来可考虑配置化
+            log.error("Fetch latest taskResultStatistics from DB error:", ex);
+        }
+        return Optional.fromNullable(result);
+    }
+    
+    /**
+     * 获取运行中的任务统计数据集合.
+     * 
+     * @param from 统计开始时间
+     * @return 运行中的任务统计数据集合
+     */
+    public List<TaskRunningStatistics> findTaskRunningStatistics(final Date from) {
+        List<TaskRunningStatistics> result = new LinkedList<>();
+        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        String sql = String.format("SELECT id, running_count, statistics_time, creation_time FROM %s WHERE statistics_time >= '%s' order by id ASC", 
+                TABLE_TASK_RUNNING_STATISTICS, formatter.format(from));
+        try (
+                Connection conn = dataSource.getConnection();
+                PreparedStatement preparedStatement = conn.prepareStatement(sql);
+                ResultSet resultSet = preparedStatement.executeQuery()
+                ) {
+            while (resultSet.next()) {
+                TaskRunningStatistics taskRunningStatistics = new TaskRunningStatistics(resultSet.getLong(1), resultSet.getInt(2), 
+                        new Date(resultSet.getTimestamp(3).getTime()), new Date(resultSet.getTimestamp(4).getTime()));
+                result.add(taskRunningStatistics);
+            }
+        } catch (final SQLException ex) {
+            // TODO 记录失败直接输出日志,未来可考虑配置化
+            log.error("Fetch taskRunningStatistics from DB error:", ex);
+        }
+        return result;
+    }
+    
+    /**
+     * 获取运行中的任务统计数据集合.
+     * 
+     * @param from 统计开始时间
+     * @return 运行中的任务统计数据集合
+     */
+    public List<JobRunningStatistics> findJobRunningStatistics(final Date from) {
+        List<JobRunningStatistics> result = new LinkedList<>();
+        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        String sql = String.format("SELECT id, running_count, statistics_time, creation_time FROM %s WHERE statistics_time >= '%s' order by id ASC", 
+                TABLE_JOB_RUNNING_STATISTICS, formatter.format(from));
+        try (
+                Connection conn = dataSource.getConnection();
+                PreparedStatement preparedStatement = conn.prepareStatement(sql);
+                ResultSet resultSet = preparedStatement.executeQuery()
+                ) {
+            while (resultSet.next()) {
+                JobRunningStatistics jobRunningStatistics = new JobRunningStatistics(resultSet.getLong(1), resultSet.getInt(2), 
+                        new Date(resultSet.getTimestamp(3).getTime()), new Date(resultSet.getTimestamp(4).getTime()));
+                result.add(jobRunningStatistics);
+            }
+        } catch (final SQLException ex) {
+            // TODO 记录失败直接输出日志,未来可考虑配置化
+            log.error("Fetch jobRunningStatistics from DB error:", ex);
+        }
+        return result;
+    }
+    
+    /**
+     * 获取最近一条运行中的任务统计数据.
+     * 
+     * @return 运行中的任务统计数据对象
+     */
+    public Optional<TaskRunningStatistics> findLatestTaskRunningStatistics() {
+        TaskRunningStatistics result = null;
+        String sql = String.format("SELECT id, running_count, statistics_time, creation_time FROM %s order by id DESC LIMIT 1", 
+                TABLE_TASK_RUNNING_STATISTICS);
+        try (
+                Connection conn = dataSource.getConnection();
+                PreparedStatement preparedStatement = conn.prepareStatement(sql);
+                ResultSet resultSet = preparedStatement.executeQuery()
+                ) {
+            while (resultSet.next()) {
+                result = new TaskRunningStatistics(resultSet.getLong(1), resultSet.getInt(2), 
+                        new Date(resultSet.getTimestamp(3).getTime()), new Date(resultSet.getTimestamp(4).getTime()));
+            }
+        } catch (final SQLException ex) {
+            // TODO 记录失败直接输出日志,未来可考虑配置化
+            log.error("Fetch latest taskRunningStatistics from DB error:", ex);
+        }
+        return Optional.fromNullable(result);
+    }
+    
+    /**
+     * 获取最近一条运行中的任务统计数据.
+     * 
+     * @return 运行中的任务统计数据对象
+     */
+    public Optional<JobRunningStatistics> findLatestJobRunningStatistics() {
+        JobRunningStatistics result = null;
+        String sql = String.format("SELECT id, running_count, statistics_time, creation_time FROM %s order by id DESC LIMIT 1", 
+                TABLE_JOB_RUNNING_STATISTICS);
+        try (
+                Connection conn = dataSource.getConnection();
+                PreparedStatement preparedStatement = conn.prepareStatement(sql);
+                ResultSet resultSet = preparedStatement.executeQuery()
+                ) {
+            while (resultSet.next()) {
+                result = new JobRunningStatistics(resultSet.getLong(1), resultSet.getInt(2), 
+                        new Date(resultSet.getTimestamp(3).getTime()), new Date(resultSet.getTimestamp(4).getTime()));
+            }
+        } catch (final SQLException ex) {
+            // TODO 记录失败直接输出日志,未来可考虑配置化
+            log.error("Fetch latest jobRunningStatistics from DB error:", ex);
+        }
+        return Optional.fromNullable(result);
+    }
+    
+    /**
+     * 获取作业注册统计数据集合.
+     * 
+     * @param from 统计开始时间
+     * @return 作业注册统计数据集合
+     */
+    public List<JobRegisterStatistics> findJobRegisterStatistics(final Date from) {
+        List<JobRegisterStatistics> result = new LinkedList<>();
+        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        String sql = String.format("SELECT id, registered_count, statistics_time, creation_time FROM %s WHERE statistics_time >= '%s' order by id ASC", 
+                TABLE_JOB_REGISTER_STATISTICS, formatter.format(from));
+        try (
+                Connection conn = dataSource.getConnection();
+                PreparedStatement preparedStatement = conn.prepareStatement(sql);
+                ResultSet resultSet = preparedStatement.executeQuery()
+                ) {
+            while (resultSet.next()) {
+                JobRegisterStatistics jobRegisterStatistics = new JobRegisterStatistics(resultSet.getLong(1), resultSet.getInt(2), 
+                        new Date(resultSet.getTimestamp(3).getTime()), new Date(resultSet.getTimestamp(4).getTime()));
+                result.add(jobRegisterStatistics);
+            }
+        } catch (final SQLException ex) {
+            // TODO 记录失败直接输出日志,未来可考虑配置化
+            log.error("Fetch jobRegisterStatistics from DB error:", ex);
+        }
+        return result;
+    }
+    
+    /**
+     * 获取最近一条作业注册统计数据.
+     * 
+     * @return 作业注册统计数据对象
+     */
+    public Optional<JobRegisterStatistics> findLatestJobRegisterStatistics() {
+        JobRegisterStatistics result = null;
+        String sql = String.format("SELECT id, registered_count, statistics_time, creation_time FROM %s order by id DESC LIMIT 1", 
+                TABLE_JOB_REGISTER_STATISTICS);
+        try (
+                Connection conn = dataSource.getConnection();
+                PreparedStatement preparedStatement = conn.prepareStatement(sql);
+                ResultSet resultSet = preparedStatement.executeQuery()
+                ) {
+            while (resultSet.next()) {
+                result = new JobRegisterStatistics(resultSet.getLong(1), resultSet.getInt(2), 
+                        new Date(resultSet.getTimestamp(3).getTime()), new Date(resultSet.getTimestamp(4).getTime()));
+            }
+        } catch (final SQLException ex) {
+            // TODO 记录失败直接输出日志,未来可考虑配置化
+            log.error("Fetch latest jobRegisterStatistics from DB error:", ex);
+        }
+        return Optional.fromNullable(result);
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/statistics/type/job/JobExecutionTypeStatistics.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/statistics/type/job/JobExecutionTypeStatistics.java
new file mode 100644
index 0000000..1cc18a3
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/statistics/type/job/JobExecutionTypeStatistics.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.statistics.type.job;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 作业执行类型统计数据.
+ *
+ * @author liguangyun
+ */
+@Getter
+@AllArgsConstructor
+public final class JobExecutionTypeStatistics {
+    
+    private int transientJobCount;
+    
+    private int daemonJobCount;
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/statistics/type/job/JobRegisterStatistics.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/statistics/type/job/JobRegisterStatistics.java
new file mode 100644
index 0000000..c47b445
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/statistics/type/job/JobRegisterStatistics.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.statistics.type.job;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Date;
+
+/**
+ * 作业注册到Cloud平台统计数据.
+ *
+ * @author liguangyun
+ */
+@Getter
+@RequiredArgsConstructor
+@AllArgsConstructor
+public final class JobRegisterStatistics {
+    
+    private long id;
+    
+    private final int registeredCount;
+    
+    private final Date statisticsTime;
+    
+    private Date creationTime = new Date();
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/statistics/type/job/JobRunningStatistics.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/statistics/type/job/JobRunningStatistics.java
new file mode 100644
index 0000000..fa77b5b
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/statistics/type/job/JobRunningStatistics.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.statistics.type.job;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Date;
+
+/**
+ * 运行中的作业统计数据.
+ *
+ * @author liguangyun
+ */
+@Getter
+@RequiredArgsConstructor
+@AllArgsConstructor
+public final class JobRunningStatistics {
+    
+    private long id;
+    
+    private final int runningCount;
+    
+    private final Date statisticsTime;
+    
+    private Date creationTime = new Date();
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/statistics/type/job/JobTypeStatistics.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/statistics/type/job/JobTypeStatistics.java
new file mode 100644
index 0000000..f9b5b0f
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/statistics/type/job/JobTypeStatistics.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.statistics.type.job;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 作业类型统计数据.
+ *
+ * @author liguangyun
+ */
+@Getter
+@AllArgsConstructor
+public final class JobTypeStatistics {
+    
+    private int scriptJobCount;
+    
+    private int simpleJobCount;
+    
+    private int dataflowJobCount;
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/statistics/type/task/TaskResultStatistics.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/statistics/type/task/TaskResultStatistics.java
new file mode 100644
index 0000000..d151bb1
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/statistics/type/task/TaskResultStatistics.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.statistics.type.task;
+
+import io.elasticjob.cloud.statistics.StatisticInterval;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Date;
+
+/**
+ * 任务运行结果统计数据.
+ *
+ * @author liguangyun
+ */
+@Getter
+@AllArgsConstructor
+@RequiredArgsConstructor
+public final class TaskResultStatistics {
+    
+    private long id;
+    
+    private final int successCount;
+    
+    private final int failedCount;
+    
+    private final StatisticInterval statisticInterval;
+    
+    private final Date statisticsTime;
+    
+    private Date creationTime = new Date();
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/statistics/type/task/TaskRunningStatistics.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/statistics/type/task/TaskRunningStatistics.java
new file mode 100644
index 0000000..2c9de69
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/statistics/type/task/TaskRunningStatistics.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.statistics.type.task;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Date;
+
+/**
+ * 运行中的任务统计数据.
+ *
+ * @author liguangyun
+ */
+@Getter
+@RequiredArgsConstructor
+@AllArgsConstructor
+public final class TaskRunningStatistics {
+    
+    private long id;
+    
+    private final int runningCount;
+    
+    private final Date statisticsTime;
+    
+    private Date creationTime = new Date();
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/concurrent/BlockUtils.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/concurrent/BlockUtils.java
new file mode 100644
index 0000000..66096f4
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/concurrent/BlockUtils.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.util.concurrent;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class BlockUtils {
+    
+    public static void waitingShortTime() {
+        sleep(100L);
+    }
+    
+    public static void sleep(final long millis) {
+        try {
+            Thread.sleep(millis);
+        } catch (final InterruptedException ex) {
+            Thread.currentThread().interrupt();
+        }
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/concurrent/ExecutorServiceObject.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/concurrent/ExecutorServiceObject.java
new file mode 100644
index 0000000..e8c2417
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/concurrent/ExecutorServiceObject.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.util.concurrent;
+
+import com.google.common.base.Joiner;
+import com.google.common.util.concurrent.MoreExecutors;
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 线程池执行服务对象.
+ *
+ * @author zhangliang
+ */
+public final class ExecutorServiceObject {
+    
+    private final ThreadPoolExecutor threadPoolExecutor;
+    
+    private final BlockingQueue<Runnable> workQueue;
+    
+    public ExecutorServiceObject(final String namingPattern, final int threadSize) {
+        workQueue = new LinkedBlockingQueue<>();
+        threadPoolExecutor = new ThreadPoolExecutor(threadSize, threadSize, 5L, TimeUnit.MINUTES, workQueue, 
+                new BasicThreadFactory.Builder().namingPattern(Joiner.on("-").join(namingPattern, "%s")).build());
+        threadPoolExecutor.allowCoreThreadTimeOut(true);
+    }
+    
+    /**
+     * 创建线程池服务对象.
+     *
+     * @return 线程池服务对象
+     */
+    public ExecutorService createExecutorService() {
+        return MoreExecutors.listeningDecorator(MoreExecutors.getExitingExecutorService(threadPoolExecutor));
+    }
+    
+    public boolean isShutdown() {
+        return threadPoolExecutor.isShutdown();
+    }
+    
+    /**
+     * 获取当前活跃的线程数.
+     *
+     * @return 当前活跃的线程数
+     */
+    public int getActiveThreadCount() {
+        return threadPoolExecutor.getActiveCount();
+    }
+    
+    /**
+     * 获取待执行任务数量.
+     *
+     * @return 待执行任务数量
+     */
+    public int getWorkQueueSize() {
+        return workQueue.size();
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/config/ShardingItemParameters.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/config/ShardingItemParameters.java
new file mode 100644
index 0000000..7fef4a5
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/config/ShardingItemParameters.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.util.config;
+
+import com.google.common.base.Strings;
+import io.elasticjob.cloud.exception.JobConfigurationException;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 分片序列号个性化参数.
+ *
+ * @author zhangliang
+ */
+@Getter
+public final class ShardingItemParameters {
+    
+    private static final String PARAMETER_DELIMITER = ",";
+    
+    private static final String KEY_VALUE_DELIMITER = "=";
+    
+    private final Map<Integer, String> map;
+    
+    public ShardingItemParameters(final String shardingItemParameters) {
+        map = toMap(shardingItemParameters);
+    }
+    
+    private Map<Integer, String> toMap(final String originalShardingItemParameters) {
+        if (Strings.isNullOrEmpty(originalShardingItemParameters)) {
+            return Collections.emptyMap();
+        }
+        String[] shardingItemParameters = originalShardingItemParameters.split(PARAMETER_DELIMITER);
+        Map<Integer, String> result = new HashMap<>(shardingItemParameters.length);
+        for (String each : shardingItemParameters) {
+            ShardingItem shardingItem = parse(each, originalShardingItemParameters);
+            result.put(shardingItem.item, shardingItem.parameter);
+        }
+        return result;
+    }
+    
+    private ShardingItem parse(final String shardingItemParameter, final String originalShardingItemParameters) {
+        String[] pair = shardingItemParameter.trim().split(KEY_VALUE_DELIMITER);
+        if (2 != pair.length) {
+            throw new JobConfigurationException("Sharding item parameters '%s' format error, should be int=xx,int=xx", originalShardingItemParameters);
+        }
+        try {
+            return new ShardingItem(Integer.parseInt(pair[0].trim()), pair[1].trim());
+        } catch (final NumberFormatException ex) {
+            throw new JobConfigurationException("Sharding item parameters key '%s' is not an integer.", pair[0]);
+        }
+    }
+    
+    /**
+     * 分片项.
+     */
+    @AllArgsConstructor
+    private static final class ShardingItem {
+        
+        private final int item;
+        
+        private final String parameter;
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/config/ShardingItems.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/config/ShardingItems.java
new file mode 100644
index 0000000..86bbe4d
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/config/ShardingItems.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.util.config;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
+import lombok.Getter;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 分片项工具类.
+ *
+ * @author zhangliang
+ */
+@Getter
+public final class ShardingItems {
+    
+    private static final String DELIMITER = ",";
+    
+    /**
+     * 根据分片项字符串获取分片项列表.
+     *
+     * @param itemsString 分片项字符串
+     * @return 分片项列表
+     */
+    public static List<Integer> toItemList(final String itemsString) {
+        if (Strings.isNullOrEmpty(itemsString)) {
+            return Collections.emptyList();
+        }
+        String[] items = itemsString.split(DELIMITER);
+        List<Integer> result = new ArrayList<>(items.length);
+        for (String each : items) {
+            int item = Integer.parseInt(each);
+            if (!result.contains(item)) {
+                result.add(item);
+            }
+        }
+        return result;
+    }
+    
+    /**
+     * 根据分片项列表获取分片项字符串.
+     *
+     * @param items 分片项列表
+     * @return 分片项字符串
+     */
+    public static String toItemsString(final List<Integer> items) {
+        return items.isEmpty() ? "" : Joiner.on(DELIMITER).join(items);
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/digest/Encryption.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/digest/Encryption.java
new file mode 100644
index 0000000..4701518
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/digest/Encryption.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.util.digest;
+
+import io.elasticjob.cloud.exception.JobSystemException;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * 字符串加密工具类.
+ *
+ * @author zhangliang
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class Encryption {
+    
+    private static final String MD5 = "MD5";
+    
+    /**
+     * 采用MD5算法加密字符串.
+     * 
+     * @param str 需要加密的字符串
+     * @return 加密后的字符串
+     */
+    public static String md5(final String str) {
+        try {
+            MessageDigest messageDigest = MessageDigest.getInstance(MD5);
+            messageDigest.update(str.getBytes());
+            return new BigInteger(1, messageDigest.digest()).toString(16);
+        } catch (final NoSuchAlgorithmException ex) {
+            throw new JobSystemException(ex);
+        }
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/env/HostException.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/env/HostException.java
new file mode 100644
index 0000000..48168c9
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/env/HostException.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.util.env;
+
+import java.io.IOException;
+
+/**
+ * 网络主机异常.
+ * 
+ * @author zhangliang
+ */
+public final class HostException extends RuntimeException {
+    
+    private static final long serialVersionUID = 3589264847881174997L;
+    
+    public HostException(final IOException cause) {
+        super(cause);
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/env/IpUtils.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/env/IpUtils.java
new file mode 100644
index 0000000..3552182
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/env/IpUtils.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.util.env;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.Enumeration;
+
+/**
+ * 获取真实本机网络的服务.
+ * 
+ * @author zhangliang
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class IpUtils {
+    
+    /**
+     * IP地址的正则表达式.
+     */
+    public static final String IP_REGEX = "((\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3})";
+    
+    private static volatile String cachedIpAddress;
+    
+    /**
+     * 获取本机IP地址.
+     * 
+     * <p>
+     * 有限获取外网IP地址.
+     * 也有可能是链接着路由器的最终IP地址.
+     * </p>
+     * 
+     * @return 本机IP地址
+     */
+    public static String getIp() {
+        if (null != cachedIpAddress) {
+            return cachedIpAddress;
+        }
+        Enumeration<NetworkInterface> netInterfaces;
+        try {
+            netInterfaces = NetworkInterface.getNetworkInterfaces();
+        } catch (final SocketException ex) {
+            throw new HostException(ex);
+        }
+        String localIpAddress = null;
+        while (netInterfaces.hasMoreElements()) {
+            NetworkInterface netInterface = netInterfaces.nextElement();
+            Enumeration<InetAddress> ipAddresses = netInterface.getInetAddresses();
+            while (ipAddresses.hasMoreElements()) {
+                InetAddress ipAddress = ipAddresses.nextElement();
+                if (isPublicIpAddress(ipAddress)) {
+                    String publicIpAddress = ipAddress.getHostAddress();
+                    cachedIpAddress = publicIpAddress;
+                    return publicIpAddress;
+                }
+                if (isLocalIpAddress(ipAddress)) {
+                    localIpAddress = ipAddress.getHostAddress();
+                }
+            }
+        }
+        cachedIpAddress = localIpAddress;
+        return localIpAddress;
+    }
+    
+    private static boolean isPublicIpAddress(final InetAddress ipAddress) {
+        return !ipAddress.isSiteLocalAddress() && !ipAddress.isLoopbackAddress() && !isV6IpAddress(ipAddress);
+    }
+    
+    private static boolean isLocalIpAddress(final InetAddress ipAddress) {
+        return ipAddress.isSiteLocalAddress() && !ipAddress.isLoopbackAddress() && !isV6IpAddress(ipAddress);
+    }
+    
+    private static boolean isV6IpAddress(final InetAddress ipAddress) {
+        return ipAddress.getHostAddress().contains(":");
+    }
+    
+    /**
+     * 获取本机Host名称.
+     * 
+     * @return 本机Host名称
+     */
+    public static String getHostName() {
+        try {
+            return InetAddress.getLocalHost().getHostName();
+        } catch (final UnknownHostException ex) {
+            return "unknown";
+        }
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/env/TimeService.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/env/TimeService.java
new file mode 100644
index 0000000..d3aebaa
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/env/TimeService.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.util.env;
+
+/**
+ * 获取时间的服务.
+ * 
+ * @author zhangliang
+ */
+public final class TimeService {
+    
+    /**
+     * 获取当前时间的毫秒数.
+     * 
+     * @return 当前时间的毫秒数
+     */
+    public long getCurrentMillis() {
+        return System.currentTimeMillis();
+    }
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/json/AbstractJobConfigurationGsonTypeAdapter.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/json/AbstractJobConfigurationGsonTypeAdapter.java
new file mode 100644
index 0000000..520c71f
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/json/AbstractJobConfigurationGsonTypeAdapter.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.util.json;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import io.elasticjob.cloud.api.JobType;
+import io.elasticjob.cloud.config.JobTypeConfiguration;
+import io.elasticjob.cloud.config.script.ScriptJobConfiguration;
+import io.elasticjob.cloud.config.JobCoreConfiguration;
+import io.elasticjob.cloud.config.JobRootConfiguration;
+import io.elasticjob.cloud.config.dataflow.DataflowJobConfiguration;
+import io.elasticjob.cloud.config.simple.SimpleJobConfiguration;
+import io.elasticjob.cloud.executor.handler.JobProperties;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 作业配置的Json转换适配器.
+ *
+ * @param <T> 作业配置对象泛型
+ *
+ * @author zhangliang
+ * @author caohao
+ */
+public abstract class AbstractJobConfigurationGsonTypeAdapter<T extends JobRootConfiguration> extends TypeAdapter<T> {
+    
+    @Override
+    public T read(final JsonReader in) throws IOException {
+        String jobName = "";
+        String cron = "";
+        int shardingTotalCount = 0;
+        String shardingItemParameters = "";
+        String jobParameter = "";
+        boolean failover = false;
+        boolean misfire = failover;
+        String description = "";
+        JobProperties jobProperties = new JobProperties();
+        JobType jobType = null;
+        String jobClass = "";
+        boolean streamingProcess = false;
+        String scriptCommandLine = "";
+        Map<String, Object> customizedValueMap = new HashMap<>(32, 1);
+        in.beginObject();
+        while (in.hasNext()) {
+            String jsonName = in.nextName();
+            switch (jsonName) {
+                case "jobName":
+                    jobName = in.nextString();
+                    break;
+                case "cron":
+                    cron = in.nextString();
+                    break;
+                case "shardingTotalCount":
+                    shardingTotalCount = in.nextInt();
+                    break;
+                case "shardingItemParameters":
+                    shardingItemParameters = in.nextString();
+                    break;
+                case "jobParameter":
+                    jobParameter = in.nextString();
+                    break;
+                case "failover":
+                    failover = in.nextBoolean();
+                    break;
+                case "misfire":
+                    misfire = in.nextBoolean();
+                    break;
+                case "description":
+                    description = in.nextString();
+                    break;
+                case "jobProperties":
+                    jobProperties = getJobProperties(in);
+                    break;
+                case "jobType":
+                    jobType = JobType.valueOf(in.nextString());
+                    break;
+                case "jobClass":
+                    jobClass = in.nextString();
+                    break;
+                case "streamingProcess":
+                    streamingProcess = in.nextBoolean();
+                    break;
+                case "scriptCommandLine":
+                    scriptCommandLine = in.nextString();
+                    break;
+                default:
+                    addToCustomizedValueMap(jsonName, in, customizedValueMap);
+                    break;
+            }
+        }
+        in.endObject();
+        JobCoreConfiguration coreConfig = getJobCoreConfiguration(jobName, cron, shardingTotalCount, shardingItemParameters,
+                jobParameter, failover, misfire, description, jobProperties);
+        JobTypeConfiguration typeConfig = getJobTypeConfiguration(coreConfig, jobType, jobClass, streamingProcess, scriptCommandLine);
+        return getJobRootConfiguration(typeConfig, customizedValueMap);
+    }
+    
+    private JobProperties getJobProperties(final JsonReader in) throws IOException {
+        JobProperties result = new JobProperties();
+        in.beginObject();
+        while (in.hasNext()) {
+            switch (in.nextName()) {
+                case "job_exception_handler":
+                    result.put(JobProperties.JobPropertiesEnum.JOB_EXCEPTION_HANDLER.getKey(), in.nextString());
+                    break;
+                case "executor_service_handler":
+                    result.put(JobProperties.JobPropertiesEnum.EXECUTOR_SERVICE_HANDLER.getKey(), in.nextString());
+                    break;
+                default:
+                    break;
+            }
+        }
+        in.endObject();
+        return result;
+    }
+    
+    protected abstract void addToCustomizedValueMap(final String jsonName, final JsonReader in, final Map<String, Object> customizedValueMap) throws IOException;
+    
+    private JobCoreConfiguration getJobCoreConfiguration(final String jobName, final String cron, final int shardingTotalCount,
+                                                         final String shardingItemParameters, final String jobParameter, final boolean failover,
+                                                         final boolean misfire, final String description,
+                                                         final JobProperties jobProperties) {
+        return JobCoreConfiguration.newBuilder(jobName, cron, shardingTotalCount)
+                .shardingItemParameters(shardingItemParameters).jobParameter(jobParameter).failover(failover).misfire(misfire).description(description)
+                .jobProperties(JobProperties.JobPropertiesEnum.JOB_EXCEPTION_HANDLER.getKey(), jobProperties.get(JobProperties.JobPropertiesEnum.JOB_EXCEPTION_HANDLER))
+                .jobProperties(JobProperties.JobPropertiesEnum.EXECUTOR_SERVICE_HANDLER.getKey(), jobProperties.get(JobProperties.JobPropertiesEnum.EXECUTOR_SERVICE_HANDLER))
+                .build();
+    }
+    
+    private JobTypeConfiguration getJobTypeConfiguration(
+            final JobCoreConfiguration coreConfig, final JobType jobType, final String jobClass, final boolean streamingProcess, final String scriptCommandLine) {
+        Preconditions.checkNotNull(jobType, "jobType cannot be null.");
+        switch (jobType) {
+            case SIMPLE:
+                Preconditions.checkArgument(!Strings.isNullOrEmpty(jobClass), "jobClass cannot be empty.");
+                return new SimpleJobConfiguration(coreConfig, jobClass);
+            case DATAFLOW:
+                Preconditions.checkArgument(!Strings.isNullOrEmpty(jobClass), "jobClass cannot be empty.");
+                return new DataflowJobConfiguration(coreConfig, jobClass, streamingProcess);
+            case SCRIPT:
+                return new ScriptJobConfiguration(coreConfig, scriptCommandLine);
+            default:
+                throw new UnsupportedOperationException(String.valueOf(jobType));
+        }
+    }
+    
+    protected abstract T getJobRootConfiguration(final JobTypeConfiguration typeConfig, final Map<String, Object> customizedValueMap);
+    
+    @Override
+    public void write(final JsonWriter out, final T value) throws IOException {
+        out.beginObject();
+        out.name("jobName").value(value.getTypeConfig().getCoreConfig().getJobName());
+        out.name("jobClass").value(value.getTypeConfig().getJobClass());
+        out.name("jobType").value(value.getTypeConfig().getJobType().name());
+        out.name("cron").value(value.getTypeConfig().getCoreConfig().getCron());
+        out.name("shardingTotalCount").value(value.getTypeConfig().getCoreConfig().getShardingTotalCount());
+        out.name("shardingItemParameters").value(value.getTypeConfig().getCoreConfig().getShardingItemParameters());
+        out.name("jobParameter").value(value.getTypeConfig().getCoreConfig().getJobParameter());
+        out.name("failover").value(value.getTypeConfig().getCoreConfig().isFailover());
+        out.name("misfire").value(value.getTypeConfig().getCoreConfig().isMisfire());
+        out.name("description").value(value.getTypeConfig().getCoreConfig().getDescription());
+        out.name("jobProperties").jsonValue(value.getTypeConfig().getCoreConfig().getJobProperties().json());
+        if (value.getTypeConfig().getJobType() == JobType.DATAFLOW) {
+            DataflowJobConfiguration dataflowJobConfig = (DataflowJobConfiguration) value.getTypeConfig();
+            out.name("streamingProcess").value(dataflowJobConfig.isStreamingProcess());
+        } else if (value.getTypeConfig().getJobType() == JobType.SCRIPT) {
+            ScriptJobConfiguration scriptJobConfig = (ScriptJobConfiguration) value.getTypeConfig();
+            out.name("scriptCommandLine").value(scriptJobConfig.getScriptCommandLine());
+        }
+        writeCustomized(out, value);
+        out.endObject();
+    }
+    
+    protected abstract void writeCustomized(final JsonWriter out, final T value) throws IOException;
+}
diff --git a/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/json/GsonFactory.java b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/json/GsonFactory.java
new file mode 100644
index 0000000..ada7d36
--- /dev/null
+++ b/elastic-job-cloud-common/src/main/java/io/elasticjob/cloud/util/json/GsonFactory.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.util.json;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.TypeAdapter;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.lang.reflect.Type;
+
+/**
+ * Gson构建器.
+ *
+ * @author caohao
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class GsonFactory {
+    
+    private static final GsonBuilder GSON_BUILDER = new GsonBuilder();
+    
+    private static volatile Gson gson = GSON_BUILDER.create();
+    
+    /**
+     * 注册Gson解析对象.
+     * 
+     * @param type Gson解析对象类型
+     * @param typeAdapter Gson解析对象适配器
+     */
+    public static synchronized void registerTypeAdapter(final Type type, final TypeAdapter typeAdapter) {
+        GSON_BUILDER.registerTypeAdapter(type, typeAdapter);
+        gson = GSON_BUILDER.create();
+    }
+    
+    /**
+     * 获取Gson实例.
+     * 
+     * @return Gson实例
+     */
+    public static Gson getGson() {
+        return gson;
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/AllCoreTests.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/AllCoreTests.java
new file mode 100644
index 0000000..5e03817
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/AllCoreTests.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud;
+
+import io.elasticjob.cloud.api.AllApiTests;
+import io.elasticjob.cloud.executor.AllExecutorTests;
+import io.elasticjob.cloud.statistics.AllStatisticsTests;
+import io.elasticjob.cloud.config.AllConfigTests;
+import io.elasticjob.cloud.context.AllContextTests;
+import io.elasticjob.cloud.event.AllEventTests;
+import io.elasticjob.cloud.exception.AllExceptionTests;
+import io.elasticjob.cloud.reg.AllRegTests;
+import io.elasticjob.cloud.util.AllUtilTests;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+@RunWith(Suite.class)
+@SuiteClasses({
+        AllRegTests.class,
+        AllContextTests.class,
+        AllApiTests.class, 
+        AllConfigTests.class, 
+        AllExecutorTests.class, 
+        AllEventTests.class, 
+        AllExceptionTests.class,
+        AllStatisticsTests.class,
+        AllUtilTests.class
+    })
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class AllCoreTests {
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/api/AllApiTests.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/api/AllApiTests.java
new file mode 100644
index 0000000..07ec020
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/api/AllApiTests.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.api;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+@RunWith(Suite.class)
+@SuiteClasses(ShardingContextTest.class)
+public final class AllApiTests {
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/api/ShardingContextTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/api/ShardingContextTest.java
new file mode 100644
index 0000000..ee47ab1
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/api/ShardingContextTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.api;
+
+import io.elasticjob.cloud.executor.ShardingContexts;
+import io.elasticjob.cloud.fixture.ShardingContextsBuilder;
+import org.junit.Test;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+public final class ShardingContextTest {
+    
+    @Test
+    public void assertNew() {
+        ShardingContexts shardingContexts = ShardingContextsBuilder.getMultipleShardingContexts();
+        ShardingContext actual = new ShardingContext(shardingContexts, 1);
+        assertThat(actual.getJobName(), is(shardingContexts.getJobName()));
+        assertThat(actual.getTaskId(), is(shardingContexts.getTaskId()));
+        assertThat(actual.getShardingTotalCount(), is(shardingContexts.getShardingTotalCount()));
+        assertThat(actual.getJobParameter(), is(shardingContexts.getJobParameter()));
+        assertThat(actual.getShardingItem(), is(1));
+        assertThat(actual.getShardingParameter(), is(shardingContexts.getShardingItemParameters().get(1)));
+    }
+    
+    @Test
+    public void assertToString() {
+        assertThat(new ShardingContext(ShardingContextsBuilder.getMultipleShardingContexts(), 1).toString(), 
+                is("ShardingContext(jobName=test_job, taskId=fake_task_id, shardingTotalCount=2, jobParameter=, shardingItem=1, shardingParameter=B)"));
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/config/AllConfigTests.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/config/AllConfigTests.java
new file mode 100644
index 0000000..b49ce04
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/config/AllConfigTests.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.config;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+@RunWith(Suite.class)
+@SuiteClasses(JobCoreConfigurationTest.class)
+public final class AllConfigTests {
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/config/JobCoreConfigurationTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/config/JobCoreConfigurationTest.java
new file mode 100644
index 0000000..744f54a
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/config/JobCoreConfigurationTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.config;
+
+import io.elasticjob.cloud.executor.handler.impl.DefaultJobExceptionHandler;
+import io.elasticjob.cloud.fixture.handler.IgnoreJobExceptionHandler;
+import io.elasticjob.cloud.executor.handler.JobProperties;
+import org.junit.Test;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+public final class JobCoreConfigurationTest {
+    
+    @Test
+    public void assertBuildAllProperties() {
+        JobCoreConfiguration actual = JobCoreConfiguration.newBuilder("test_job", "0/1 * * * * ?", 3)
+                .shardingItemParameters("0=a,1=b,2=c").jobParameter("param").failover(true).misfire(false).description("desc")
+                .jobProperties("job_exception_handler", IgnoreJobExceptionHandler.class.getName()).build();
+        assertRequiredProperties(actual);
+        assertThat(actual.getShardingItemParameters(), is("0=a,1=b,2=c"));
+        assertThat(actual.getJobParameter(), is("param"));
+        assertTrue(actual.isFailover());
+        assertFalse(actual.isMisfire());
+        assertThat(actual.getDescription(), is("desc"));
+        assertThat(actual.getJobProperties().get(JobProperties.JobPropertiesEnum.JOB_EXCEPTION_HANDLER), is(IgnoreJobExceptionHandler.class.getName()));
+    }
+    
+    @Test
+    public void assertBuildRequiredProperties() {
+        JobCoreConfiguration actual = JobCoreConfiguration.newBuilder("test_job", "0/1 * * * * ?", 3).build();
+        assertRequiredProperties(actual);
+        assertDefaultValues(actual);
+    }
+    
+    @Test
+    public void assertBuildWhenOptionalParametersIsNull() {
+        //noinspection NullArgumentToVariableArgMethod
+        JobCoreConfiguration actual = JobCoreConfiguration.newBuilder("test_job", "0/1 * * * * ?", 3).shardingItemParameters(null).jobParameter(null).description(null).build();
+        assertRequiredProperties(actual);
+        assertDefaultValues(actual);
+    }
+    
+    private void assertRequiredProperties(final JobCoreConfiguration actual) {
+        assertThat(actual.getJobName(), is("test_job"));
+        assertThat(actual.getCron(), is("0/1 * * * * ?"));
+        assertThat(actual.getShardingTotalCount(), is(3));
+    }
+    
+    private void assertDefaultValues(final JobCoreConfiguration actual) {
+        assertThat(actual.getShardingItemParameters(), is(""));
+        assertThat(actual.getJobParameter(), is(""));
+        assertFalse(actual.isFailover());
+        assertTrue(actual.isMisfire());
+        assertThat(actual.getDescription(), is(""));
+        assertThat(actual.getJobProperties().get(JobProperties.JobPropertiesEnum.JOB_EXCEPTION_HANDLER), is(DefaultJobExceptionHandler.class.getName()));
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void assertBuildWhenJobNameIsNull() {
+        JobCoreConfiguration.newBuilder(null, "0/1 * * * * ?", 3).build();
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void assertBuildWhenCronIsNull() {
+        JobCoreConfiguration.newBuilder("test_job", null, 3).build();
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void assertBuildWhenTotalSHardingCountIsNegative() {
+        JobCoreConfiguration.newBuilder(null, "0/1 * * * * ?", -1).build();
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/context/AllContextTests.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/context/AllContextTests.java
new file mode 100644
index 0000000..76f099f
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/context/AllContextTests.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.context;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses(TaskContextTest.class)
+public final class AllContextTests {
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/context/TaskContextTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/context/TaskContextTest.java
new file mode 100644
index 0000000..1b0dddc
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/context/TaskContextTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.context;
+
+import com.google.common.collect.Lists;
+import io.elasticjob.cloud.fixture.context.TaskNode;
+import org.hamcrest.core.Is;
+import org.junit.Test;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.StringStartsWith.startsWith;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+public final class TaskContextTest {
+    
+    @Test
+    public void assertNew() {
+        TaskContext actual = new TaskContext("test_job", Lists.newArrayList(0), ExecutionType.READY, "slave-S0");
+        assertThat(actual.getMetaInfo().getJobName(), is("test_job"));
+        assertThat(actual.getMetaInfo().getShardingItems().get(0), is(0));
+        assertThat(actual.getType(), is(ExecutionType.READY));
+        assertThat(actual.getSlaveId(), is("slave-S0"));
+        assertThat(actual.getId(), startsWith(TaskNode.builder().build().getTaskNodeValue().substring(0, TaskNode.builder().build().getTaskNodeValue().length() - 1)));
+    }
+    
+    @Test
+    public void assertNewWithoutSlaveId() {
+        TaskContext actual = new TaskContext("test_job", Lists.newArrayList(0), ExecutionType.READY);
+        assertThat(actual.getSlaveId(), is("unassigned-slave"));
+    }
+    
+    @Test
+    public void assertGetMetaInfo() {
+        TaskContext actual = new TaskContext("test_job", Lists.newArrayList(0), ExecutionType.READY, "slave-S0");
+        assertThat(actual.getMetaInfo().toString(), is("test_job@-@0"));
+    }
+    
+    @Test
+    public void assertTaskContextFrom() {
+        TaskContext actual = TaskContext.from(TaskNode.builder().build().getTaskNodeValue());
+        assertThat(actual.getId(), Is.is(TaskNode.builder().build().getTaskNodeValue()));
+        assertThat(actual.getMetaInfo().getJobName(), is("test_job"));
+        assertThat(actual.getMetaInfo().getShardingItems().get(0), is(0));
+        assertThat(actual.getType(), is(ExecutionType.READY));
+        assertThat(actual.getSlaveId(), is("slave-S0"));
+    }
+    
+    @Test
+    public void assertMetaInfoFromWithMetaInfo() {
+        TaskContext.MetaInfo actual = TaskContext.MetaInfo.from("test_job@-@1");
+        assertThat(actual.getJobName(), is("test_job"));
+        assertThat(actual.getShardingItems().get(0), is(1));
+    }
+    
+    @Test
+    public void assertMetaInfoFromWithTaskId() {
+        TaskContext.MetaInfo actual = TaskContext.MetaInfo.from("test_job@-@1@-@READY@-@unassigned-slave@-@0");
+        assertThat(actual.getJobName(), is("test_job"));
+        assertThat(actual.getShardingItems().get(0), is(1));
+    }
+    
+    @Test
+    public void assertMetaInfoFromWithMetaInfoWithoutShardingItems() {
+        TaskContext.MetaInfo actual = TaskContext.MetaInfo.from("test_job@-@");
+        assertThat(actual.getJobName(), is("test_job"));
+        assertTrue(actual.getShardingItems().isEmpty());
+    }
+    
+    @Test
+    public void assertMetaInfoFromWithTaskIdWithoutShardingItems() {
+        TaskContext.MetaInfo actual = TaskContext.MetaInfo.from("test_job@-@@-@READY@-@unassigned-slave@-@0");
+        assertThat(actual.getJobName(), is("test_job"));
+        assertTrue(actual.getShardingItems().isEmpty());
+    }
+    
+    @Test
+    public void assertGetIdForUnassignedSlave() {
+        assertThat(TaskContext.getIdForUnassignedSlave("test_job@-@0@-@READY@-@slave-S0@-@0"), is("test_job@-@0@-@READY@-@unassigned-slave@-@0"));
+    }
+    
+    @Test
+    public void assertGetTaskName() {
+        TaskContext actual = TaskContext.from(TaskNode.builder().build().getTaskNodeValue());
+        assertThat(actual.getTaskName(), is("test_job@-@0@-@READY@-@slave-S0"));
+    }
+    
+    @Test
+    public void assertGetExecutorId() {
+        TaskContext actual = TaskContext.from(TaskNode.builder().build().getTaskNodeValue());
+        assertThat(actual.getExecutorId("app"), is("app@-@slave-S0"));
+    }
+    
+    @Test
+    public void assertSetSlaveId() {
+        TaskContext actual = new TaskContext("test_job", Lists.newArrayList(0), ExecutionType.READY, "slave-S0");
+        assertThat(actual.getSlaveId(), is("slave-S0"));
+        actual.setSlaveId("slave-S1");
+        assertThat(actual.getSlaveId(), is("slave-S1"));
+    }
+    
+    @Test
+    public void assertSetIdle() {
+        TaskContext actual = new TaskContext("test_job", Lists.newArrayList(0), ExecutionType.READY, "slave-S0");
+        assertFalse(actual.isIdle());
+        actual.setIdle(true);
+        assertTrue(actual.isIdle());
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/AllEventTests.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/AllEventTests.java
new file mode 100644
index 0000000..79fe411
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/AllEventTests.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event;
+
+import io.elasticjob.cloud.event.rdb.JobEventRdbConfigurationTest;
+import io.elasticjob.cloud.event.rdb.JobEventRdbIdentityTest;
+import io.elasticjob.cloud.event.rdb.JobEventRdbListenerTest;
+import io.elasticjob.cloud.event.rdb.JobEventRdbSearchTest;
+import io.elasticjob.cloud.event.rdb.JobEventRdbStorageTest;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+        JobExecutionEventTest.class, 
+        JobEventBusTest.class, 
+        JobEventRdbIdentityTest.class,
+        JobEventRdbConfigurationTest.class, 
+        JobEventRdbListenerTest.class, 
+        JobEventRdbStorageTest.class,
+        JobEventRdbSearchTest.class
+    })
+public final class AllEventTests {
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/JobEventBusTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/JobEventBusTest.java
new file mode 100644
index 0000000..8bc11cc
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/JobEventBusTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event;
+
+import com.google.common.eventbus.EventBus;
+import io.elasticjob.cloud.event.fixture.JobEventCaller;
+import io.elasticjob.cloud.event.fixture.TestJobEventConfiguration;
+import io.elasticjob.cloud.event.fixture.TestJobEventListener;
+import io.elasticjob.cloud.event.type.JobExecutionEvent;
+import io.elasticjob.cloud.event.fixture.TestJobEventFailureConfiguration;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.unitils.util.ReflectionUtils;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class JobEventBusTest {
+    
+    @Mock
+    private JobEventCaller jobEventCaller;
+    
+    @Mock
+    private EventBus eventBus;
+    
+    private JobEventBus jobEventBus;
+    
+    @Test
+    public void assertRegisterFailure() throws NoSuchFieldException {
+        jobEventBus = new JobEventBus(new TestJobEventFailureConfiguration());
+        assertIsRegistered(false);
+    }
+    
+    @Test
+    public void assertPost() throws InterruptedException, NoSuchFieldException {
+        jobEventBus = new JobEventBus(new TestJobEventConfiguration(jobEventCaller));
+        assertIsRegistered(true);
+        jobEventBus.post(new JobExecutionEvent("fake_task_id", "test_event_bus_job", JobExecutionEvent.ExecutionSource.NORMAL_TRIGGER, 0));
+        while (!TestJobEventListener.isExecutionEventCalled()) {
+            Thread.sleep(100L);
+        }
+        verify(jobEventCaller).call();
+    }
+    
+    @Test
+    public void assertPostWithoutListener() throws NoSuchFieldException {
+        jobEventBus = new JobEventBus();
+        assertIsRegistered(false);
+        ReflectionUtils.setFieldValue(jobEventBus, "eventBus", eventBus);
+        jobEventBus.post(new JobExecutionEvent("fake_task_id", "test_event_bus_job", JobExecutionEvent.ExecutionSource.NORMAL_TRIGGER, 0));
+        verify(eventBus, times(0)).post(ArgumentMatchers.<JobEvent>any());
+    }
+    
+    private void assertIsRegistered(final boolean actual) throws NoSuchFieldException {
+        assertThat((boolean) ReflectionUtils.getFieldValue(jobEventBus, JobEventBus.class.getDeclaredField("isRegistered")), is(actual));
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/JobExecutionEventTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/JobExecutionEventTest.java
new file mode 100644
index 0000000..e234d3d
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/JobExecutionEventTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event;
+
+import io.elasticjob.cloud.event.type.JobExecutionEvent;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+public final class JobExecutionEventTest {
+    
+    @Test
+    public void assertNewJobExecutionEvent() {
+        JobExecutionEvent actual = new JobExecutionEvent("fake_task_id", "test_job", JobExecutionEvent.ExecutionSource.NORMAL_TRIGGER, 0);
+        assertThat(actual.getJobName(), is("test_job"));
+        assertThat(actual.getSource(), is(JobExecutionEvent.ExecutionSource.NORMAL_TRIGGER));
+        assertThat(actual.getShardingItem(), is(0));
+        assertNotNull(actual.getHostname());
+        assertNotNull(actual.getStartTime());
+        assertNull(actual.getCompleteTime());
+        assertFalse(actual.isSuccess());
+        assertThat(actual.getFailureCause(), is(""));
+    }
+    
+    @Test
+    public void assertExecutionSuccess() {
+        JobExecutionEvent startEvent = new JobExecutionEvent("fake_task_id", "test_job", JobExecutionEvent.ExecutionSource.NORMAL_TRIGGER, 0);
+        JobExecutionEvent successEvent = startEvent.executionSuccess();
+        assertNotNull(successEvent.getCompleteTime());
+        assertTrue(successEvent.isSuccess());
+    }
+    
+    @Test
+    public void assertExecutionFailure() {
+        JobExecutionEvent startEvent = new JobExecutionEvent("fake_task_id", "test_job", JobExecutionEvent.ExecutionSource.NORMAL_TRIGGER, 0);
+        JobExecutionEvent failureEvent = startEvent.executionFailure(new RuntimeException("failure"));
+        assertNotNull(failureEvent.getCompleteTime());
+        assertFalse(failureEvent.isSuccess());
+        assertThat(failureEvent.getFailureCause(), startsWith("java.lang.RuntimeException: failure"));
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/fixture/JobEventCaller.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/fixture/JobEventCaller.java
new file mode 100644
index 0000000..8b846e6
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/fixture/JobEventCaller.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event.fixture;
+
+public interface JobEventCaller {
+    
+    void call();
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/fixture/TestJobEventConfiguration.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/fixture/TestJobEventConfiguration.java
new file mode 100644
index 0000000..3d485f4
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/fixture/TestJobEventConfiguration.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event.fixture;
+
+import io.elasticjob.cloud.event.JobEventConfiguration;
+import io.elasticjob.cloud.event.JobEventListener;
+import io.elasticjob.cloud.event.JobEventListenerConfigurationException;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public final class TestJobEventConfiguration extends TestJobEventIdentity implements JobEventConfiguration {
+    
+    private final JobEventCaller jobEventCaller;
+    
+    @Override
+    public JobEventListener createJobEventListener() throws JobEventListenerConfigurationException {
+        return new TestJobEventListener(jobEventCaller);
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/fixture/TestJobEventFailureConfiguration.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/fixture/TestJobEventFailureConfiguration.java
new file mode 100644
index 0000000..5024124
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/fixture/TestJobEventFailureConfiguration.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event.fixture;
+
+import io.elasticjob.cloud.event.JobEventConfiguration;
+import io.elasticjob.cloud.event.JobEventListener;
+import io.elasticjob.cloud.event.JobEventListenerConfigurationException;
+
+public final class TestJobEventFailureConfiguration extends TestJobEventIdentity implements JobEventConfiguration {
+    
+    @Override
+    public JobEventListener createJobEventListener() throws JobEventListenerConfigurationException {
+        throw new JobEventListenerConfigurationException(new RuntimeException("assert failure"));
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/fixture/TestJobEventIdentity.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/fixture/TestJobEventIdentity.java
new file mode 100644
index 0000000..15738e3
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/fixture/TestJobEventIdentity.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event.fixture;
+
+import io.elasticjob.cloud.event.JobEventIdentity;
+
+public class TestJobEventIdentity implements JobEventIdentity {
+    
+    @Override
+    public String getIdentity() {
+        return "test";
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/fixture/TestJobEventListener.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/fixture/TestJobEventListener.java
new file mode 100644
index 0000000..a7dc317
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/fixture/TestJobEventListener.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event.fixture;
+
+import io.elasticjob.cloud.event.JobEventListener;
+import io.elasticjob.cloud.event.type.JobExecutionEvent;
+import io.elasticjob.cloud.event.type.JobStatusTraceEvent;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public final class TestJobEventListener extends TestJobEventIdentity implements JobEventListener {
+    
+    @Getter
+    private static volatile boolean executionEventCalled;
+    
+    private final JobEventCaller jobEventCaller;
+    
+    @Override
+    public void listen(final JobExecutionEvent jobExecutionEvent) {
+        jobEventCaller.call();
+        executionEventCalled = true;
+    }
+    
+    @Override
+    public void listen(final JobStatusTraceEvent jobStatusTraceEvent) {
+        jobEventCaller.call();
+    }
+    
+    public static void reset() {
+        executionEventCalled = false;
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/rdb/JobEventRdbConfigurationTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/rdb/JobEventRdbConfigurationTest.java
new file mode 100644
index 0000000..1d04ad9
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/rdb/JobEventRdbConfigurationTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event.rdb;
+
+import io.elasticjob.cloud.event.JobEventListenerConfigurationException;
+import org.apache.commons.dbcp.BasicDataSource;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+public final class JobEventRdbConfigurationTest {
+    
+    @Test
+    public void assertGetDataSource() throws JobEventListenerConfigurationException {
+        BasicDataSource dataSource = new BasicDataSource();
+        dataSource.setDriverClassName(org.h2.Driver.class.getName());
+        dataSource.setUrl("jdbc:h2:mem:job_event_storage");
+        dataSource.setUsername("sa");
+        dataSource.setPassword("");
+        assertThat((BasicDataSource) (new JobEventRdbConfiguration(dataSource).getDataSource()), is(dataSource));
+    }
+    
+    @Test
+    public void assertCreateJobEventListenerSuccess() throws JobEventListenerConfigurationException {
+        BasicDataSource dataSource = new BasicDataSource();
+        dataSource.setDriverClassName(org.h2.Driver.class.getName());
+        dataSource.setUrl("jdbc:h2:mem:job_event_storage");
+        dataSource.setUsername("sa");
+        dataSource.setPassword("");
+        assertThat(new JobEventRdbConfiguration(dataSource).createJobEventListener(), instanceOf(JobEventRdbListener.class));
+    }
+    
+    @Test(expected = JobEventListenerConfigurationException.class)
+    public void assertCreateJobEventListenerFailure() throws JobEventListenerConfigurationException {
+        new JobEventRdbConfiguration(new BasicDataSource()).createJobEventListener();
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/rdb/JobEventRdbIdentityTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/rdb/JobEventRdbIdentityTest.java
new file mode 100644
index 0000000..76b8812
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/rdb/JobEventRdbIdentityTest.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event.rdb;
+
+import org.junit.Test;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+public final class JobEventRdbIdentityTest {
+    
+    @Test
+    public void assertGetIdentity() {
+        assertThat(new JobEventRdbIdentity().getIdentity(), is("rdb"));
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/rdb/JobEventRdbListenerTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/rdb/JobEventRdbListenerTest.java
new file mode 100644
index 0000000..d7d066d
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/rdb/JobEventRdbListenerTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event.rdb;
+        
+import io.elasticjob.cloud.context.ExecutionType;
+import io.elasticjob.cloud.event.JobEventBus;
+import io.elasticjob.cloud.event.JobEventListenerConfigurationException;
+import io.elasticjob.cloud.event.type.JobExecutionEvent;
+import io.elasticjob.cloud.event.type.JobStatusTraceEvent;
+import org.apache.commons.dbcp.BasicDataSource;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.unitils.util.ReflectionUtils;
+
+import java.sql.SQLException;
+
+import static org.mockito.Mockito.atMost;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class JobEventRdbListenerTest {
+    
+    private static final String JOB_NAME = "test_rdb_event_listener";
+    
+    @Mock
+    private JobEventRdbConfiguration jobEventRdbConfiguration;
+    
+    @Mock
+    private JobEventRdbStorage repository;
+    
+    private JobEventBus jobEventBus;
+    
+    @Before
+    public void setUp() throws JobEventListenerConfigurationException, SQLException, NoSuchFieldException {
+        BasicDataSource dataSource = new BasicDataSource();
+        dataSource.setDriverClassName(org.h2.Driver.class.getName());
+        dataSource.setUrl("jdbc:h2:mem:job_event_storage");
+        dataSource.setUsername("sa");
+        dataSource.setPassword("");
+        JobEventRdbListener jobEventRdbListener = new JobEventRdbListener(dataSource);
+        ReflectionUtils.setFieldValue(jobEventRdbListener, "repository", repository);
+        when(jobEventRdbConfiguration.createJobEventListener()).thenReturn(jobEventRdbListener);
+        jobEventBus = new JobEventBus(jobEventRdbConfiguration);
+    }
+    
+    @Test
+    public void assertPostJobExecutionEvent() {
+        JobExecutionEvent jobExecutionEvent = new JobExecutionEvent("fake_task_id", JOB_NAME, JobExecutionEvent.ExecutionSource.NORMAL_TRIGGER, 0);
+        jobEventBus.post(jobExecutionEvent);
+        verify(repository, atMost(1)).addJobExecutionEvent(jobExecutionEvent);
+    }
+    
+    @Test
+    public void assertPostJobStatusTraceEvent() {
+        JobStatusTraceEvent jobStatusTraceEvent = new JobStatusTraceEvent(
+                JOB_NAME, "fake_task_id", "fake_slave_id",  JobStatusTraceEvent.Source.LITE_EXECUTOR, ExecutionType.READY, "0", JobStatusTraceEvent.State.TASK_RUNNING, "message is empty.");
+        jobEventBus.post(jobStatusTraceEvent);
+        verify(repository, atMost(1)).addJobStatusTraceEvent(jobStatusTraceEvent);
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/rdb/JobEventRdbSearchTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/rdb/JobEventRdbSearchTest.java
new file mode 100644
index 0000000..bab6943
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/rdb/JobEventRdbSearchTest.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event.rdb;
+
+import io.elasticjob.cloud.context.ExecutionType;
+import io.elasticjob.cloud.event.type.JobExecutionEvent;
+import io.elasticjob.cloud.event.type.JobStatusTraceEvent;
+import org.apache.commons.dbcp.BasicDataSource;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.sql.SQLException;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class JobEventRdbSearchTest {
+    
+    private static JobEventRdbStorage storage;
+    
+    private static JobEventRdbSearch  repository;
+    
+    @BeforeClass
+    public static void setUpClass() throws SQLException {
+        BasicDataSource dataSource = new BasicDataSource();
+        dataSource.setDriverClassName(org.h2.Driver.class.getName());
+        dataSource.setUrl("jdbc:h2:mem:");
+        dataSource.setUsername("sa");
+        dataSource.setPassword("");
+        storage = new JobEventRdbStorage(dataSource);
+        repository = new JobEventRdbSearch(dataSource);
+        initStorage();
+    }
+    
+    private static void initStorage() {
+        for (int i = 1; i <= 500; i++) {
+            JobExecutionEvent startEvent = new JobExecutionEvent("fake_task_id", "test_job_" + i, JobExecutionEvent.ExecutionSource.NORMAL_TRIGGER, 0);
+            storage.addJobExecutionEvent(startEvent);
+            if (i % 2 == 0) {
+                JobExecutionEvent successEvent = startEvent.executionSuccess();
+                storage.addJobExecutionEvent(successEvent);
+            }
+            storage.addJobStatusTraceEvent(
+                    new JobStatusTraceEvent("test_job_" + i, "fake_failed_failover_task_id", "fake_slave_id", 
+                            JobStatusTraceEvent.Source.LITE_EXECUTOR, ExecutionType.FAILOVER, "0", JobStatusTraceEvent.State.TASK_FAILED, "message is empty."));
+        }
+    }
+    
+    @Test
+    public void assertFindJobExecutionEventsWithPageSizeAndNumber() {
+        JobEventRdbSearch.Result<JobExecutionEvent> result = repository.findJobExecutionEvents(new JobEventRdbSearch.Condition(10, 1, null, null, null, null, null));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(10));
+        result = repository.findJobExecutionEvents(new JobEventRdbSearch.Condition(50, 1, null, null, null, null, null));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(50));
+        result = repository.findJobExecutionEvents(new JobEventRdbSearch.Condition(100, 5, null, null, null, null, null));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(100));
+        result = repository.findJobExecutionEvents(new JobEventRdbSearch.Condition(100, 6, null, null, null, null, null));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(0));
+    }
+    
+    @Test
+    public void assertFindJobExecutionEventsWithErrorPageSizeAndNumber() {
+        JobEventRdbSearch.Result<JobExecutionEvent> result = repository.findJobExecutionEvents(new JobEventRdbSearch.Condition(-1, -1, null, null, null, null, null));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(10));
+    }
+    
+    @Test
+    public void assertFindJobExecutionEventsWithSort() {
+        JobEventRdbSearch.Result<JobExecutionEvent> result = repository.findJobExecutionEvents(new JobEventRdbSearch.Condition(10, 1, "jobName", "ASC", null, null, null));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(10));
+        assertThat(result.getRows().get(0).getJobName(), is("test_job_1"));
+        result = repository.findJobExecutionEvents(new JobEventRdbSearch.Condition(10, 1, "jobName", "DESC", null, null, null));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(10));
+        assertThat(result.getRows().get(0).getJobName(), is("test_job_99"));
+    }
+    
+    @Test
+    public void assertFindJobExecutionEventsWithErrorSort() {
+        JobEventRdbSearch.Result<JobExecutionEvent> result = repository.findJobExecutionEvents(new JobEventRdbSearch.Condition(10, 1, "jobName", "ERROR_SORT", null, null, null));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(10));
+        assertThat(result.getRows().get(0).getJobName(), is("test_job_1"));
+        result = repository.findJobExecutionEvents(new JobEventRdbSearch.Condition(10, 1, "notExistField", "ASC", null, null, null));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(10));
+    }
+    
+    @Test
+    public void assertFindJobExecutionEventsWithTime() {
+        Date now = new Date();
+        Date tenMinutesBefore = new Date(now.getTime() - 10 * 60 * 1000);
+        JobEventRdbSearch.Result<JobExecutionEvent> result = repository.findJobExecutionEvents(new JobEventRdbSearch.Condition(10, 1, null, null, tenMinutesBefore, null, null));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(10));
+        result = repository.findJobExecutionEvents(new JobEventRdbSearch.Condition(10, 1, null, null, now, null, null));
+        assertThat(result.getTotal(), is(0));
+        assertThat(result.getRows().size(), is(0));
+        result = repository.findJobExecutionEvents(new JobEventRdbSearch.Condition(10, 1, null, null, null, tenMinutesBefore, null));
+        assertThat(result.getTotal(), is(0));
+        assertThat(result.getRows().size(), is(0));
+        result = repository.findJobExecutionEvents(new JobEventRdbSearch.Condition(10, 1, null, null, null, now, null));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(10));
+        result = repository.findJobExecutionEvents(new JobEventRdbSearch.Condition(10, 1, null, null, tenMinutesBefore, now, null));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(10));
+    }
+    
+    @Test
+    public void assertFindJobExecutionEventsWithFields() {
+        Map<String, Object> fields = new HashMap<>();
+        fields.put("isSuccess", "1");
+        JobEventRdbSearch.Result<JobExecutionEvent> result = repository.findJobExecutionEvents(new JobEventRdbSearch.Condition(10, 1, null, null, null, null, fields));
+        assertThat(result.getTotal(), is(250));
+        assertThat(result.getRows().size(), is(10));
+        fields.put("isSuccess", null);
+        fields.put("jobName", "test_job_1");
+        result = repository.findJobExecutionEvents(new JobEventRdbSearch.Condition(10, 1, null, null, null, null, fields));
+        assertThat(result.getTotal(), is(1));
+        assertThat(result.getRows().size(), is(1));
+    }
+    
+    @Test
+    public void assertFindJobExecutionEventsWithErrorFields() {
+        Map<String, Object> fields = new HashMap<>();
+        fields.put("notExistField", "some value");
+        JobEventRdbSearch.Result<JobExecutionEvent> result = repository.findJobExecutionEvents(new JobEventRdbSearch.Condition(10, 1, null, null, null, null, fields));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(10));
+    }
+    
+    @Test
+    public void assertFindJobStatusTraceEventsWithPageSizeAndNumber() {
+        JobEventRdbSearch.Result<JobStatusTraceEvent> result = repository.findJobStatusTraceEvents(new JobEventRdbSearch.Condition(10, 1, null, null, null, null, null));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(10));
+        result = repository.findJobStatusTraceEvents(new JobEventRdbSearch.Condition(50, 1, null, null, null, null, null));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(50));
+        result = repository.findJobStatusTraceEvents(new JobEventRdbSearch.Condition(100, 5, null, null, null, null, null));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(100));
+        result = repository.findJobStatusTraceEvents(new JobEventRdbSearch.Condition(100, 6, null, null, null, null, null));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(0));
+    }
+    
+    @Test
+    public void assertFindJobStatusTraceEventsWithErrorPageSizeAndNumber() {
+        JobEventRdbSearch.Result<JobStatusTraceEvent> result = repository.findJobStatusTraceEvents(new JobEventRdbSearch.Condition(-1, -1, null, null, null, null, null));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(10));
+    }
+    
+    @Test
+    public void assertFindJobStatusTraceEventsWithSort() {
+        JobEventRdbSearch.Result<JobStatusTraceEvent> result = repository.findJobStatusTraceEvents(new JobEventRdbSearch.Condition(10, 1, "jobName", "ASC", null, null, null));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(10));
+        assertThat(result.getRows().get(0).getJobName(), is("test_job_1"));
+        result = repository.findJobStatusTraceEvents(new JobEventRdbSearch.Condition(10, 1, "jobName", "DESC", null, null, null));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(10));
+        assertThat(result.getRows().get(0).getJobName(), is("test_job_99"));
+    }
+    
+    @Test
+    public void assertFindJobStatusTraceEventsWithErrorSort() {
+        JobEventRdbSearch.Result<JobStatusTraceEvent> result = repository.findJobStatusTraceEvents(new JobEventRdbSearch.Condition(10, 1, "jobName", "ERROR_SORT", null, null, null));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(10));
+        assertThat(result.getRows().get(0).getJobName(), is("test_job_1"));
+        result = repository.findJobStatusTraceEvents(new JobEventRdbSearch.Condition(10, 1, "notExistField", "ASC", null, null, null));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(10));
+    }
+    
+    @Test
+    public void assertFindJobStatusTraceEventsWithTime() {
+        Date now = new Date();
+        Date tenMinutesBefore = new Date(now.getTime() - 10 * 60 * 1000);
+        JobEventRdbSearch.Result<JobStatusTraceEvent> result = repository.findJobStatusTraceEvents(new JobEventRdbSearch.Condition(10, 1, null, null, tenMinutesBefore, null, null));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(10));
+        result = repository.findJobStatusTraceEvents(new JobEventRdbSearch.Condition(10, 1, null, null, now, null, null));
+        assertThat(result.getTotal(), is(0));
+        assertThat(result.getRows().size(), is(0));
+        result = repository.findJobStatusTraceEvents(new JobEventRdbSearch.Condition(10, 1, null, null, null, tenMinutesBefore, null));
+        assertThat(result.getTotal(), is(0));
+        assertThat(result.getRows().size(), is(0));
+        result = repository.findJobStatusTraceEvents(new JobEventRdbSearch.Condition(10, 1, null, null, null, now, null));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(10));
+        result = repository.findJobStatusTraceEvents(new JobEventRdbSearch.Condition(10, 1, null, null, tenMinutesBefore, now, null));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(10));
+    }
+    
+    @Test
+    public void assertFindJobStatusTraceEventsWithFields() {
+        Map<String, Object> fields = new HashMap<>();
+        fields.put("jobName", "test_job_1");
+        JobEventRdbSearch.Result<JobStatusTraceEvent> result = repository.findJobStatusTraceEvents(new JobEventRdbSearch.Condition(10, 1, null, null, null, null, fields));
+        assertThat(result.getTotal(), is(1));
+        assertThat(result.getRows().size(), is(1));
+    }
+    
+    @Test
+    public void assertFindJobStatusTraceEventsWithErrorFields() {
+        Map<String, Object> fields = new HashMap<>();
+        fields.put("notExistField", "some value");
+        JobEventRdbSearch.Result<JobStatusTraceEvent> result = repository.findJobStatusTraceEvents(new JobEventRdbSearch.Condition(10, 1, null, null, null, null, fields));
+        assertThat(result.getTotal(), is(500));
+        assertThat(result.getRows().size(), is(10));
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/rdb/JobEventRdbStorageTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/rdb/JobEventRdbStorageTest.java
new file mode 100644
index 0000000..41a7f5b
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/event/rdb/JobEventRdbStorageTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.event.rdb;
+
+import io.elasticjob.cloud.context.ExecutionType;
+import io.elasticjob.cloud.event.type.JobExecutionEvent;
+import io.elasticjob.cloud.event.type.JobStatusTraceEvent;
+import org.apache.commons.dbcp.BasicDataSource;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.junit.Assert.assertThat;
+
+public class JobEventRdbStorageTest {
+    
+    private JobEventRdbStorage storage;
+    
+    @Before
+    public void setup() throws SQLException {
+        BasicDataSource dataSource = new BasicDataSource();
+        dataSource.setDriverClassName(org.h2.Driver.class.getName());
+        dataSource.setUrl("jdbc:h2:mem:job_event_storage");
+        dataSource.setUsername("sa");
+        dataSource.setPassword("");
+        storage = new JobEventRdbStorage(dataSource);
+    }
+    
+    @Test
+    public void assertAddJobExecutionEvent() throws SQLException {
+        assertTrue(storage.addJobExecutionEvent(new JobExecutionEvent("fake_task_id", "test_job", JobExecutionEvent.ExecutionSource.NORMAL_TRIGGER, 0)));
+    }
+    
+    @Test
+    public void assertAddJobStatusTraceEvent() throws SQLException {
+        assertTrue(storage.addJobStatusTraceEvent(new JobStatusTraceEvent("test_job", "fake_task_id", "fake_slave_id", JobStatusTraceEvent.Source.LITE_EXECUTOR, ExecutionType.READY, "0", 
+                JobStatusTraceEvent.State.TASK_RUNNING, "message is empty.")));
+    }
+    
+    @Test
+    public void assertAddJobStatusTraceEventWhenFailoverWithTaskStagingState() throws SQLException {
+        JobStatusTraceEvent jobStatusTraceEvent = new JobStatusTraceEvent("test_job", "fake_failover_task_id", "fake_slave_id", JobStatusTraceEvent.Source.LITE_EXECUTOR, ExecutionType.FAILOVER, "0",
+                JobStatusTraceEvent.State.TASK_STAGING, "message is empty.");
+        jobStatusTraceEvent.setOriginalTaskId("original_fake_failover_task_id");
+        assertThat(storage.getJobStatusTraceEvents("fake_failover_task_id").size(), is(0));
+        storage.addJobStatusTraceEvent(jobStatusTraceEvent);
+        assertThat(storage.getJobStatusTraceEvents("fake_failover_task_id").size(), is(1));
+    }
+    
+    @Test
+    public void assertAddJobStatusTraceEventWhenFailoverWithTaskFailedState() throws SQLException {
+        JobStatusTraceEvent stagingJobStatusTraceEvent = new JobStatusTraceEvent("test_job", "fake_failed_failover_task_id", "fake_slave_id", JobStatusTraceEvent.Source.LITE_EXECUTOR, ExecutionType.FAILOVER, "0",
+                JobStatusTraceEvent.State.TASK_STAGING, "message is empty.");
+        stagingJobStatusTraceEvent.setOriginalTaskId("original_fake_failed_failover_task_id");
+        storage.addJobStatusTraceEvent(stagingJobStatusTraceEvent);
+        JobStatusTraceEvent failedJobStatusTraceEvent = new JobStatusTraceEvent("test_job", "fake_failed_failover_task_id", "fake_slave_id", JobStatusTraceEvent.Source.LITE_EXECUTOR, ExecutionType.FAILOVER, "0",
+                JobStatusTraceEvent.State.TASK_FAILED, "message is empty.");
+        storage.addJobStatusTraceEvent(failedJobStatusTraceEvent);
+        List<JobStatusTraceEvent> jobStatusTraceEvents = storage.getJobStatusTraceEvents("fake_failed_failover_task_id");
+        assertThat(jobStatusTraceEvents.size(), is(2));
+        for (JobStatusTraceEvent jobStatusTraceEvent : jobStatusTraceEvents) {
+            assertThat(jobStatusTraceEvent.getOriginalTaskId(), is("original_fake_failed_failover_task_id"));
+        }
+    }
+    
+    @Test
+    public void assertUpdateJobExecutionEventWhenSuccess() throws SQLException {
+        JobExecutionEvent startEvent = new JobExecutionEvent("fake_task_id", "test_job", JobExecutionEvent.ExecutionSource.NORMAL_TRIGGER, 0);
+        assertTrue(storage.addJobExecutionEvent(startEvent));
+        JobExecutionEvent successEvent = startEvent.executionSuccess();
+        assertTrue(storage.addJobExecutionEvent(successEvent));
+    }
+    
+    @Test
+    public void assertUpdateJobExecutionEventWhenFailure() throws SQLException {
+        JobExecutionEvent startEvent = new JobExecutionEvent("fake_task_id", "test_job", JobExecutionEvent.ExecutionSource.NORMAL_TRIGGER, 0);
+        assertTrue(storage.addJobExecutionEvent(startEvent));
+        JobExecutionEvent failureEvent = startEvent.executionFailure(new RuntimeException("failure"));
+        assertTrue(storage.addJobExecutionEvent(failureEvent));
+        assertThat(failureEvent.getFailureCause(), startsWith("java.lang.RuntimeException: failure"));
+        assertTrue(null != failureEvent.getCompleteTime());
+    }
+    
+    @Test
+    public void assertUpdateJobExecutionEventWhenSuccessAndConflict() throws SQLException {
+        JobExecutionEvent startEvent = new JobExecutionEvent("fake_task_id", "test_job", JobExecutionEvent.ExecutionSource.NORMAL_TRIGGER, 0);
+        JobExecutionEvent successEvent = startEvent.executionSuccess();
+        assertTrue(storage.addJobExecutionEvent(successEvent));
+        assertFalse(storage.addJobExecutionEvent(startEvent));
+    }
+    
+    @Test
+    public void assertUpdateJobExecutionEventWhenFailureAndConflict() throws SQLException {
+        JobExecutionEvent startEvent = new JobExecutionEvent("fake_task_id", "test_job", JobExecutionEvent.ExecutionSource.NORMAL_TRIGGER, 0);
+        JobExecutionEvent failureEvent = startEvent.executionFailure(new RuntimeException("failure"));
+        assertTrue(storage.addJobExecutionEvent(failureEvent));
+        assertThat(failureEvent.getFailureCause(), startsWith("java.lang.RuntimeException: failure"));
+        assertFalse(storage.addJobExecutionEvent(startEvent));
+    }
+    
+    @Test
+    public void assertUpdateJobExecutionEventWhenFailureAndMessageExceed() throws SQLException {
+        JobExecutionEvent startEvent = new JobExecutionEvent("fake_task_id", "test_job", JobExecutionEvent.ExecutionSource.NORMAL_TRIGGER, 0);
+        assertTrue(storage.addJobExecutionEvent(startEvent));
+        StringBuilder failureMsg = new StringBuilder();
+        for (int i = 0; i < 600; i++) {
+            failureMsg.append(i);
+        }
+        JobExecutionEvent failEvent = startEvent.executionFailure(new RuntimeException("failure" + failureMsg.toString()));
+        assertTrue(storage.addJobExecutionEvent(failEvent));
+        assertThat(failEvent.getFailureCause(), startsWith("java.lang.RuntimeException: failure"));
+    }
+    
+    @Test
+    public void assertFindJobExecutionEvent() throws SQLException {
+        storage.addJobExecutionEvent(new JobExecutionEvent("fake_task_id", "test_job", JobExecutionEvent.ExecutionSource.NORMAL_TRIGGER, 0));
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/exception/AllExceptionTests.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/exception/AllExceptionTests.java
new file mode 100644
index 0000000..387864b
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/exception/AllExceptionTests.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.exception;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+@RunWith(Suite.class)
+@SuiteClasses({
+        JobConfigurationExceptionTest.class, 
+        JobExecutionEnvironmentExceptionTest.class, 
+        JobSystemExceptionTest.class,
+        ExceptionUtilTest.class,
+        JobStatisticExceptionTest.class
+    })
+public final class AllExceptionTests {
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/exception/ExceptionUtilTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/exception/ExceptionUtilTest.java
new file mode 100644
index 0000000..04c79ad
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/exception/ExceptionUtilTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.exception;
+
+import org.junit.Test;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+public final class ExceptionUtilTest {
+    
+    @Test
+    public void assertTransformWithError() {
+        assertTrue(ExceptionUtil.transform(new Error("Error")).startsWith("java.lang.Error"));
+    }
+    
+    @Test
+    public void assertTransformWithException() {
+        assertTrue(ExceptionUtil.transform(new Exception("Exception")).startsWith("java.lang.Exception"));
+    }
+    
+    @Test
+    public void assertTransformWithNull() {
+        assertThat(ExceptionUtil.transform(null), is(""));
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/exception/JobConfigurationExceptionTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/exception/JobConfigurationExceptionTest.java
new file mode 100644
index 0000000..9c93547
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/exception/JobConfigurationExceptionTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.exception;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+public final class JobConfigurationExceptionTest {
+    
+    @Test
+    public void assertGetMessage() {
+        assertThat(new JobConfigurationException("message is: '%s'", "test").getMessage(), is("message is: 'test'"));
+    }
+    
+    @Test
+    public void assertGetCause() {
+        assertThat(new JobConfigurationException(new RuntimeException()).getCause(), instanceOf(RuntimeException.class));
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/exception/JobExecutionEnvironmentExceptionTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/exception/JobExecutionEnvironmentExceptionTest.java
new file mode 100644
index 0000000..f908923
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/exception/JobExecutionEnvironmentExceptionTest.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.exception;
+
+import org.junit.Test;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+public final class JobExecutionEnvironmentExceptionTest {
+    
+    @Test
+    public void assertGetMessage() {
+        assertThat(new JobExecutionEnvironmentException("message is: '%s'", "test").getMessage(), is("message is: 'test'"));
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/exception/JobStatisticExceptionTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/exception/JobStatisticExceptionTest.java
new file mode 100644
index 0000000..0b57e6e
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/exception/JobStatisticExceptionTest.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.exception;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.assertThat;
+
+public class JobStatisticExceptionTest {
+    
+    @Test
+    public void assertGetCause() {
+        assertThat(new JobStatisticException(new RuntimeException()).getCause(), instanceOf(RuntimeException.class));
+    }
+
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/exception/JobSystemExceptionTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/exception/JobSystemExceptionTest.java
new file mode 100644
index 0000000..ca18c0c
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/exception/JobSystemExceptionTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.exception;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+public final class JobSystemExceptionTest {
+    
+    @Test
+    public void assertGetMessage() {
+        assertThat(new JobSystemException("message is: '%s'", "test").getMessage(), is("message is: 'test'"));
+    }
+    
+    @Test
+    public void assertGetCause() {
+        assertThat(new JobSystemException(new RuntimeException()).getCause(), instanceOf(RuntimeException.class));
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/AllExecutorTests.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/AllExecutorTests.java
new file mode 100644
index 0000000..d93362c
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/AllExecutorTests.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.executor;
+
+import io.elasticjob.cloud.executor.handler.ExecutorServiceHandlerRegistryTest;
+import io.elasticjob.cloud.executor.handler.JobPropertiesTest;
+import io.elasticjob.cloud.executor.handler.impl.DefaultJobExceptionHandlerTest;
+import io.elasticjob.cloud.executor.type.DataflowJobExecutorTest;
+import io.elasticjob.cloud.executor.type.ScriptJobExecutorTest;
+import io.elasticjob.cloud.executor.type.SimpleJobExecutorTest;
+import io.elasticjob.cloud.executor.type.WrongJobExecutorTest;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+@RunWith(Suite.class)
+@SuiteClasses({
+        JobExecutorFactoryTest.class,
+        ExecutorServiceHandlerRegistryTest.class, 
+        JobPropertiesTest.class,
+        DefaultJobExceptionHandlerTest.class, 
+        SimpleJobExecutorTest.class,
+        WrongJobExecutorTest.class,
+        DataflowJobExecutorTest.class, 
+        ScriptJobExecutorTest.class
+    })
+public final class AllExecutorTests {
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/JobExecutorFactoryTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/JobExecutorFactoryTest.java
new file mode 100644
index 0000000..b869a2a
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/JobExecutorFactoryTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.executor;
+
+import io.elasticjob.cloud.exception.JobConfigurationException;
+import io.elasticjob.cloud.executor.type.DataflowJobExecutor;
+import io.elasticjob.cloud.fixture.config.TestDataflowJobConfiguration;
+import io.elasticjob.cloud.fixture.config.TestScriptJobConfiguration;
+import io.elasticjob.cloud.fixture.config.TestSimpleJobConfiguration;
+import io.elasticjob.cloud.fixture.job.TestDataflowJob;
+import io.elasticjob.cloud.executor.type.ScriptJobExecutor;
+import io.elasticjob.cloud.executor.type.SimpleJobExecutor;
+import io.elasticjob.cloud.fixture.handler.IgnoreJobExceptionHandler;
+import io.elasticjob.cloud.fixture.job.OtherJob;
+import io.elasticjob.cloud.fixture.job.TestSimpleJob;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class JobExecutorFactoryTest {
+    
+    @Mock
+    private JobFacade jobFacade;
+    
+    @Test
+    public void assertGetJobExecutorForScriptJob() {
+        when(jobFacade.loadJobRootConfiguration(true)).thenReturn(new TestScriptJobConfiguration("test.sh", IgnoreJobExceptionHandler.class));
+        assertThat(JobExecutorFactory.getJobExecutor(null, jobFacade), instanceOf(ScriptJobExecutor.class));
+    }
+    
+    @Test
+    public void assertGetJobExecutorForSimpleJob() {
+        when(jobFacade.loadJobRootConfiguration(true)).thenReturn(new TestSimpleJobConfiguration());
+        assertThat(JobExecutorFactory.getJobExecutor(new TestSimpleJob(null), jobFacade), instanceOf(SimpleJobExecutor.class));
+    }
+    
+    @Test
+    public void assertGetJobExecutorForDataflowJob() {
+        when(jobFacade.loadJobRootConfiguration(true)).thenReturn(new TestDataflowJobConfiguration(false));
+        assertThat(JobExecutorFactory.getJobExecutor(new TestDataflowJob(null), jobFacade), instanceOf(DataflowJobExecutor.class));
+    }
+    
+    @Test(expected = JobConfigurationException.class)
+    public void assertGetJobExecutorWhenJobClassWhenUnsupportedJob() {
+        JobExecutorFactory.getJobExecutor(new OtherJob(), jobFacade);
+    }
+    
+    @Test
+    public void assertGetJobExecutorTwice() {
+        when(jobFacade.loadJobRootConfiguration(true)).thenReturn(new TestDataflowJobConfiguration(false));
+        AbstractElasticJobExecutor executor = JobExecutorFactory.getJobExecutor(new TestSimpleJob(null), jobFacade);
+        AbstractElasticJobExecutor anotherExecutor = JobExecutorFactory.getJobExecutor(new TestSimpleJob(null), jobFacade);
+        assertTrue(executor.hashCode() != anotherExecutor.hashCode());
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/handler/ExecutorServiceHandlerRegistryTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/handler/ExecutorServiceHandlerRegistryTest.java
new file mode 100644
index 0000000..37e621e
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/handler/ExecutorServiceHandlerRegistryTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.executor.handler;
+
+import io.elasticjob.cloud.executor.handler.impl.DefaultExecutorServiceHandler;
+import lombok.RequiredArgsConstructor;
+import org.junit.After;
+import org.junit.Test;
+
+import java.util.Set;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertThat;
+
+public final class ExecutorServiceHandlerRegistryTest {
+    
+    @After
+    public void clear() {
+        ExecutorServiceHandlerRegistry.remove("test_job");
+    }
+    
+    @Test
+    public void assertRemove() {
+        ExecutorService actual = ExecutorServiceHandlerRegistry.getExecutorServiceHandler("test_job", new DefaultExecutorServiceHandler());
+        ExecutorServiceHandlerRegistry.remove("test_job");
+        assertThat(actual, not(ExecutorServiceHandlerRegistry.getExecutorServiceHandler("test_job", new DefaultExecutorServiceHandler())));
+    }
+    
+    @Test
+    public void assertGetExecutorServiceHandlerForSameThread() {
+        assertThat(ExecutorServiceHandlerRegistry.getExecutorServiceHandler("test_job", new DefaultExecutorServiceHandler()), 
+                is(ExecutorServiceHandlerRegistry.getExecutorServiceHandler("test_job", new DefaultExecutorServiceHandler())));
+    }
+    
+    @Test
+    public void assertGetExecutorServiceHandlerForConcurrent() throws InterruptedException {
+        int threadCount = 100;
+        CyclicBarrier barrier = new CyclicBarrier(threadCount);
+        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
+        CountDownLatch latch = new CountDownLatch(threadCount);
+        Set<ExecutorService> set = new CopyOnWriteArraySet<>();
+        for (int i = 0; i < threadCount; i++) {
+            executorService.submit(new GetExecutorServiceHandlerTask(barrier, latch, set));
+        }
+        latch.await();
+        assertThat(set.size(), is(1));
+        assertThat(ExecutorServiceHandlerRegistry.getExecutorServiceHandler("test_job", new DefaultExecutorServiceHandler()), is(set.iterator().next()));
+    }
+    
+    @RequiredArgsConstructor
+    class GetExecutorServiceHandlerTask implements Runnable {
+        
+        private final CyclicBarrier barrier;
+        
+        private final CountDownLatch latch;
+        
+        private final Set<ExecutorService> set;
+        
+        @Override
+        public void run() {
+            try {
+                barrier.await();
+            } catch (final InterruptedException | BrokenBarrierException ex) {
+                ex.printStackTrace();
+            }
+            set.add(ExecutorServiceHandlerRegistry.getExecutorServiceHandler("test_job", new DefaultExecutorServiceHandler()));
+            latch.countDown();
+        }
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/handler/JobPropertiesTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/handler/JobPropertiesTest.java
new file mode 100644
index 0000000..2834bcb
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/handler/JobPropertiesTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.executor.handler;
+
+import io.elasticjob.cloud.executor.handler.impl.DefaultExecutorServiceHandler;
+import io.elasticjob.cloud.executor.handler.impl.DefaultJobExceptionHandler;
+import io.elasticjob.cloud.fixture.APIJsonConstants;
+import io.elasticjob.cloud.fixture.handler.IgnoreJobExceptionHandler;
+import org.junit.Test;
+import org.unitils.util.ReflectionUtils;
+
+import java.util.Map;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public final class JobPropertiesTest {
+    
+    @Test
+    public void assertPutInvalidKey() throws NoSuchFieldException {
+        JobProperties actual = new JobProperties();
+        actual.put("invalid_key", "");
+        assertTrue(getMap(actual).isEmpty());
+    }
+    
+    @Test
+    public void assertPutNullValue() throws NoSuchFieldException {
+        JobProperties actual = new JobProperties();
+        actual.put(JobProperties.JobPropertiesEnum.JOB_EXCEPTION_HANDLER.getKey(), null);
+        assertTrue(getMap(actual).isEmpty());
+    }
+    
+    @Test
+    public void assertPutSuccess() throws NoSuchFieldException {
+        JobProperties actual = new JobProperties();
+        actual.put(JobProperties.JobPropertiesEnum.JOB_EXCEPTION_HANDLER.getKey(), DefaultJobExceptionHandler.class.getCanonicalName());
+        assertThat(getMap(actual).size(), is(1));
+    }
+    
+    private Map getMap(final JobProperties jobProperties) throws NoSuchFieldException {
+        return (Map) ReflectionUtils.getFieldValue(jobProperties, JobProperties.class.getDeclaredField("map"));
+    }
+    
+    @Test
+    public void assertGetWhenValueIsEmpty() throws NoSuchFieldException {
+        JobProperties actual = new JobProperties();
+        assertThat(actual.get(JobProperties.JobPropertiesEnum.JOB_EXCEPTION_HANDLER), is(DefaultJobExceptionHandler.class.getCanonicalName()));
+        assertThat(actual.get(JobProperties.JobPropertiesEnum.EXECUTOR_SERVICE_HANDLER), is(DefaultExecutorServiceHandler.class.getCanonicalName()));
+    }
+    
+    @Test
+    public void assertGetWhenValueIsNotEmpty() throws NoSuchFieldException {
+        JobProperties actual = new JobProperties();
+        actual.put(JobProperties.JobPropertiesEnum.JOB_EXCEPTION_HANDLER.getKey(), IgnoreJobExceptionHandler.class.getCanonicalName());
+        assertThat(actual.get(JobProperties.JobPropertiesEnum.JOB_EXCEPTION_HANDLER), is(IgnoreJobExceptionHandler.class.getCanonicalName()));
+    }
+    
+    @Test
+    public void assertJson() {
+        assertThat(new JobProperties().json(), is(APIJsonConstants.getJobPropertiesJson(DefaultJobExceptionHandler.class.getCanonicalName())));
+    }
+    
+    @Test
+    public void assertJobPropertiesEnumFromValidValue() {
+        assertThat(JobProperties.JobPropertiesEnum.from(JobProperties.JobPropertiesEnum.JOB_EXCEPTION_HANDLER.getKey()), is(JobProperties.JobPropertiesEnum.JOB_EXCEPTION_HANDLER));
+    }
+    
+    @Test
+    public void assertJobPropertiesEnumFromInvalidValue() {
+        assertNull(JobProperties.JobPropertiesEnum.from("invalid"));
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/handler/impl/DefaultJobExceptionHandlerTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/handler/impl/DefaultJobExceptionHandlerTest.java
new file mode 100644
index 0000000..fcbb718
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/handler/impl/DefaultJobExceptionHandlerTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.executor.handler.impl;
+
+import io.elasticjob.cloud.event.fixture.JobEventCaller;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import static org.mockito.Mockito.atMost;
+import static org.mockito.Mockito.verify;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class DefaultJobExceptionHandlerTest {
+    
+    @Mock
+    private JobEventCaller caller;
+    
+    @Test
+    public void assertHandleException() {
+        new DefaultJobExceptionHandler().handleException("test_job", new RuntimeException("test"));
+        verify(caller, atMost(1)).call();
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/type/DataflowJobExecutorTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/type/DataflowJobExecutorTest.java
new file mode 100644
index 0000000..29573db
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/type/DataflowJobExecutorTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.executor.type;
+
+import io.elasticjob.cloud.executor.JobFacade;
+import io.elasticjob.cloud.executor.ShardingContexts;
+import io.elasticjob.cloud.fixture.ShardingContextsBuilder;
+import io.elasticjob.cloud.fixture.config.TestDataflowJobConfiguration;
+import io.elasticjob.cloud.fixture.job.JobCaller;
+import io.elasticjob.cloud.fixture.job.TestDataflowJob;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class DataflowJobExecutorTest {
+    
+    @Mock
+    private JobCaller jobCaller;
+    
+    @Mock
+    private JobFacade jobFacade;
+    
+    private ShardingContexts shardingContexts;
+    
+    private DataflowJobExecutor dataflowJobExecutor;
+    
+    @After
+    public void tearDown() throws NoSuchFieldException {
+        verify(jobFacade).loadJobRootConfiguration(true);
+        ElasticJobVerify.verifyForIsNotMisfire(jobFacade, shardingContexts);
+    }
+    
+    @Test
+    public void assertExecuteWhenFetchDataIsNullAndEmpty() {
+        setUp(true, ShardingContextsBuilder.getMultipleShardingContexts());
+        when(jobCaller.fetchData(0)).thenReturn(null);
+        when(jobCaller.fetchData(1)).thenReturn(Collections.emptyList());
+        dataflowJobExecutor.execute();
+        verify(jobCaller).fetchData(0);
+        verify(jobCaller).fetchData(1);
+        verify(jobCaller, times(0)).processData(any());
+    }
+    
+    @Test
+    public void assertExecuteWhenFetchDataIsNotEmptyForUnStreamingProcessAndSingleShardingItem() {
+        setUp(false, ShardingContextsBuilder.getSingleShardingContexts());
+        doThrow(new IllegalStateException()).when(jobCaller).fetchData(0);
+        dataflowJobExecutor.execute();
+        verify(jobCaller).fetchData(0);
+        verify(jobCaller, times(0)).processData(any());
+    }
+    
+    @Test
+    public void assertExecuteWhenFetchDataIsNotEmptyForUnStreamingProcessAndMultipleShardingItems() {
+        setUp(false, ShardingContextsBuilder.getMultipleShardingContexts());
+        when(jobCaller.fetchData(0)).thenReturn(Arrays.<Object>asList(1, 2));
+        when(jobCaller.fetchData(1)).thenReturn(Arrays.<Object>asList(3, 4));
+        doThrow(new IllegalStateException()).when(jobCaller).processData(4);
+        dataflowJobExecutor.execute();
+        verify(jobCaller).fetchData(0);
+        verify(jobCaller).fetchData(1);
+        verify(jobCaller).processData(1);
+        verify(jobCaller).processData(2);
+        verify(jobCaller).processData(3);
+        verify(jobCaller).processData(4);
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Test
+    public void assertExecuteWhenFetchDataIsNotEmptyForStreamingProcessAndSingleShardingItem() {
+        setUp(true, ShardingContextsBuilder.getSingleShardingContexts());
+        when(jobCaller.fetchData(0)).thenReturn(Collections.<Object>singletonList(1), Collections.emptyList());
+        when(jobFacade.isEligibleForJobRunning()).thenReturn(true);
+        dataflowJobExecutor.execute();
+        verify(jobCaller, times(2)).fetchData(0);
+        verify(jobCaller).processData(1);
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Test
+    public void assertExecuteWhenFetchDataIsNotEmptyForStreamingProcessAndMultipleShardingItems() {
+        setUp(true, ShardingContextsBuilder.getMultipleShardingContexts());
+        when(jobCaller.fetchData(0)).thenReturn(Collections.<Object>singletonList(1), Collections.emptyList());
+        when(jobCaller.fetchData(1)).thenReturn(Collections.<Object>singletonList(2), Collections.emptyList());
+        when(jobFacade.isEligibleForJobRunning()).thenReturn(true);
+        dataflowJobExecutor.execute();
+        verify(jobCaller, times(2)).fetchData(0);
+        verify(jobCaller, times(2)).fetchData(1);
+        verify(jobCaller).processData(1);
+        verify(jobCaller).processData(2);
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Test
+    public void assertExecuteWhenFetchDataIsNotEmptyAndProcessFailureWithExceptionForStreamingProcess() {
+        setUp(true, ShardingContextsBuilder.getMultipleShardingContexts());
+        when(jobCaller.fetchData(0)).thenReturn(Collections.<Object>singletonList(1), Collections.emptyList());
+        when(jobCaller.fetchData(1)).thenReturn(Arrays.<Object>asList(2, 3), Collections.emptyList());
+        when(jobFacade.isEligibleForJobRunning()).thenReturn(true);
+        doThrow(new IllegalStateException()).when(jobCaller).processData(2);
+        dataflowJobExecutor.execute();
+        verify(jobCaller, times(2)).fetchData(0);
+        verify(jobCaller, times(1)).fetchData(1);
+        verify(jobCaller).processData(1);
+        verify(jobCaller).processData(2);
+        verify(jobCaller, times(0)).processData(3);
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Test
+    public void assertExecuteWhenFetchDataIsNotEmptyAndIsEligibleForJobRunningForStreamingProcess() {
+        setUp(true, ShardingContextsBuilder.getMultipleShardingContexts());
+        when(jobFacade.isEligibleForJobRunning()).thenReturn(true);
+        when(jobCaller.fetchData(0)).thenReturn(Arrays.<Object>asList(1, 2), Collections.emptyList());
+        when(jobCaller.fetchData(1)).thenReturn(Arrays.<Object>asList(3, 4), Collections.emptyList());
+        doThrow(new IllegalStateException()).when(jobCaller).processData(4);
+        dataflowJobExecutor.execute();
+        verify(jobCaller, times(2)).fetchData(0);
+        verify(jobCaller, times(1)).fetchData(1);
+        verify(jobCaller).processData(1);
+        verify(jobCaller).processData(2);
+        verify(jobCaller).processData(3);
+        verify(jobCaller).processData(4);
+    }
+    
+    @Test
+    public void assertExecuteWhenFetchDataIsNotEmptyAndIsNotEligibleForJobRunningForStreamingProcess() {
+        setUp(true, ShardingContextsBuilder.getMultipleShardingContexts());
+        when(jobFacade.isEligibleForJobRunning()).thenReturn(false);
+        when(jobCaller.fetchData(0)).thenReturn(Arrays.<Object>asList(1, 2));
+        when(jobCaller.fetchData(1)).thenReturn(Arrays.<Object>asList(3, 4));
+        doThrow(new IllegalStateException()).when(jobCaller).processData(4);
+        dataflowJobExecutor.execute();
+        verify(jobCaller).fetchData(0);
+        verify(jobCaller).fetchData(1);
+        verify(jobCaller).processData(1);
+        verify(jobCaller).processData(2);
+        verify(jobCaller).processData(3);
+        verify(jobCaller).processData(4);
+    }
+    
+    private void setUp(final boolean isStreamingProcess, final ShardingContexts shardingContexts) {
+        this.shardingContexts = shardingContexts;
+        when(jobFacade.loadJobRootConfiguration(true)).thenReturn(new TestDataflowJobConfiguration(isStreamingProcess));
+        when(jobFacade.getShardingContexts()).thenReturn(shardingContexts);
+        dataflowJobExecutor = new DataflowJobExecutor(new TestDataflowJob(jobCaller), jobFacade);
+        ElasticJobVerify.prepareForIsNotMisfire(jobFacade, shardingContexts);
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/type/ElasticJobVerify.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/type/ElasticJobVerify.java
new file mode 100644
index 0000000..16b4157
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/type/ElasticJobVerify.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.executor.type;
+
+import io.elasticjob.cloud.executor.ShardingContexts;
+import io.elasticjob.cloud.event.type.JobStatusTraceEvent;
+import io.elasticjob.cloud.exception.JobExecutionEnvironmentException;
+import io.elasticjob.cloud.executor.JobFacade;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+final class ElasticJobVerify {
+    
+    public static void prepareForIsNotMisfire(final JobFacade jobFacade, final ShardingContexts shardingContexts) {
+        when(jobFacade.getShardingContexts()).thenReturn(shardingContexts);
+        when(jobFacade.misfireIfRunning(shardingContexts.getShardingItemParameters().keySet())).thenReturn(false);
+        when(jobFacade.isExecuteMisfired(shardingContexts.getShardingItemParameters().keySet())).thenReturn(false);
+    }
+    
+    public static void verifyForIsNotMisfire(final JobFacade jobFacade, final ShardingContexts shardingContexts) {
+        try {
+            verify(jobFacade).checkJobExecutionEnvironment();
+        } catch (final JobExecutionEnvironmentException ex) {
+            throw new RuntimeException(ex);
+        }
+        verify(jobFacade).getShardingContexts();
+        verify(jobFacade).postJobStatusTraceEvent(shardingContexts.getTaskId(), JobStatusTraceEvent.State.TASK_STAGING, "Job 'test_job' execute begin.");
+        verify(jobFacade).misfireIfRunning(shardingContexts.getShardingItemParameters().keySet());
+        verify(jobFacade).beforeJobExecuted(shardingContexts);
+        verify(jobFacade).registerJobBegin(shardingContexts);
+        verify(jobFacade).registerJobCompleted(shardingContexts);
+        verify(jobFacade).isExecuteMisfired(shardingContexts.getShardingItemParameters().keySet());
+        verify(jobFacade).afterJobExecuted(shardingContexts);
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/type/ScriptJobExecutorTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/type/ScriptJobExecutorTest.java
new file mode 100644
index 0000000..7f2403b
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/type/ScriptJobExecutorTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.executor.type;
+
+import io.elasticjob.cloud.executor.ShardingContexts;
+import io.elasticjob.cloud.fixture.config.TestScriptJobConfiguration;
+import io.elasticjob.cloud.exception.JobSystemException;
+import io.elasticjob.cloud.executor.JobFacade;
+import io.elasticjob.cloud.fixture.ShardingContextsBuilder;
+import io.elasticjob.cloud.fixture.handler.IgnoreJobExceptionHandler;
+import io.elasticjob.cloud.fixture.handler.ThrowJobExceptionHandler;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.io.IOException;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class ScriptJobExecutorTest {
+    
+    @Mock
+    private JobFacade jobFacade;
+    
+    private ScriptJobExecutor scriptJobExecutor;
+    
+    @Test
+    public void assertExecuteWhenCommandLineIsEmpty() throws IOException {
+        ElasticJobVerify.prepareForIsNotMisfire(jobFacade, ShardingContextsBuilder.getMultipleShardingContexts());
+        when(jobFacade.loadJobRootConfiguration(true)).thenReturn(new TestScriptJobConfiguration("", IgnoreJobExceptionHandler.class));
+        scriptJobExecutor = new ScriptJobExecutor(jobFacade);
+        scriptJobExecutor.execute();
+    }
+    
+    @Test(expected = JobSystemException.class)
+    public void assertExecuteWhenExecuteFailureForSingleShardingItems() throws IOException, NoSuchFieldException {
+        assertExecuteWhenExecuteFailure(ShardingContextsBuilder.getSingleShardingContexts());
+    }
+    
+    @Test
+    public void assertExecuteWhenExecuteFailureForMultipleShardingItems() throws IOException, NoSuchFieldException {
+        assertExecuteWhenExecuteFailure(ShardingContextsBuilder.getMultipleShardingContexts());
+    }
+    
+    private void assertExecuteWhenExecuteFailure(final ShardingContexts shardingContexts) {
+        ElasticJobVerify.prepareForIsNotMisfire(jobFacade, shardingContexts);
+        when(jobFacade.loadJobRootConfiguration(true)).thenReturn(new TestScriptJobConfiguration("not_exists_file", ThrowJobExceptionHandler.class));
+        scriptJobExecutor = new ScriptJobExecutor(jobFacade);
+        scriptJobExecutor.execute();
+    }
+    
+    @Test
+    public void assertExecuteSuccessForMultipleShardingItems() throws IOException, NoSuchFieldException {
+        assertExecuteSuccess(ShardingContextsBuilder.getMultipleShardingContexts());
+    }
+    
+    @Test
+    public void assertExecuteSuccessForSingleShardingItems() throws IOException, NoSuchFieldException {
+        assertExecuteSuccess(ShardingContextsBuilder.getSingleShardingContexts());
+    }
+    
+    private void assertExecuteSuccess(final ShardingContexts shardingContexts) throws IOException, NoSuchFieldException {
+        ElasticJobVerify.prepareForIsNotMisfire(jobFacade, shardingContexts);
+        when(jobFacade.loadJobRootConfiguration(true)).thenReturn(new TestScriptJobConfiguration("exists_file param0 param1", IgnoreJobExceptionHandler.class));
+        scriptJobExecutor = new ScriptJobExecutor(jobFacade);
+        scriptJobExecutor.execute();
+        verify(jobFacade).loadJobRootConfiguration(true);
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/type/SimpleJobExecutorTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/type/SimpleJobExecutorTest.java
new file mode 100644
index 0000000..13d2b78
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/type/SimpleJobExecutorTest.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.executor.type;
+
+import io.elasticjob.cloud.executor.AbstractElasticJobExecutor;
+import io.elasticjob.cloud.executor.ShardingContexts;
+import io.elasticjob.cloud.fixture.config.TestSimpleJobConfiguration;
+import io.elasticjob.cloud.event.type.JobStatusTraceEvent;
+import io.elasticjob.cloud.exception.JobExecutionEnvironmentException;
+import io.elasticjob.cloud.exception.JobSystemException;
+import io.elasticjob.cloud.executor.JobFacade;
+import io.elasticjob.cloud.executor.handler.impl.DefaultExecutorServiceHandler;
+import io.elasticjob.cloud.executor.handler.impl.DefaultJobExceptionHandler;
+import io.elasticjob.cloud.fixture.ShardingContextsBuilder;
+import io.elasticjob.cloud.fixture.job.JobCaller;
+import io.elasticjob.cloud.fixture.job.TestSimpleJob;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.unitils.util.ReflectionUtils;
+
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class SimpleJobExecutorTest {
+    
+    @Mock
+    private JobCaller jobCaller;
+    
+    @Mock
+    private JobFacade jobFacade;
+    
+    private SimpleJobExecutor simpleJobExecutor;
+    
+    @Before
+    public void setUp() throws NoSuchFieldException {
+        when(jobFacade.loadJobRootConfiguration(true)).thenReturn(new TestSimpleJobConfiguration());
+        simpleJobExecutor = new SimpleJobExecutor(new TestSimpleJob(jobCaller), jobFacade);
+    }
+    
+    @Test
+    public void assertNewExecutorWithDefaultHandlers() throws NoSuchFieldException {
+        when(jobFacade.loadJobRootConfiguration(true)).thenReturn(new TestSimpleJobConfiguration("ErrorHandler", Object.class.getName()));
+        SimpleJobExecutor simpleJobExecutor = new SimpleJobExecutor(new TestSimpleJob(jobCaller), jobFacade);
+        assertThat(ReflectionUtils.getFieldValue(simpleJobExecutor, AbstractElasticJobExecutor.class.getDeclaredField("executorService")), 
+                instanceOf(new DefaultExecutorServiceHandler().createExecutorService("test_job").getClass()));
+        assertThat(ReflectionUtils.getFieldValue(simpleJobExecutor, AbstractElasticJobExecutor.class.getDeclaredField("jobExceptionHandler")),
+                instanceOf(DefaultJobExceptionHandler.class));
+    }
+    
+    @Test(expected = JobSystemException.class)
+    public void assertExecuteWhenCheckMaxTimeDiffSecondsIntolerable() throws JobExecutionEnvironmentException {
+        doThrow(JobExecutionEnvironmentException.class).when(jobFacade).checkJobExecutionEnvironment();
+        try {
+            simpleJobExecutor.execute();
+        } finally {
+            verify(jobFacade).checkJobExecutionEnvironment();
+            verify(jobCaller, times(0)).execute();
+        }
+    }
+    
+    @Test
+    public void assertExecuteWhenPreviousJobStillRunning() throws JobExecutionEnvironmentException {
+        ShardingContexts shardingContexts = new ShardingContexts("fake_task_id", "test_job", 10, "", Collections.<Integer, String>emptyMap());
+        when(jobFacade.getShardingContexts()).thenReturn(shardingContexts);
+        when(jobFacade.misfireIfRunning(shardingContexts.getShardingItemParameters().keySet())).thenReturn(true);
+        simpleJobExecutor.execute();
+        verify(jobFacade).postJobStatusTraceEvent(shardingContexts.getTaskId(), JobStatusTraceEvent.State.TASK_STAGING, "Job 'test_job' execute begin.");
+        verify(jobFacade).postJobStatusTraceEvent(shardingContexts.getTaskId(), JobStatusTraceEvent.State.TASK_FINISHED, 
+                "Previous job 'test_job' - shardingItems '[]' is still running, misfired job will start after previous job completed.");
+        verify(jobFacade).checkJobExecutionEnvironment();
+        verify(jobFacade).getShardingContexts();
+        verify(jobFacade).misfireIfRunning(shardingContexts.getShardingItemParameters().keySet());
+        verify(jobCaller, times(0)).execute();
+    }
+    
+    @Test
+    public void assertExecuteWhenShardingItemsIsEmpty() throws JobExecutionEnvironmentException {
+        ShardingContexts shardingContexts = new ShardingContexts("fake_task_id", "test_job", 10, "", Collections.<Integer, String>emptyMap());
+        ElasticJobVerify.prepareForIsNotMisfire(jobFacade, shardingContexts);
+        simpleJobExecutor.execute();
+        verify(jobFacade).postJobStatusTraceEvent(shardingContexts.getTaskId(), JobStatusTraceEvent.State.TASK_STAGING, "Job 'test_job' execute begin.");
+        verify(jobFacade).postJobStatusTraceEvent(shardingContexts.getTaskId(), JobStatusTraceEvent.State.TASK_FINISHED, "Sharding item for job 'test_job' is empty.");
+        verify(jobFacade).checkJobExecutionEnvironment();
+        verify(jobFacade).getShardingContexts();
+        verify(jobFacade).misfireIfRunning(shardingContexts.getShardingItemParameters().keySet());
+        verify(jobCaller, times(0)).execute();
+    }
+    
+    @Test(expected = JobSystemException.class)
+    public void assertExecuteWhenRunOnceAndThrowExceptionForSingleShardingItem() throws JobExecutionEnvironmentException {
+        assertExecuteWhenRunOnceAndThrowException(ShardingContextsBuilder.getSingleShardingContexts());
+    }
+    
+    @Test
+    public void assertExecuteWhenRunOnceAndThrowExceptionForMultipleShardingItems() throws JobExecutionEnvironmentException {
+        assertExecuteWhenRunOnceAndThrowException(ShardingContextsBuilder.getMultipleShardingContexts());
+    }
+    
+    private void assertExecuteWhenRunOnceAndThrowException(final ShardingContexts shardingContexts) throws JobExecutionEnvironmentException {
+        ElasticJobVerify.prepareForIsNotMisfire(jobFacade, shardingContexts);
+        doThrow(RuntimeException.class).when(jobCaller).execute();
+        try {
+            simpleJobExecutor.execute();
+        } finally {
+            verify(jobFacade).postJobStatusTraceEvent(shardingContexts.getTaskId(), JobStatusTraceEvent.State.TASK_STAGING, "Job 'test_job' execute begin.");
+            verify(jobFacade).postJobStatusTraceEvent(shardingContexts.getTaskId(), JobStatusTraceEvent.State.TASK_RUNNING, "");
+            String errorMessage;
+            String lineSeparator = System.getProperty("line.separator");
+            if (1 == shardingContexts.getShardingItemParameters().size()) {
+                errorMessage = "{0=java.lang.RuntimeException" + lineSeparator + "}";
+            } else {
+                errorMessage = "{0=java.lang.RuntimeException" + lineSeparator + ", 1=java.lang.RuntimeException" + lineSeparator + "}";
+            }
+            verify(jobFacade).postJobStatusTraceEvent(shardingContexts.getTaskId(), JobStatusTraceEvent.State.TASK_ERROR, errorMessage);
+            verify(jobFacade).checkJobExecutionEnvironment();
+            verify(jobFacade).getShardingContexts();
+            verify(jobFacade).misfireIfRunning(shardingContexts.getShardingItemParameters().keySet());
+            verify(jobFacade).registerJobBegin(shardingContexts);
+            verify(jobCaller, times(shardingContexts.getShardingTotalCount())).execute();
+            verify(jobFacade).registerJobCompleted(shardingContexts);
+        }
+    }
+    
+    @Test
+    public void assertExecuteWhenRunOnceSuccessForSingleShardingItems() {
+        assertExecuteWhenRunOnceSuccess(ShardingContextsBuilder.getSingleShardingContexts());
+    }
+    
+    @Test
+    public void assertExecuteWhenRunOnceSuccessForMultipleShardingItems() {
+        assertExecuteWhenRunOnceSuccess(ShardingContextsBuilder.getMultipleShardingContexts());
+    }
+    
+    private void assertExecuteWhenRunOnceSuccess(final ShardingContexts shardingContexts) {
+        ElasticJobVerify.prepareForIsNotMisfire(jobFacade, shardingContexts);
+        simpleJobExecutor.execute();
+        verify(jobFacade).postJobStatusTraceEvent(shardingContexts.getTaskId(), JobStatusTraceEvent.State.TASK_STAGING, "Job 'test_job' execute begin.");
+        verify(jobFacade).postJobStatusTraceEvent(shardingContexts.getTaskId(), JobStatusTraceEvent.State.TASK_FINISHED, "");
+        ElasticJobVerify.verifyForIsNotMisfire(jobFacade, shardingContexts);
+        verify(jobCaller, times(shardingContexts.getShardingTotalCount())).execute();
+    }
+    
+    @Test
+    public void assertExecuteWhenRunOnceWithMisfireIsEmpty() {
+        ShardingContexts shardingContexts = ShardingContextsBuilder.getMultipleShardingContexts();
+        when(jobFacade.getShardingContexts()).thenReturn(shardingContexts);
+        when(jobFacade.isExecuteMisfired(shardingContexts.getShardingItemParameters().keySet())).thenReturn(false);
+        simpleJobExecutor.execute();
+        ElasticJobVerify.verifyForIsNotMisfire(jobFacade, shardingContexts);
+        verify(jobCaller, times(2)).execute();
+    }
+    
+    @Test
+    public void assertExecuteWhenRunOnceWithMisfireIsNotEmptyButIsNotEligibleForJobRunning() {
+        ShardingContexts shardingContexts = ShardingContextsBuilder.getMultipleShardingContexts();
+        when(jobFacade.getShardingContexts()).thenReturn(shardingContexts);
+        when(jobFacade.isExecuteMisfired(shardingContexts.getShardingItemParameters().keySet())).thenReturn(false);
+        simpleJobExecutor.execute();
+        ElasticJobVerify.verifyForIsNotMisfire(jobFacade, shardingContexts);
+        verify(jobCaller, times(2)).execute();
+        verify(jobFacade, times(0)).clearMisfire(shardingContexts.getShardingItemParameters().keySet());
+    }
+    
+    @Test
+    public void assertExecuteWhenRunOnceWithMisfire() throws JobExecutionEnvironmentException {
+        ShardingContexts shardingContexts = ShardingContextsBuilder.getMultipleShardingContexts();
+        when(jobFacade.getShardingContexts()).thenReturn(shardingContexts);
+        when(jobFacade.misfireIfRunning(shardingContexts.getShardingItemParameters().keySet())).thenReturn(false);
+        when(jobFacade.isExecuteMisfired(shardingContexts.getShardingItemParameters().keySet())).thenReturn(true, false);
+        simpleJobExecutor.execute();
+        verify(jobFacade).postJobStatusTraceEvent(shardingContexts.getTaskId(), JobStatusTraceEvent.State.TASK_STAGING, "Job 'test_job' execute begin.");
+        verify(jobFacade, times(2)).postJobStatusTraceEvent(shardingContexts.getTaskId(), JobStatusTraceEvent.State.TASK_RUNNING, "");
+        verify(jobFacade).checkJobExecutionEnvironment();
+        verify(jobFacade).getShardingContexts();
+        verify(jobFacade).misfireIfRunning(shardingContexts.getShardingItemParameters().keySet());
+        verify(jobFacade, times(2)).registerJobBegin(shardingContexts);
+        verify(jobCaller, times(4)).execute();
+        verify(jobFacade, times(2)).registerJobCompleted(shardingContexts);
+    }
+    
+    @Test(expected = JobSystemException.class)
+    public void assertBeforeJobExecutedFailure() {
+        ShardingContexts shardingContexts = ShardingContextsBuilder.getMultipleShardingContexts();
+        when(jobFacade.getShardingContexts()).thenReturn(shardingContexts);
+        when(jobFacade.misfireIfRunning(shardingContexts.getShardingItemParameters().keySet())).thenReturn(false);
+        doThrow(RuntimeException.class).when(jobFacade).beforeJobExecuted(shardingContexts);
+        try {
+            simpleJobExecutor.execute();
+        } finally {
+            verify(jobCaller, times(0)).execute();
+        }
+    }
+    
+    @Test(expected = JobSystemException.class)
+    public void assertAfterJobExecutedFailure() {
+        ShardingContexts shardingContexts = ShardingContextsBuilder.getMultipleShardingContexts();
+        when(jobFacade.getShardingContexts()).thenReturn(shardingContexts);
+        when(jobFacade.misfireIfRunning(shardingContexts.getShardingItemParameters().keySet())).thenReturn(false);
+        when(jobFacade.isExecuteMisfired(shardingContexts.getShardingItemParameters().keySet())).thenReturn(false);
+        doThrow(RuntimeException.class).when(jobFacade).afterJobExecuted(shardingContexts);
+        try {
+            simpleJobExecutor.execute();
+        } finally {
+            verify(jobCaller, times(2)).execute();
+        }
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/type/WrongJobExecutorTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/type/WrongJobExecutorTest.java
new file mode 100644
index 0000000..52d4889
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/executor/type/WrongJobExecutorTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.executor.type;
+
+import io.elasticjob.cloud.event.type.JobStatusTraceEvent;
+import io.elasticjob.cloud.executor.JobFacade;
+import io.elasticjob.cloud.executor.ShardingContexts;
+import io.elasticjob.cloud.fixture.config.TestSimpleJobConfiguration;
+import io.elasticjob.cloud.fixture.job.TestWrongJob;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class WrongJobExecutorTest {
+    
+    @Mock
+    private JobFacade jobFacade;
+    
+    private SimpleJobExecutor wrongSimpleJobExecutor;
+    
+    @Before
+    public void setUp() throws NoSuchFieldException {
+        when(jobFacade.loadJobRootConfiguration(true)).thenReturn(new TestSimpleJobConfiguration());
+        wrongSimpleJobExecutor = new SimpleJobExecutor(new TestWrongJob(), jobFacade);
+    }
+    
+    @Test(expected = RuntimeException.class)
+    public void assertWrongJobExecutorWithSingleItem() throws NoSuchFieldException {
+        Map<Integer, String> map = new HashMap<>(1, 1);
+        map.put(0, "A");
+        ShardingContexts shardingContexts = new ShardingContexts("fake_task_id", "test_job", 10, "", map);
+        when(jobFacade.getShardingContexts()).thenReturn(shardingContexts);
+        wrongSimpleJobExecutor.execute();
+    }
+    
+    @Test
+    public void assertWrongJobExecutorWithMultipleItems() throws NoSuchFieldException {
+        Map<Integer, String> map = new HashMap<>(1, 1);
+        map.put(0, "A");
+        map.put(1, "B");
+        ShardingContexts shardingContexts = new ShardingContexts("fake_task_id", "test_job", 10, "", map);
+        when(jobFacade.getShardingContexts()).thenReturn(shardingContexts);
+        wrongSimpleJobExecutor.execute();
+        verify(jobFacade).getShardingContexts();
+        verify(jobFacade).postJobStatusTraceEvent("fake_task_id", JobStatusTraceEvent.State.TASK_RUNNING, "");
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/APIJsonConstants.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/APIJsonConstants.java
new file mode 100644
index 0000000..91ae423
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/APIJsonConstants.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.fixture;
+
+import io.elasticjob.cloud.executor.handler.impl.DefaultExecutorServiceHandler;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class APIJsonConstants {
+    
+    private static final String JOB_PROPS_JSON = "{\"job_exception_handler\":\"%s\",\"executor_service_handler\":\"" + DefaultExecutorServiceHandler.class.getCanonicalName() + "\"}";
+    
+    // CHECKSTYLE:OFF
+    private static final String SIMPLE_JOB_JSON =  "{\"jobName\":\"test_job\",\"jobClass\":\"io.elasticjob.cloud.fixture.job.TestSimpleJob\",\"jobType\":\"SIMPLE\","
+            + "\"cron\":\"0/1 * * * * ?\",\"shardingTotalCount\":3,\"shardingItemParameters\":\"0\\u003dA,1\\u003dB,2\\u003dC\",\"jobParameter\":\"param\",\"failover\":true,\"misfire\":false,"
+            + "\"description\":\"desc\",\"jobProperties\":%s}";
+    // CHECKSTYLE:ON
+    
+    private static final String DATAFLOW_JOB_JSON = "{\"jobName\":\"test_job\",\"jobClass\":\"io.elasticjob.cloud.fixture.job.TestDataflowJob\",\"jobType\":\"DATAFLOW\","
+            + "\"cron\":\"0/1 * * * * ?\",\"shardingTotalCount\":3,\"shardingItemParameters\":\"\",\"jobParameter\":\"\",\"failover\":false,\"misfire\":true,\"description\":\"\","
+            + "\"jobProperties\":%s,\"streamingProcess\":true}";
+    
+    private static final String SCRIPT_JOB_JSON = "{\"jobName\":\"test_job\",\"jobClass\":\"io.elasticjob.cloud.api.script.ScriptJob\",\"jobType\":\"SCRIPT\",\"cron\":\"0/1 * * * * ?\","
+            + "\"shardingTotalCount\":3,\"shardingItemParameters\":\"\",\"jobParameter\":\"\",\"failover\":false,\"misfire\":true,\"description\":\"\","
+            + "\"jobProperties\":%s,\"scriptCommandLine\":\"test.sh\"}";
+    
+    public static String getJobPropertiesJson(final String jobExceptionHandler) {
+        return String.format(JOB_PROPS_JSON, jobExceptionHandler);
+    }
+    
+    public static String getSimpleJobJson(final String jobExceptionHandler) {
+        return String.format(SIMPLE_JOB_JSON, getJobPropertiesJson(jobExceptionHandler));
+    }
+    
+    public static String getDataflowJobJson(final String jobExceptionHandler) {
+        return String.format(DATAFLOW_JOB_JSON, getJobPropertiesJson(jobExceptionHandler));
+    }
+    
+    public static String getScriptJobJson(final String jobExceptionHandler) {
+        return String.format(SCRIPT_JOB_JSON, getJobPropertiesJson(jobExceptionHandler));
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/EmbedTestingServer.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/EmbedTestingServer.java
new file mode 100644
index 0000000..646f109
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/EmbedTestingServer.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.fixture;
+
+import com.google.common.base.Joiner;
+import io.elasticjob.cloud.reg.exception.RegExceptionHandler;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.apache.curator.test.TestingServer;
+
+import java.io.File;
+import java.io.IOException;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class EmbedTestingServer {
+    
+    private static final int PORT = 3181;
+    
+    private static volatile TestingServer testingServer;
+    
+    public static String getConnectionString() {
+        return Joiner.on(":").join("localhost", PORT);
+    }
+    
+    public static void start() {
+        if (null != testingServer) {
+            return;
+        }
+        try {
+            testingServer = new TestingServer(PORT, new File(String.format("target/test_zk_data/%s/", System.nanoTime())));
+            // CHECKSTYLE:OFF
+        } catch (final Exception ex) {
+            // CHECKSTYLE:ON
+            RegExceptionHandler.handleException(ex);
+        } finally {
+            Runtime.getRuntime().addShutdownHook(new Thread() {
+                
+                @Override
+                public void run() {
+                    try {
+                        testingServer.close();
+                    } catch (final IOException ex) {
+                        RegExceptionHandler.handleException(ex);
+                    }
+                }
+            });
+        }
+    }
+}
+
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/ShardingContextsBuilder.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/ShardingContextsBuilder.java
new file mode 100644
index 0000000..dad7100
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/ShardingContextsBuilder.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.fixture;
+
+import io.elasticjob.cloud.executor.ShardingContexts;
+import lombok.AccessLevel;
+import lombok.RequiredArgsConstructor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+public final class ShardingContextsBuilder {
+    
+    public static final String JOB_NAME = "test_job";
+    
+    public static ShardingContexts getSingleShardingContexts() {
+        Map<Integer, String> map = new HashMap<>(1, 1);
+        map.put(0, "A");
+        return new ShardingContexts("fake_task_id", JOB_NAME, 1, "", map);
+    }
+    
+    public static ShardingContexts getMultipleShardingContexts() {
+        Map<Integer, String> map = new HashMap<>(2, 1);
+        map.put(0, "A");
+        map.put(1, "B");
+        return new ShardingContexts("fake_task_id", JOB_NAME, 2, "", map);
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/config/TestDataflowJobConfiguration.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/config/TestDataflowJobConfiguration.java
new file mode 100644
index 0000000..2d85455
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/config/TestDataflowJobConfiguration.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.fixture.config;
+
+import io.elasticjob.cloud.config.JobRootConfiguration;
+import io.elasticjob.cloud.config.JobTypeConfiguration;
+import io.elasticjob.cloud.fixture.job.TestDataflowJob;
+import io.elasticjob.cloud.config.JobCoreConfiguration;
+import io.elasticjob.cloud.config.dataflow.DataflowJobConfiguration;
+import io.elasticjob.cloud.executor.handler.JobProperties;
+import io.elasticjob.cloud.fixture.ShardingContextsBuilder;
+import io.elasticjob.cloud.fixture.handler.IgnoreJobExceptionHandler;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public final class TestDataflowJobConfiguration implements JobRootConfiguration {
+    
+    private final boolean streamingProcess;
+    
+    @Override
+    public JobTypeConfiguration getTypeConfig() {
+        return new DataflowJobConfiguration(JobCoreConfiguration.newBuilder(ShardingContextsBuilder.JOB_NAME, "0/1 * * * * ?", 3)
+                .jobProperties(JobProperties.JobPropertiesEnum.JOB_EXCEPTION_HANDLER.getKey(), IgnoreJobExceptionHandler.class.getCanonicalName()).build(), 
+                TestDataflowJob.class.getCanonicalName(), streamingProcess);
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/config/TestJobRootConfiguration.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/config/TestJobRootConfiguration.java
new file mode 100644
index 0000000..1a18881
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/config/TestJobRootConfiguration.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.fixture.config;
+
+import io.elasticjob.cloud.config.JobTypeConfiguration;
+import io.elasticjob.cloud.config.JobRootConfiguration;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public final class TestJobRootConfiguration implements JobRootConfiguration {
+    
+    private final JobTypeConfiguration typeConfig;
+    
+    @Override
+    public JobTypeConfiguration getTypeConfig() {
+        return typeConfig;
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/config/TestScriptJobConfiguration.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/config/TestScriptJobConfiguration.java
new file mode 100644
index 0000000..1b324a2
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/config/TestScriptJobConfiguration.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.fixture.config;
+
+import io.elasticjob.cloud.config.JobTypeConfiguration;
+import io.elasticjob.cloud.config.script.ScriptJobConfiguration;
+import io.elasticjob.cloud.executor.handler.JobExceptionHandler;
+import io.elasticjob.cloud.config.JobCoreConfiguration;
+import io.elasticjob.cloud.config.JobRootConfiguration;
+import io.elasticjob.cloud.executor.handler.JobProperties;
+import io.elasticjob.cloud.fixture.ShardingContextsBuilder;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public final class TestScriptJobConfiguration implements JobRootConfiguration {
+    
+    private final String scriptCommandLine;
+    
+    private final Class<? extends JobExceptionHandler> jobExceptionHandlerClass;
+    
+    @Override
+    public JobTypeConfiguration getTypeConfig() {
+        return new ScriptJobConfiguration(JobCoreConfiguration.newBuilder(ShardingContextsBuilder.JOB_NAME, "0/1 * * * * ?", 3)
+                .jobProperties(JobProperties.JobPropertiesEnum.JOB_EXCEPTION_HANDLER.getKey(), jobExceptionHandlerClass.getCanonicalName()).build(), scriptCommandLine);
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/config/TestSimpleJobConfiguration.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/config/TestSimpleJobConfiguration.java
new file mode 100644
index 0000000..a9878f5
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/config/TestSimpleJobConfiguration.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.fixture.config;
+
+import io.elasticjob.cloud.config.JobCoreConfiguration;
+import io.elasticjob.cloud.config.JobRootConfiguration;
+import io.elasticjob.cloud.config.JobTypeConfiguration;
+import io.elasticjob.cloud.config.simple.SimpleJobConfiguration;
+import io.elasticjob.cloud.executor.handler.JobProperties;
+import io.elasticjob.cloud.fixture.ShardingContextsBuilder;
+import io.elasticjob.cloud.fixture.handler.ThrowJobExceptionHandler;
+import io.elasticjob.cloud.fixture.job.TestSimpleJob;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor
+public final class TestSimpleJobConfiguration implements JobRootConfiguration {
+    
+    private String jobExceptionHandlerClassName;
+    
+    private String executorServiceHandlerClassName;
+    
+    public TestSimpleJobConfiguration(final String jobExceptionHandlerClassName, final String executorServiceHandlerClassName) {
+        this.jobExceptionHandlerClassName = jobExceptionHandlerClassName;
+        this.executorServiceHandlerClassName = executorServiceHandlerClassName;
+    }
+    
+    @Override
+    public JobTypeConfiguration getTypeConfig() {
+        JobCoreConfiguration.Builder builder = JobCoreConfiguration.newBuilder(ShardingContextsBuilder.JOB_NAME, "0/1 * * * * ?", 3)
+                .shardingItemParameters("0=A,1=B,2=C").jobParameter("param").failover(true).misfire(false).description("desc");
+        if (null == jobExceptionHandlerClassName) {
+            builder.jobProperties(JobProperties.JobPropertiesEnum.JOB_EXCEPTION_HANDLER.getKey(), ThrowJobExceptionHandler.class.getCanonicalName());
+        } else {
+            builder.jobProperties(JobProperties.JobPropertiesEnum.JOB_EXCEPTION_HANDLER.getKey(), jobExceptionHandlerClassName);
+        }
+        if (null != executorServiceHandlerClassName) {
+            builder.jobProperties(JobProperties.JobPropertiesEnum.EXECUTOR_SERVICE_HANDLER.getKey(), executorServiceHandlerClassName);
+        }
+        return new SimpleJobConfiguration(builder.build(), TestSimpleJob.class.getCanonicalName());
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/context/TaskNode.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/context/TaskNode.java
new file mode 100644
index 0000000..820b5ee
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/context/TaskNode.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.fixture.context;
+
+import com.google.common.base.Joiner;
+import io.elasticjob.cloud.context.ExecutionType;
+import lombok.Builder;
+
+@Builder
+public final class TaskNode {
+    
+    private String jobName;
+    
+    private int shardingItem;
+    
+    private ExecutionType type;
+    
+    private String slaveId;
+    
+    private String uuid;
+    
+    public String getTaskNodePath() {
+        return Joiner.on("@-@").join(null == jobName ? "test_job" : jobName, shardingItem);
+    }
+    
+    public String getTaskNodeValue() {
+        return Joiner.on("@-@").join(getTaskNodePath(), null == type ? ExecutionType.READY : type, null == slaveId ? "slave-S0" : slaveId, null == uuid ? "0" : uuid);
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/handler/IgnoreJobExceptionHandler.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/handler/IgnoreJobExceptionHandler.java
new file mode 100644
index 0000000..224d00e
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/handler/IgnoreJobExceptionHandler.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.fixture.handler;
+
+import io.elasticjob.cloud.executor.handler.JobExceptionHandler;
+
+public final class IgnoreJobExceptionHandler implements JobExceptionHandler {
+    
+    @Override
+    public void handleException(final String jobName, final Throwable cause) {
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/handler/ThrowJobExceptionHandler.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/handler/ThrowJobExceptionHandler.java
new file mode 100644
index 0000000..6bae5c1
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/handler/ThrowJobExceptionHandler.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.fixture.handler;
+
+import io.elasticjob.cloud.executor.handler.JobExceptionHandler;
+import io.elasticjob.cloud.exception.JobSystemException;
+
+public final class ThrowJobExceptionHandler implements JobExceptionHandler {
+    
+    @Override
+    public void handleException(final String jobName, final Throwable cause) {
+        throw new JobSystemException(cause);
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/job/JobCaller.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/job/JobCaller.java
new file mode 100644
index 0000000..217a8a6
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/job/JobCaller.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.fixture.job;
+
+import java.util.List;
+
+public interface JobCaller {
+    
+    void execute();
+    
+    List<Object> fetchData(int shardingItem);
+    
+    void processData(Object data);
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/job/OtherJob.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/job/OtherJob.java
new file mode 100644
index 0000000..b59ceae
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/job/OtherJob.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.fixture.job;
+
+import io.elasticjob.cloud.api.ElasticJob;
+
+public final class OtherJob implements ElasticJob {
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/job/TestDataflowJob.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/job/TestDataflowJob.java
new file mode 100644
index 0000000..8e8d97f
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/job/TestDataflowJob.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.fixture.job;
+
+import io.elasticjob.cloud.api.ShardingContext;
+import io.elasticjob.cloud.api.dataflow.DataflowJob;
+import lombok.RequiredArgsConstructor;
+
+import java.util.List;
+
+@RequiredArgsConstructor
+public final class TestDataflowJob implements DataflowJob<Object> {
+    
+    private final JobCaller jobCaller;
+    
+    @Override
+    public List<Object> fetchData(final ShardingContext shardingContext) {
+        return jobCaller.fetchData(shardingContext.getShardingItem());
+    }
+    
+    @Override
+    public void processData(final ShardingContext shardingContext, final List<Object> data) {
+        for (Object each : data) {
+            jobCaller.processData(each);
+        }
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/job/TestSimpleJob.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/job/TestSimpleJob.java
new file mode 100644
index 0000000..4ecc58e
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/job/TestSimpleJob.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.fixture.job;
+
+import io.elasticjob.cloud.api.ShardingContext;
+import io.elasticjob.cloud.api.simple.SimpleJob;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public final class TestSimpleJob implements SimpleJob {
+    
+    private final JobCaller jobCaller;
+    
+    @Override
+    public void execute(final ShardingContext shardingContext) {
+        jobCaller.execute();
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/job/TestWrongJob.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/job/TestWrongJob.java
new file mode 100644
index 0000000..a631cf8
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/fixture/job/TestWrongJob.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.fixture.job;
+
+import io.elasticjob.cloud.api.ShardingContext;
+import io.elasticjob.cloud.api.simple.SimpleJob;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public final class TestWrongJob implements SimpleJob {
+    
+    @Override
+    public void execute(final ShardingContext shardingContext) {
+        throw new RuntimeException("WrongJobException");
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/AllRegTests.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/AllRegTests.java
new file mode 100644
index 0000000..c68a685
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/AllRegTests.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.reg;
+
+import io.elasticjob.cloud.reg.zookeeper.ZookeeperRegistryCenterForAuthTest;
+import io.elasticjob.cloud.reg.zookeeper.ZookeeperRegistryCenterModifyTest;
+import io.elasticjob.cloud.reg.zookeeper.ZookeeperRegistryCenterQueryWithoutCacheTest;
+import io.elasticjob.cloud.reg.exception.RegExceptionHandlerTest;
+import io.elasticjob.cloud.reg.zookeeper.ZookeeperConfigurationTest;
+import io.elasticjob.cloud.reg.zookeeper.ZookeeperElectionServiceTest;
+import io.elasticjob.cloud.reg.zookeeper.ZookeeperRegistryCenterInitFailureTest;
+import io.elasticjob.cloud.reg.zookeeper.ZookeeperRegistryCenterMiscellaneousTest;
+import io.elasticjob.cloud.reg.zookeeper.ZookeeperRegistryCenterQueryWithCacheTest;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+@RunWith(Suite.class)
+@SuiteClasses({
+        ZookeeperConfigurationTest.class, 
+        ZookeeperRegistryCenterForAuthTest.class, 
+        ZookeeperRegistryCenterQueryWithCacheTest.class, 
+        ZookeeperRegistryCenterQueryWithoutCacheTest.class, 
+        ZookeeperRegistryCenterModifyTest.class, 
+        ZookeeperRegistryCenterMiscellaneousTest.class,
+        ZookeeperElectionServiceTest.class,
+        RegExceptionHandlerTest.class, 
+        ZookeeperRegistryCenterInitFailureTest.class
+    })
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class AllRegTests {
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/exception/RegExceptionHandlerTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/exception/RegExceptionHandlerTest.java
new file mode 100644
index 0000000..dec4358
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/exception/RegExceptionHandlerTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.reg.exception;
+
+import org.apache.zookeeper.KeeperException.ConnectionLossException;
+import org.apache.zookeeper.KeeperException.NoNodeException;
+import org.apache.zookeeper.KeeperException.NodeExistsException;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public final class RegExceptionHandlerTest {
+    
+    @Test
+    @Ignore
+    // TODO throw InterruptedException will cause zookeeper TestingServer break. Ignore first, fix it later.
+    public void assertHandleExceptionWithInterruptedException() {
+        RegExceptionHandler.handleException(new InterruptedException());
+    }
+    
+    @Test
+    public void assertHandleExceptionWithNull() {
+        RegExceptionHandler.handleException(null);
+    }
+    
+    @Test(expected = RegException.class)
+    public void assertHandleExceptionWithOtherException() {
+        RegExceptionHandler.handleException(new RuntimeException());
+    }
+    
+    @Test
+    public void assertHandleExceptionWithConnectionLossException() {
+        RegExceptionHandler.handleException(new ConnectionLossException());
+    }
+    
+    @Test
+    public void assertHandleExceptionWithNoNodeException() {
+        RegExceptionHandler.handleException(new NoNodeException());
+    }
+    
+    @Test
+    public void assertHandleExceptionWithNoNodeExistsException() {
+        RegExceptionHandler.handleException(new NodeExistsException());
+    }
+    
+    @Test
+    public void assertHandleExceptionWithCausedConnectionLossException() {
+        RegExceptionHandler.handleException(new RuntimeException(new ConnectionLossException()));
+    }
+    
+    @Test
+    public void assertHandleExceptionWithCausedNoNodeException() {
+        RegExceptionHandler.handleException(new RuntimeException(new NoNodeException()));
+    }
+    
+    @Test
+    public void assertHandleExceptionWithCausedNoNodeExistsException() {
+        RegExceptionHandler.handleException(new RuntimeException(new NodeExistsException()));
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperConfigurationTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperConfigurationTest.java
new file mode 100644
index 0000000..b756d6f
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperConfigurationTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.reg.zookeeper;
+
+import org.junit.Test;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+public final class ZookeeperConfigurationTest {
+    
+    @Test
+    public void assertNewZookeeperConfigurationForServerListsAndNamespace() {
+        ZookeeperConfiguration zkConfig = new ZookeeperConfiguration("localhost:2181", "myNamespace");
+        assertThat(zkConfig.getServerLists(), is("localhost:2181"));
+        assertThat(zkConfig.getNamespace(), is("myNamespace"));
+        assertThat(zkConfig.getBaseSleepTimeMilliseconds(), is(1000));
+        assertThat(zkConfig.getMaxSleepTimeMilliseconds(), is(3000));
+        assertThat(zkConfig.getMaxRetries(), is(3));
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperElectionServiceTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperElectionServiceTest.java
new file mode 100644
index 0000000..bfe76d9
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperElectionServiceTest.java
@@ -0,0 +1,52 @@
+package io.elasticjob.cloud.reg.zookeeper;
+
+import io.elasticjob.cloud.fixture.EmbedTestingServer;
+import io.elasticjob.cloud.reg.base.ElectionCandidate;
+import org.apache.curator.framework.CuratorFramework;
+import org.apache.curator.framework.CuratorFrameworkFactory;
+import org.apache.curator.retry.RetryOneTime;
+import org.apache.curator.test.KillSession;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ZookeeperElectionServiceTest {
+    
+    private static final String HOST_AND_PORT = "localhost:8899";
+    
+    private static final String ELECTION_PATH = "/election";
+    
+    @Mock
+    private ElectionCandidate electionCandidate;
+    
+    @BeforeClass
+    public static void init() throws InterruptedException {
+        EmbedTestingServer.start();
+    }
+    
+    @Test
+    @Ignore
+    public void assertContend() throws Exception {
+        CuratorFramework client = CuratorFrameworkFactory.newClient(EmbedTestingServer.getConnectionString(), new RetryOneTime(2000));
+        client.start();
+        client.blockUntilConnected();
+        ZookeeperElectionService service = new ZookeeperElectionService(HOST_AND_PORT, client, ELECTION_PATH, electionCandidate);
+        service.start();
+        ElectionCandidate anotherElectionCandidate = mock(ElectionCandidate.class);
+        CuratorFramework anotherClient = CuratorFrameworkFactory.newClient(EmbedTestingServer.getConnectionString(), new RetryOneTime(2000));
+        ZookeeperElectionService anotherService = new ZookeeperElectionService("ANOTHER_CLIENT:8899", anotherClient, ELECTION_PATH, anotherElectionCandidate);
+        anotherClient.start();
+        anotherClient.blockUntilConnected();
+        anotherService.start();
+        KillSession.kill(client.getZookeeperClient().getZooKeeper(), EmbedTestingServer.getConnectionString());
+        service.stop();
+        verify(anotherElectionCandidate).startLeadership();
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperRegistryCenterForAuthTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperRegistryCenterForAuthTest.java
new file mode 100644
index 0000000..4449650
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperRegistryCenterForAuthTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.reg.zookeeper;
+
+import io.elasticjob.cloud.fixture.EmbedTestingServer;
+import io.elasticjob.cloud.reg.zookeeper.util.ZookeeperRegistryCenterTestUtil;
+import org.apache.curator.framework.CuratorFramework;
+import org.apache.curator.framework.CuratorFrameworkFactory;
+import org.apache.curator.retry.RetryOneTime;
+import org.apache.zookeeper.KeeperException.NoAuthException;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public final class ZookeeperRegistryCenterForAuthTest {
+    
+    private static final String NAME_SPACE = ZookeeperRegistryCenterForAuthTest.class.getName();
+    
+    private static final ZookeeperConfiguration ZOOKEEPER_CONFIGURATION = new ZookeeperConfiguration(EmbedTestingServer.getConnectionString(), NAME_SPACE);
+    
+    private static ZookeeperRegistryCenter zkRegCenter;
+    
+    @BeforeClass
+    public static void setUp() {
+        EmbedTestingServer.start();
+        ZOOKEEPER_CONFIGURATION.setDigest("digest:password");
+        ZOOKEEPER_CONFIGURATION.setSessionTimeoutMilliseconds(5000);
+        ZOOKEEPER_CONFIGURATION.setConnectionTimeoutMilliseconds(5000);
+        zkRegCenter = new ZookeeperRegistryCenter(ZOOKEEPER_CONFIGURATION);
+        zkRegCenter.init();
+        ZookeeperRegistryCenterTestUtil.persist(zkRegCenter);
+    }
+    
+    @AfterClass
+    public static void tearDown() {
+        zkRegCenter.close();
+    }
+    
+    @Test
+    public void assertInitWithDigestSuccess() throws Exception {
+        CuratorFramework client = CuratorFrameworkFactory.builder()
+            .connectString(EmbedTestingServer.getConnectionString())
+            .retryPolicy(new RetryOneTime(2000))
+            .authorization("digest", "digest:password".getBytes()).build();
+        client.start();
+        client.blockUntilConnected();
+        assertThat(client.getData().forPath("/" + ZookeeperRegistryCenterForAuthTest.class.getName() + "/test/deep/nested"), is("deepNested".getBytes()));
+    }
+    
+    @Test(expected = NoAuthException.class)
+    public void assertInitWithDigestFailure() throws Exception {
+        CuratorFramework client = CuratorFrameworkFactory.newClient(EmbedTestingServer.getConnectionString(), new RetryOneTime(2000));
+        client.start();
+        client.blockUntilConnected();
+        client.getData().forPath("/" + ZookeeperRegistryCenterForAuthTest.class.getName() + "/test/deep/nested");
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperRegistryCenterInitFailureTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperRegistryCenterInitFailureTest.java
new file mode 100644
index 0000000..891f757
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperRegistryCenterInitFailureTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.reg.zookeeper;
+
+import io.elasticjob.cloud.reg.exception.RegException;
+import org.junit.Test;
+
+public final class ZookeeperRegistryCenterInitFailureTest {
+    
+    @Test(expected = RegException.class)
+    public void assertInitFailure() {
+        ZookeeperRegistryCenter zkRegCenter = new ZookeeperRegistryCenter(new ZookeeperConfiguration("localhost:1", ZookeeperRegistryCenterInitFailureTest.class.getName()));
+        zkRegCenter.init();
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperRegistryCenterMiscellaneousTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperRegistryCenterMiscellaneousTest.java
new file mode 100644
index 0000000..b5d32dc
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperRegistryCenterMiscellaneousTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.reg.zookeeper;
+
+import io.elasticjob.cloud.fixture.EmbedTestingServer;
+import org.apache.curator.framework.CuratorFramework;
+import org.apache.curator.framework.recipes.cache.TreeCache;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public final class ZookeeperRegistryCenterMiscellaneousTest {
+    
+    private static final ZookeeperConfiguration ZOOKEEPER_CONFIGURATION = 
+            new ZookeeperConfiguration(EmbedTestingServer.getConnectionString(), ZookeeperRegistryCenterMiscellaneousTest.class.getName());
+    
+    private static ZookeeperRegistryCenter zkRegCenter;
+    
+    @BeforeClass
+    public static void setUp() {
+        EmbedTestingServer.start();
+        ZOOKEEPER_CONFIGURATION.setConnectionTimeoutMilliseconds(30000);
+        zkRegCenter = new ZookeeperRegistryCenter(ZOOKEEPER_CONFIGURATION);
+        zkRegCenter.init();
+        zkRegCenter.addCacheData("/test");
+    }
+    
+    @AfterClass
+    public static void tearDown() {
+        zkRegCenter.close();
+    }
+    
+    @Test
+    public void assertGetRawClient() {
+        assertThat(zkRegCenter.getRawClient(), instanceOf(CuratorFramework.class));
+        assertThat(((CuratorFramework) zkRegCenter.getRawClient()).getNamespace(), is(ZookeeperRegistryCenterMiscellaneousTest.class.getName()));
+    }
+    
+    @Test
+    public void assertGetRawCache() {
+        assertThat(zkRegCenter.getRawCache("/test"), instanceOf(TreeCache.class));
+    }
+    
+    @Test
+    public void assertGetZkConfig() {
+        ZookeeperRegistryCenter zkRegCenter = new ZookeeperRegistryCenter(ZOOKEEPER_CONFIGURATION);
+        assertThat(zkRegCenter.getZkConfig(), is(ZOOKEEPER_CONFIGURATION));
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperRegistryCenterModifyTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperRegistryCenterModifyTest.java
new file mode 100644
index 0000000..9d0f175
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperRegistryCenterModifyTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.reg.zookeeper;
+
+import io.elasticjob.cloud.fixture.EmbedTestingServer;
+import io.elasticjob.cloud.reg.zookeeper.util.ZookeeperRegistryCenterTestUtil;
+import org.apache.curator.framework.CuratorFramework;
+import org.apache.curator.framework.CuratorFrameworkFactory;
+import org.apache.curator.retry.RetryOneTime;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+public final class ZookeeperRegistryCenterModifyTest {
+    
+    private static final ZookeeperConfiguration ZOOKEEPER_CONFIGURATION = new ZookeeperConfiguration(EmbedTestingServer.getConnectionString(), ZookeeperRegistryCenterModifyTest.class.getName());
+    
+    private static ZookeeperRegistryCenter zkRegCenter;
+    
+    @BeforeClass
+    public static void setUp() {
+        EmbedTestingServer.start();
+        zkRegCenter = new ZookeeperRegistryCenter(ZOOKEEPER_CONFIGURATION);
+        ZOOKEEPER_CONFIGURATION.setConnectionTimeoutMilliseconds(30000);
+        zkRegCenter.init();
+        ZookeeperRegistryCenterTestUtil.persist(zkRegCenter);
+    }
+    
+    @AfterClass
+    public static void tearDown() {
+        zkRegCenter.close();
+    }
+    
+    @Test
+    public void assertPersist() {
+        zkRegCenter.persist("/test", "test_update");
+        zkRegCenter.persist("/persist/new", "new_value");
+        assertThat(zkRegCenter.get("/test"), is("test_update"));
+        assertThat(zkRegCenter.get("/persist/new"), is("new_value"));
+    }
+    
+    @Test
+    public void assertUpdate() {
+        zkRegCenter.persist("/update", "before_update");
+        zkRegCenter.update("/update", "after_update");
+        assertThat(zkRegCenter.getDirectly("/update"), is("after_update"));
+    }
+    
+    @Test
+    public void assertPersistEphemeral() throws Exception {
+        zkRegCenter.persist("/persist", "persist_value");
+        zkRegCenter.persistEphemeral("/ephemeral", "ephemeral_value");
+        assertThat(zkRegCenter.get("/persist"), is("persist_value"));
+        assertThat(zkRegCenter.get("/ephemeral"), is("ephemeral_value"));
+        zkRegCenter.close();
+        CuratorFramework client = CuratorFrameworkFactory.newClient(EmbedTestingServer.getConnectionString(), new RetryOneTime(2000));
+        client.start();
+        client.blockUntilConnected();
+        assertThat(client.getData().forPath("/" + ZookeeperRegistryCenterModifyTest.class.getName() + "/persist"), is("persist_value".getBytes()));
+        assertNull(client.checkExists().forPath("/" + ZookeeperRegistryCenterModifyTest.class.getName() + "/ephemeral"));
+        zkRegCenter.init();
+    }
+    
+    
+    @Test
+    public void assertPersistSequential() throws Exception {
+        assertThat(zkRegCenter.persistSequential("/sequential/test_sequential", "test_value"), startsWith("/sequential/test_sequential"));
+        assertThat(zkRegCenter.persistSequential("/sequential/test_sequential", "test_value"), startsWith("/sequential/test_sequential"));
+        CuratorFramework client = CuratorFrameworkFactory.newClient(EmbedTestingServer.getConnectionString(), new RetryOneTime(2000));
+        client.start();
+        client.blockUntilConnected();
+        List<String> actual = client.getChildren().forPath("/" + ZookeeperRegistryCenterModifyTest.class.getName() + "/sequential");
+        assertThat(actual.size(), is(2));
+        for (String each : actual) {
+            assertThat(each, startsWith("test_sequential"));
+            assertThat(zkRegCenter.get("/sequential/" + each), startsWith("test_value"));
+        }
+    }
+    
+    @Test
+    public void assertPersistEphemeralSequential() throws Exception {
+        zkRegCenter.persistEphemeralSequential("/sequential/test_ephemeral_sequential");
+        zkRegCenter.persistEphemeralSequential("/sequential/test_ephemeral_sequential");
+        CuratorFramework client = CuratorFrameworkFactory.newClient(EmbedTestingServer.getConnectionString(), new RetryOneTime(2000));
+        client.start();
+        client.blockUntilConnected();
+        List<String> actual = client.getChildren().forPath("/" + ZookeeperRegistryCenterModifyTest.class.getName() + "/sequential");
+        assertThat(actual.size(), is(2));
+        for (String each : actual) {
+            assertThat(each, startsWith("test_ephemeral_sequential"));
+        }
+        zkRegCenter.close();
+        actual = client.getChildren().forPath("/" + ZookeeperRegistryCenterModifyTest.class.getName() + "/sequential");
+        assertTrue(actual.isEmpty());
+        zkRegCenter.init();
+    }
+    
+    @Test
+    public void assertRemove() {
+        zkRegCenter.remove("/test");
+        assertFalse(zkRegCenter.isExisted("/test"));
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperRegistryCenterQueryWithCacheTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperRegistryCenterQueryWithCacheTest.java
new file mode 100644
index 0000000..97c2143
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperRegistryCenterQueryWithCacheTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.reg.zookeeper;
+
+import io.elasticjob.cloud.fixture.EmbedTestingServer;
+import io.elasticjob.cloud.reg.zookeeper.util.ZookeeperRegistryCenterTestUtil;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+
+public final class ZookeeperRegistryCenterQueryWithCacheTest {
+    
+    private static final ZookeeperConfiguration ZOOKEEPER_CONFIGURATION = 
+            new ZookeeperConfiguration(EmbedTestingServer.getConnectionString(), ZookeeperRegistryCenterQueryWithCacheTest.class.getName());
+    
+    private static ZookeeperRegistryCenter zkRegCenter;
+    
+    @BeforeClass
+    public static void setUp() {
+        EmbedTestingServer.start();
+        zkRegCenter = new ZookeeperRegistryCenter(ZOOKEEPER_CONFIGURATION);
+        ZOOKEEPER_CONFIGURATION.setConnectionTimeoutMilliseconds(30000);
+        zkRegCenter.init();
+        ZookeeperRegistryCenterTestUtil.persist(zkRegCenter);
+        zkRegCenter.addCacheData("/test");
+    }
+    
+    @AfterClass
+    public static void tearDown() {
+        zkRegCenter.close();
+    }
+    
+    @Test
+    public void assertGetWithoutValue() {
+        assertNull(zkRegCenter.get("/test/null"));
+    }
+    
+    @Test
+    public void assertGetFromCache() {
+        assertThat(zkRegCenter.get("/test"), is("test"));
+        assertThat(zkRegCenter.get("/test/deep/nested"), is("deepNested"));
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperRegistryCenterQueryWithoutCacheTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperRegistryCenterQueryWithoutCacheTest.java
new file mode 100644
index 0000000..3de97fb
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/ZookeeperRegistryCenterQueryWithoutCacheTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.reg.zookeeper;
+
+import io.elasticjob.cloud.fixture.EmbedTestingServer;
+import io.elasticjob.cloud.reg.zookeeper.util.ZookeeperRegistryCenterTestUtil;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import static junit.framework.TestCase.assertNull;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+public final class ZookeeperRegistryCenterQueryWithoutCacheTest {
+    
+    private static final ZookeeperConfiguration ZOOKEEPER_CONFIGURATION = 
+            new ZookeeperConfiguration(EmbedTestingServer.getConnectionString(), ZookeeperRegistryCenterQueryWithoutCacheTest.class.getName());
+    
+    private static ZookeeperRegistryCenter zkRegCenter;
+    
+    @BeforeClass
+    public static void setUp() {
+        EmbedTestingServer.start();
+        ZOOKEEPER_CONFIGURATION.setConnectionTimeoutMilliseconds(30000);
+        zkRegCenter = new ZookeeperRegistryCenter(ZOOKEEPER_CONFIGURATION);
+        zkRegCenter.init();
+        ZookeeperRegistryCenterTestUtil.persist(zkRegCenter);
+        zkRegCenter.addCacheData("/other");
+    }
+    
+    @AfterClass
+    public static void tearDown() {
+        zkRegCenter.close();
+    }
+    
+    @Test
+    public void assertGetFromServer() {
+        assertThat(zkRegCenter.get("/test"), is("test"));
+        assertThat(zkRegCenter.get("/test/deep/nested"), is("deepNested"));
+    }
+    
+    @Test
+    public void assertGetChildrenKeys() {
+        assertThat(zkRegCenter.getChildrenKeys("/test"), is(Arrays.asList("deep", "child")));
+        assertThat(zkRegCenter.getChildrenKeys("/test/deep"), is(Collections.singletonList("nested")));
+        assertThat(zkRegCenter.getChildrenKeys("/test/child"), is(Collections.<String>emptyList()));
+        assertThat(zkRegCenter.getChildrenKeys("/test/notExisted"), is(Collections.<String>emptyList()));
+    }
+    
+    @Test
+    public void assertGetNumChildren() {
+        assertThat(zkRegCenter.getNumChildren("/test"), is(2));
+        assertThat(zkRegCenter.getNumChildren("/test/deep"), is(1));
+        assertThat(zkRegCenter.getNumChildren("/test/child"), is(0));
+        assertThat(zkRegCenter.getNumChildren("/test/notExisted"), is(0));
+    }
+    
+    @Test
+    public void assertIsExisted() {
+        assertTrue(zkRegCenter.isExisted("/test"));
+        assertTrue(zkRegCenter.isExisted("/test/deep/nested"));
+        assertFalse(zkRegCenter.isExisted("/notExisted"));
+    }
+    
+    @Test
+    public void assertGetRegistryCenterTime() {
+        long regCenterTime = zkRegCenter.getRegistryCenterTime("/_systemTime/current");
+        assertTrue(regCenterTime <= System.currentTimeMillis());
+        long updatedRegCenterTime = zkRegCenter.getRegistryCenterTime("/_systemTime/current");
+        System.out.println(regCenterTime + "," + updatedRegCenterTime);
+        assertTrue(regCenterTime < updatedRegCenterTime);
+    }
+
+    @Test
+    public void assertGetWithoutNode() {
+        assertNull(zkRegCenter.get("/notExisted"));
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/util/ZookeeperRegistryCenterTestUtil.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/util/ZookeeperRegistryCenterTestUtil.java
new file mode 100644
index 0000000..485d94d
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/reg/zookeeper/util/ZookeeperRegistryCenterTestUtil.java
@@ -0,0 +1,15 @@
+package io.elasticjob.cloud.reg.zookeeper.util;
+
+import io.elasticjob.cloud.reg.zookeeper.ZookeeperRegistryCenter;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ZookeeperRegistryCenterTestUtil {
+    
+    public static void persist(final ZookeeperRegistryCenter zookeeperRegistryCenter) {
+        zookeeperRegistryCenter.persist("/test", "test");
+        zookeeperRegistryCenter.persist("/test/deep/nested", "deepNested");
+        zookeeperRegistryCenter.persist("/test/child", "child");
+    } 
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/statistics/AllStatisticsTests.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/statistics/AllStatisticsTests.java
new file mode 100644
index 0000000..0d1adc3
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/statistics/AllStatisticsTests.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.statistics;
+
+import io.elasticjob.cloud.statistics.rdb.StatisticRdbRepositoryTest;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+@RunWith(Suite.class)
+@SuiteClasses(StatisticRdbRepositoryTest.class)
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class AllStatisticsTests {
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/statistics/rdb/StatisticRdbRepositoryTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/statistics/rdb/StatisticRdbRepositoryTest.java
new file mode 100644
index 0000000..ae59806
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/statistics/rdb/StatisticRdbRepositoryTest.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.statistics.rdb;
+
+import com.google.common.base.Optional;
+import io.elasticjob.cloud.statistics.StatisticInterval;
+import io.elasticjob.cloud.statistics.type.job.JobRegisterStatistics;
+import io.elasticjob.cloud.statistics.type.job.JobRunningStatistics;
+import io.elasticjob.cloud.statistics.type.task.TaskResultStatistics;
+import io.elasticjob.cloud.statistics.type.task.TaskRunningStatistics;
+import org.apache.commons.dbcp.BasicDataSource;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.sql.SQLException;
+import java.util.Date;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+
+public class StatisticRdbRepositoryTest {
+    
+    private StatisticRdbRepository  repository;
+    
+    @Before
+    public void setup() throws SQLException {
+        BasicDataSource dataSource = new BasicDataSource();
+        dataSource.setDriverClassName(org.h2.Driver.class.getName());
+        dataSource.setUrl("jdbc:h2:mem:");
+        dataSource.setUsername("sa");
+        dataSource.setPassword("");
+        repository = new StatisticRdbRepository(dataSource);
+    }
+    
+    @Test
+    public void assertAddTaskResultStatistics() {
+        for (StatisticInterval each : StatisticInterval.values()) {
+            assertTrue(repository.add(new TaskResultStatistics(100, 0, each, new Date())));
+        }
+    }
+    
+    @Test
+    public void assertAddTaskRunningStatistics() {
+        assertTrue(repository.add(new TaskRunningStatistics(100, new Date())));
+    }
+    
+    @Test
+    public void assertAddJobRunningStatistics() {
+        assertTrue(repository.add(new TaskRunningStatistics(100, new Date())));
+    }
+    
+    @Test
+    public void assertAddJobRegisterStatistics() {
+        assertTrue(repository.add(new JobRegisterStatistics(100, new Date())));
+    }
+    
+    @Test
+    public void assertFindTaskResultStatisticsWhenTableIsEmpty() {
+        assertThat(repository.findTaskResultStatistics(new Date(), StatisticInterval.MINUTE).size(), is(0));
+        assertThat(repository.findTaskResultStatistics(new Date(), StatisticInterval.HOUR).size(), is(0));
+        assertThat(repository.findTaskResultStatistics(new Date(), StatisticInterval.DAY).size(), is(0));
+    }
+    
+    @Test
+    public void assertFindTaskResultStatisticsWithDifferentFromDate() {
+        Date now = new Date();
+        Date yesterday = getYesterday();
+        for (StatisticInterval each : StatisticInterval.values()) {
+            assertTrue(repository.add(new TaskResultStatistics(100, 0, each, yesterday)));
+            assertTrue(repository.add(new TaskResultStatistics(100, 0, each, now)));
+            assertThat(repository.findTaskResultStatistics(yesterday, each).size(), is(2));
+            assertThat(repository.findTaskResultStatistics(now, each).size(), is(1));
+        }
+    }
+    
+    @Test
+    public void assertGetSummedTaskResultStatisticsWhenTableIsEmpty() {
+        for (StatisticInterval each : StatisticInterval.values()) {
+            TaskResultStatistics po = repository.getSummedTaskResultStatistics(new Date(), each);
+            assertThat(po.getSuccessCount(), is(0));
+            assertThat(po.getFailedCount(), is(0));
+        }
+    }
+    
+    @Test
+    public void assertGetSummedTaskResultStatistics() {
+        for (StatisticInterval each : StatisticInterval.values()) {
+            Date date = new Date();
+            repository.add(new TaskResultStatistics(100, 2, each, date));
+            repository.add(new TaskResultStatistics(200, 5, each, date));
+            TaskResultStatistics po = repository.getSummedTaskResultStatistics(date, each);
+            assertThat(po.getSuccessCount(), is(300));
+            assertThat(po.getFailedCount(), is(7));
+        }
+    }
+    
+    @Test
+    public void assertFindLatestTaskResultStatisticsWhenTableIsEmpty() {
+        for (StatisticInterval each : StatisticInterval.values()) {
+            assertFalse(repository.findLatestTaskResultStatistics(each).isPresent());
+        }
+    }
+    
+    @Test
+    public void assertFindLatestTaskResultStatistics() {
+        for (StatisticInterval each : StatisticInterval.values()) {
+            repository.add(new TaskResultStatistics(100, 2, each, new Date()));
+            repository.add(new TaskResultStatistics(200, 5, each, new Date()));
+            Optional<TaskResultStatistics> po = repository.findLatestTaskResultStatistics(each);
+            assertThat(po.get().getSuccessCount(), is(200));
+            assertThat(po.get().getFailedCount(), is(5));
+        }
+    }
+    
+    @Test
+    public void assertFindTaskRunningStatisticsWhenTableIsEmpty() {
+        assertThat(repository.findTaskRunningStatistics(new Date()).size(), is(0));
+    }
+    
+    @Test
+    public void assertFindTaskRunningStatisticsWithDifferentFromDate() {
+        Date now = new Date();
+        Date yesterday = getYesterday();
+        assertTrue(repository.add(new TaskRunningStatistics(100, yesterday)));
+        assertTrue(repository.add(new TaskRunningStatistics(100, now)));
+        assertThat(repository.findTaskRunningStatistics(yesterday).size(), is(2));
+        assertThat(repository.findTaskRunningStatistics(now).size(), is(1));
+    }
+    
+    @Test
+    public void assertFindLatestTaskRunningStatisticsWhenTableIsEmpty() {
+        assertFalse(repository.findLatestTaskRunningStatistics().isPresent());
+    }
+    
+    @Test
+    public void assertFindLatestTaskRunningStatistics() {
+        repository.add(new TaskRunningStatistics(100, new Date()));
+        repository.add(new TaskRunningStatistics(200, new Date()));
+        Optional<TaskRunningStatistics> po = repository.findLatestTaskRunningStatistics();
+        assertThat(po.get().getRunningCount(), is(200));
+    }
+    
+    @Test
+    public void assertFindJobRunningStatisticsWhenTableIsEmpty() {
+        assertThat(repository.findJobRunningStatistics(new Date()).size(), is(0));
+    }
+    
+    @Test
+    public void assertFindJobRunningStatisticsWithDifferentFromDate() {
+        Date now = new Date();
+        Date yesterday = getYesterday();
+        assertTrue(repository.add(new JobRunningStatistics(100, yesterday)));
+        assertTrue(repository.add(new JobRunningStatistics(100, now)));
+        assertThat(repository.findJobRunningStatistics(yesterday).size(), is(2));
+        assertThat(repository.findJobRunningStatistics(now).size(), is(1));
+    }
+    
+    @Test
+    public void assertFindLatestJobRunningStatisticsWhenTableIsEmpty() {
+        assertFalse(repository.findLatestJobRunningStatistics().isPresent());
+    }
+    
+    @Test
+    public void assertFindLatestJobRunningStatistics() {
+        repository.add(new JobRunningStatistics(100, new Date()));
+        repository.add(new JobRunningStatistics(200, new Date()));
+        Optional<JobRunningStatistics> po = repository.findLatestJobRunningStatistics();
+        assertThat(po.get().getRunningCount(), is(200));
+    }
+    
+    @Test
+    public void assertFindJobRegisterStatisticsWhenTableIsEmpty() {
+        assertThat(repository.findJobRegisterStatistics(new Date()).size(), is(0));
+    }
+    
+    @Test
+    public void assertFindJobRegisterStatisticsWithDifferentFromDate() {
+        Date now = new Date();
+        Date yesterday = getYesterday();
+        assertTrue(repository.add(new JobRegisterStatistics(100, yesterday)));
+        assertTrue(repository.add(new JobRegisterStatistics(100, now)));
+        assertThat(repository.findJobRegisterStatistics(yesterday).size(), is(2));
+        assertThat(repository.findJobRegisterStatistics(now).size(), is(1));
+    }
+    
+    @Test
+    public void assertFindLatestJobRegisterStatisticsWhenTableIsEmpty() {
+        assertFalse(repository.findLatestJobRegisterStatistics().isPresent());
+    }
+    
+    @Test
+    public void assertFindLatestJobRegisterStatistics() {
+        repository.add(new JobRegisterStatistics(100, new Date()));
+        repository.add(new JobRegisterStatistics(200, new Date()));
+        Optional<JobRegisterStatistics> po = repository.findLatestJobRegisterStatistics();
+        assertThat(po.get().getRegisteredCount(), is(200));
+    }
+    
+    private Date getYesterday() {
+        return new Date(new Date().getTime() - 24 * 60 * 60 * 1000);
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/AllUtilTests.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/AllUtilTests.java
new file mode 100644
index 0000000..092835a
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/AllUtilTests.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.util;
+
+import io.elasticjob.cloud.util.concurrent.ExecutorServiceObjectTest;
+import io.elasticjob.cloud.util.config.ShardingItemParametersTest;
+import io.elasticjob.cloud.util.config.ShardingItemsTest;
+import io.elasticjob.cloud.util.digest.EncryptionTest;
+import io.elasticjob.cloud.util.env.HostExceptionTest;
+import io.elasticjob.cloud.util.env.IpUtilsTest;
+import io.elasticjob.cloud.util.env.TimeServiceTest;
+import io.elasticjob.cloud.util.json.GsonFactoryTest;
+import io.elasticjob.cloud.util.json.JobConfigurationGsonTypeAdapterTest;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+@RunWith(Suite.class)
+@SuiteClasses({
+        ExecutorServiceObjectTest.class, 
+        EncryptionTest.class, 
+        TimeServiceTest.class, 
+        IpUtilsTest.class, 
+        HostExceptionTest.class, 
+        GsonFactoryTest.class, 
+        JobConfigurationGsonTypeAdapterTest.class, 
+        ShardingItemsTest.class, 
+        ShardingItemParametersTest.class
+    })
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class AllUtilTests {
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/concurrent/ExecutorServiceObjectTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/concurrent/ExecutorServiceObjectTest.java
new file mode 100644
index 0000000..6c959c9
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/concurrent/ExecutorServiceObjectTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.util.concurrent;
+
+import org.junit.Test;
+
+import java.util.concurrent.ExecutorService;
+
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+
+public final class ExecutorServiceObjectTest {
+    
+    private ExecutorServiceObject executorServiceObject;
+    
+    @Test
+    public void assertCreateExecutorService() {
+        executorServiceObject = new ExecutorServiceObject("executor-service-test", 1);
+        assertThat(executorServiceObject.getActiveThreadCount(), is(0));
+        assertThat(executorServiceObject.getWorkQueueSize(), is(0));
+        assertFalse(executorServiceObject.isShutdown());
+        ExecutorService executorService = executorServiceObject.createExecutorService();
+        executorService.submit(new FooTask());
+        BlockUtils.waitingShortTime();
+        assertThat(executorServiceObject.getActiveThreadCount(), is(1));
+        assertThat(executorServiceObject.getWorkQueueSize(), is(0));
+        assertFalse(executorServiceObject.isShutdown());
+        executorService.submit(new FooTask());
+        BlockUtils.waitingShortTime();
+        assertThat(executorServiceObject.getActiveThreadCount(), is(1));
+        assertThat(executorServiceObject.getWorkQueueSize(), is(1));
+        assertFalse(executorServiceObject.isShutdown());
+        executorService.shutdownNow();
+        assertThat(executorServiceObject.getWorkQueueSize(), is(0));
+        assertTrue(executorServiceObject.isShutdown());
+    }
+    
+    class FooTask implements Runnable {
+        
+        @Override
+        public void run() {
+            BlockUtils.sleep(1000L);
+        }
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/config/ShardingItemParametersTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/config/ShardingItemParametersTest.java
new file mode 100644
index 0000000..8f7f3b0
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/config/ShardingItemParametersTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.util.config;
+
+import io.elasticjob.cloud.exception.JobConfigurationException;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public final class ShardingItemParametersTest {
+    
+    @Test(expected = JobConfigurationException.class)
+    public void assertNewWhenPairFormatInvalid() {
+        new ShardingItemParameters("xxx-xxx");
+    }
+    
+    @Test(expected = JobConfigurationException.class)
+    public void assertNewWhenItemIsNotNumber() {
+        new ShardingItemParameters("xxx=xxx");
+    }
+    
+    @Test
+    public void assertGetMapWhenIsEmpty() {
+        assertThat(new ShardingItemParameters("").getMap(), is(Collections.EMPTY_MAP));
+    }
+    
+    @Test
+    public void assertGetMap() {
+        Map<Integer, String> expected = new HashMap<>(3);
+        expected.put(0, "A");
+        expected.put(1, "B");
+        expected.put(2, "C");
+        assertThat(new ShardingItemParameters("0=A,1=B,2=C").getMap(), is(expected));
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/config/ShardingItemsTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/config/ShardingItemsTest.java
new file mode 100644
index 0000000..ebd61ad
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/config/ShardingItemsTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.util.config;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public final class ShardingItemsTest {
+    
+    @Test
+    public void assertTtoItemListWhenNull() {
+        assertThat(ShardingItems.toItemList(null), is(Collections.EMPTY_LIST));
+    }
+    
+    @Test
+    public void assertToItemListWhenEmpty() {
+        assertThat(ShardingItems.toItemList(""), is(Collections.EMPTY_LIST));
+    }
+    
+    @Test
+    public void assertToItemList() {
+        assertThat(ShardingItems.toItemList("0,1,2"), is(Arrays.asList(0, 1, 2)));
+    }
+    
+    @Test
+    public void assertToItemListForDuplicated() {
+        assertThat(ShardingItems.toItemList("0,1,2,2"), is(Arrays.asList(0, 1, 2)));
+    }
+    
+    @Test
+    public void assertToItemsStringWhenEmpty() {
+        assertThat(ShardingItems.toItemsString(Collections.<Integer>emptyList()), is(""));
+    }
+    
+    @Test
+    public void assertToItemsString() {
+        assertThat(ShardingItems.toItemsString(Arrays.asList(0, 1, 2)), is("0,1,2"));
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/digest/EncryptionTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/digest/EncryptionTest.java
new file mode 100644
index 0000000..c1ffe78
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/digest/EncryptionTest.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.util.digest;
+
+import org.junit.Test;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+public final class EncryptionTest {
+    
+    @Test
+    public void assertMd5() {
+        assertThat(Encryption.md5("test"), is("98f6bcd4621d373cade4e832627b4f6"));
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/env/HostExceptionTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/env/HostExceptionTest.java
new file mode 100644
index 0000000..5424316
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/env/HostExceptionTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.util.env;
+
+import org.hamcrest.core.Is;
+import org.junit.Test;
+
+import java.io.IOException;
+
+import static org.junit.Assert.assertThat;
+
+public final class HostExceptionTest {
+    
+    @Test
+    public void assertGetCause() {
+        IOException cause = new IOException();
+        assertThat(new HostException(cause).getCause(), Is.<Throwable>is(cause));
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/env/IpUtilsTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/env/IpUtilsTest.java
new file mode 100644
index 0000000..33d8144
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/env/IpUtilsTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.util.env;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertNotNull;
+
+public final class IpUtilsTest {
+    
+    @Test
+    public void assertGetIp() {
+        assertNotNull(IpUtils.getIp());
+    }
+    
+    @Test
+    public void assertGetHostName() {
+        assertNotNull(IpUtils.getHostName());
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/env/TimeServiceTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/env/TimeServiceTest.java
new file mode 100644
index 0000000..1cca186
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/env/TimeServiceTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.util.env;
+
+import org.junit.Test;
+
+import static junit.framework.TestCase.assertTrue;
+
+
+public class TimeServiceTest {
+    
+    private TimeService timeService = new TimeService();
+    
+    @Test
+    public void assertGetCurrentMillis() throws Exception {
+        assertTrue(timeService.getCurrentMillis() <= System.currentTimeMillis());
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/json/GsonFactoryTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/json/GsonFactoryTest.java
new file mode 100644
index 0000000..0cfc991
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/json/GsonFactoryTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.util.json;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import org.junit.Test;
+
+import java.io.IOException;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public final class GsonFactoryTest {
+    
+    @Test
+    public void assertGetGson() {
+        assertThat(GsonFactory.getGson(), is(GsonFactory.getGson()));
+    }
+    
+    @Test
+    public void assertRegisterTypeAdapter() {
+        Gson beforeRegisterGson = GsonFactory.getGson();
+        GsonFactory.registerTypeAdapter(GsonFactoryTest.class, new TypeAdapter() {
+            
+            @Override
+            public Object read(final JsonReader in) throws IOException {
+                return null;
+            }
+            
+            @Override
+            public void write(final JsonWriter out, final Object value) throws IOException {
+                out.jsonValue("test");
+            }
+        });
+        assertThat(beforeRegisterGson.toJson(new GsonFactoryTest()), is("{}"));
+        assertThat(GsonFactory.getGson().toJson(new GsonFactoryTest()), is("test"));
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/json/JobConfigurationGsonTypeAdapterTest.java b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/json/JobConfigurationGsonTypeAdapterTest.java
new file mode 100644
index 0000000..ab92232
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/java/io/elasticjob/cloud/util/json/JobConfigurationGsonTypeAdapterTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.util.json;
+
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import io.elasticjob.cloud.config.JobTypeConfiguration;
+import io.elasticjob.cloud.fixture.APIJsonConstants;
+import io.elasticjob.cloud.fixture.config.TestDataflowJobConfiguration;
+import io.elasticjob.cloud.fixture.config.TestJobRootConfiguration;
+import io.elasticjob.cloud.fixture.config.TestScriptJobConfiguration;
+import io.elasticjob.cloud.fixture.config.TestSimpleJobConfiguration;
+import io.elasticjob.cloud.executor.handler.impl.DefaultExecutorServiceHandler;
+import io.elasticjob.cloud.fixture.handler.IgnoreJobExceptionHandler;
+import io.elasticjob.cloud.fixture.handler.ThrowJobExceptionHandler;
+import org.hamcrest.core.Is;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Map;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+public final class JobConfigurationGsonTypeAdapterTest {
+    
+    @BeforeClass
+    public static void setUp() {
+        GsonFactory.registerTypeAdapter(TestJobRootConfiguration.class, new JobConfigurationGsonTypeAdapter());
+    } 
+    
+    @Test
+    public void assertToSimpleJobJson() {
+        assertThat(GsonFactory.getGson().toJson(new TestJobRootConfiguration(
+                new TestSimpleJobConfiguration(ThrowJobExceptionHandler.class.getCanonicalName(), DefaultExecutorServiceHandler.class.getCanonicalName()).getTypeConfig())),
+                Is.is(APIJsonConstants.getSimpleJobJson(ThrowJobExceptionHandler.class.getCanonicalName())));
+    }
+    
+    @Test
+    public void assertToDataflowJobJson() {
+        assertThat(GsonFactory.getGson().toJson(new TestJobRootConfiguration(new TestDataflowJobConfiguration(true).getTypeConfig())),
+                is(APIJsonConstants.getDataflowJobJson(IgnoreJobExceptionHandler.class.getCanonicalName())));
+    }
+    
+    @Test
+    public void assertToScriptJobJson() {
+        assertThat(GsonFactory.getGson().toJson(new TestJobRootConfiguration(new TestScriptJobConfiguration("test.sh", ThrowJobExceptionHandler.class).getTypeConfig())),
+                is(APIJsonConstants.getScriptJobJson(ThrowJobExceptionHandler.class.getCanonicalName())));
+    }
+    
+    @Test
+    public void assertFromSimpleJobJson() {
+        TestJobRootConfiguration actual = GsonFactory.getGson().fromJson(
+                APIJsonConstants.getSimpleJobJson(ThrowJobExceptionHandler.class.getCanonicalName()), TestJobRootConfiguration.class);
+        TestJobRootConfiguration expected = new TestJobRootConfiguration(
+                new TestSimpleJobConfiguration(ThrowJobExceptionHandler.class.getCanonicalName(), DefaultExecutorServiceHandler.class.getCanonicalName()).getTypeConfig());
+        assertThat(GsonFactory.getGson().toJson(actual), is(GsonFactory.getGson().toJson(expected)));
+    }
+    
+    @Test
+    public void assertFromDataflowJobJson() {
+        TestJobRootConfiguration actual = GsonFactory.getGson().fromJson(
+                APIJsonConstants.getDataflowJobJson(IgnoreJobExceptionHandler.class.getCanonicalName()), TestJobRootConfiguration.class);
+        TestJobRootConfiguration expected = new TestJobRootConfiguration(new TestDataflowJobConfiguration(true).getTypeConfig());
+        assertThat(GsonFactory.getGson().toJson(actual), is(GsonFactory.getGson().toJson(expected)));
+    }
+    
+    @Test
+    public void assertFromScriptJobJson() {
+        TestJobRootConfiguration actual = GsonFactory.getGson().fromJson(
+                APIJsonConstants.getScriptJobJson(ThrowJobExceptionHandler.class.getCanonicalName()), TestJobRootConfiguration.class);
+        TestJobRootConfiguration expected = new TestJobRootConfiguration(new TestScriptJobConfiguration("test.sh", ThrowJobExceptionHandler.class).getTypeConfig());
+        assertThat(GsonFactory.getGson().toJson(actual), is(GsonFactory.getGson().toJson(expected)));
+    }
+    
+    private static class JobConfigurationGsonTypeAdapter extends AbstractJobConfigurationGsonTypeAdapter<TestJobRootConfiguration> {
+    
+        @Override
+        protected void addToCustomizedValueMap(final String jsonName, final JsonReader in, final Map<String, Object> customizedValueMap) throws IOException {
+        }
+    
+        @Override
+        protected TestJobRootConfiguration getJobRootConfiguration(final JobTypeConfiguration typeConfig, final Map<String, Object> customizedValueMap) {
+            return new TestJobRootConfiguration(typeConfig);
+        }
+    
+        @Override
+        protected void writeCustomized(final JsonWriter out, final TestJobRootConfiguration value) throws IOException {
+        }
+    }
+}
diff --git a/elastic-job-cloud-common/src/test/resources/conf/reg/local.properties b/elastic-job-cloud-common/src/test/resources/conf/reg/local.properties
new file mode 100644
index 0000000..6b77fa2
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/resources/conf/reg/local.properties
@@ -0,0 +1,3 @@
+/test=test
+/test/deep/nested=deepNested
+/test/child=child
diff --git a/elastic-job-cloud-common/src/test/resources/conf/reg/local_overwrite.properties b/elastic-job-cloud-common/src/test/resources/conf/reg/local_overwrite.properties
new file mode 100644
index 0000000..41a69be
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/resources/conf/reg/local_overwrite.properties
@@ -0,0 +1,3 @@
+/test=test_overwrite
+/test/deep/nested=deepNested_overwrite
+/new=new
diff --git a/elastic-job-cloud-common/src/test/resources/logback-test.xml b/elastic-job-cloud-common/src/test/resources/logback-test.xml
new file mode 100644
index 0000000..8db3a18
--- /dev/null
+++ b/elastic-job-cloud-common/src/test/resources/logback-test.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <property name="log.context.name" value="elastic-job-common-core-test" />
+    <property name="log.charset" value="UTF-8" />
+    <property name="log.pattern" value="[%-5level] %date --%thread-- [%logger] %msg %n" />
+    
+    <contextName>${log.context.name}</contextName>
+    
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <level>ERROR</level>
+        </filter>
+        <encoder charset="${log.charset}">
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+    </appender>
+    
+    <root>
+        <appender-ref ref="STDOUT" />
+    </root>
+    
+    <logger name="io.elasticjob.cloud.event.rdb.JobEventRdbConfiguration" level="OFF" />
+    <logger name="io.elasticjob.cloud.event.JobEventBus" level="OFF" />
+    <logger name="io.elasticjob.cloud.executor.handler.impl.DefaultJobExceptionHandler" level="OFF" />
+    <logger name="org.apache.curator.framework.recipes.leader.LeaderSelector" level="OFF" />
+</configuration>
diff --git a/elastic-job-cloud-scheduler/src/main/java/io/elasticjob/cloud/restful/GsonJsonProvider.java b/elastic-job-cloud-scheduler/src/main/java/io/elasticjob/cloud/restful/GsonJsonProvider.java
new file mode 100644
index 0000000..d52f68f
--- /dev/null
+++ b/elastic-job-cloud-scheduler/src/main/java/io/elasticjob/cloud/restful/GsonJsonProvider.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.restful;
+
+import io.elasticjob.cloud.util.json.GsonFactory;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+
+/**
+ * 基于GSON解析JSON的解析器.
+ *
+ * @author zhangliang
+ */
+@Provider
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+public final class GsonJsonProvider implements MessageBodyWriter<Object>, MessageBodyReader<Object> {
+    
+    private static final String UTF_8 = "UTF-8";
+    
+    @Override
+    public Object readFrom(final Class<Object> type, final Type genericType, final Annotation[] annotations,
+                           final MediaType mediaType, final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream) {
+        try (InputStreamReader streamReader = new InputStreamReader(entityStream, UTF_8)) {
+            return GsonFactory.getGson().fromJson(streamReader, type.equals(genericType) ? type : genericType);
+        } catch (final IOException ex) {
+            throw new RestfulException(ex);
+        }
+    }
+    
+    @Override
+    public void writeTo(final Object object, final Class<?> type, final Type genericType, final Annotation[] annotations, final MediaType mediaType,
+                        final MultivaluedMap<String, Object> httpHeaders, final OutputStream entityStream) throws IOException, WebApplicationException {
+        try (OutputStreamWriter writer = new OutputStreamWriter(entityStream, UTF_8)) {
+            GsonFactory.getGson().toJson(object, type.equals(genericType) ? type : genericType, writer);
+        } catch (final IOException ex) {
+            throw new RestfulException(ex);
+        }
+    }
+    
+    @Override
+    public boolean isReadable(final Class<?> type, final Type genericType, final Annotation[] annotations, final MediaType mediaType) {
+        return true;
+    }
+    
+    @Override
+    public boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations, final MediaType mediaType) {
+        return true;
+    }
+    
+    @Override
+    public long getSize(final Object object, final Class<?> type, final Type genericType, final Annotation[] annotations, final MediaType mediaType) {
+        return -1;
+    }
+}
diff --git a/elastic-job-cloud-scheduler/src/main/java/io/elasticjob/cloud/restful/RestfulException.java b/elastic-job-cloud-scheduler/src/main/java/io/elasticjob/cloud/restful/RestfulException.java
new file mode 100644
index 0000000..0768b73
--- /dev/null
+++ b/elastic-job-cloud-scheduler/src/main/java/io/elasticjob/cloud/restful/RestfulException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.restful;
+
+/**
+ * REST API异常.
+ *
+ * @author zhangliang
+ */
+public final class RestfulException extends RuntimeException {
+    
+    private static final long serialVersionUID = -7594937349408972960L;
+    
+    public RestfulException(final Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/elastic-job-cloud-scheduler/src/main/java/io/elasticjob/cloud/restful/RestfulExceptionMapper.java b/elastic-job-cloud-scheduler/src/main/java/io/elasticjob/cloud/restful/RestfulExceptionMapper.java
new file mode 100644
index 0000000..6f5fc83
--- /dev/null
+++ b/elastic-job-cloud-scheduler/src/main/java/io/elasticjob/cloud/restful/RestfulExceptionMapper.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.restful;
+
+import io.elasticjob.cloud.exception.ExceptionUtil;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * RESTFul API的异常处理器.
+ *
+ * @author zhangliang
+ */
+@Provider
+public final class RestfulExceptionMapper implements ExceptionMapper<Throwable> {
+    
+    @Override
+    public Response toResponse(final Throwable cause) {
+        return Response.ok(ExceptionUtil.transform(cause), MediaType.TEXT_PLAIN).status(Response.Status.INTERNAL_SERVER_ERROR).build();
+    }
+}
diff --git a/elastic-job-cloud-scheduler/src/main/java/io/elasticjob/cloud/restful/RestfulServer.java b/elastic-job-cloud-scheduler/src/main/java/io/elasticjob/cloud/restful/RestfulServer.java
new file mode 100644
index 0000000..ae17748
--- /dev/null
+++ b/elastic-job-cloud-scheduler/src/main/java/io/elasticjob/cloud/restful/RestfulServer.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.restful;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.sun.jersey.api.core.PackagesResourceConfig;
+import com.sun.jersey.spi.container.servlet.ServletContainer;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.HandlerList;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.resource.Resource;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import java.util.EnumSet;
+
+/**
+ * REST API的内嵌服务器.
+ *
+ * @author zhangliang
+ * @author caohao
+ */
+@Slf4j
+public final class RestfulServer {
+    
+    private final Server server;
+    
+    private final ServletContextHandler servletContextHandler;
+    
+    public RestfulServer(final int port) {
+        server = new Server(port);
+        servletContextHandler = buildServletContextHandler();
+    }
+    
+    /**
+     * 启动内嵌的RESTful服务器.
+     * 
+     * @param packages RESTful实现类所在包
+     * @param resourcePath 资源路径
+     * @throws Exception 启动服务器异常
+     */
+    public void start(final String packages, final Optional<String> resourcePath) throws Exception {
+        start(packages, resourcePath, Optional.of("/api"));
+    }
+    
+    /**
+     * 启动内嵌的RESTful服务器.
+     *
+     * @param packages RESTful实现类所在包
+     * @param resourcePath 资源路径
+     * @param servletPath servlet路径
+     * @throws Exception 启动服务器异常
+     */
+    public void start(final String packages, final Optional<String> resourcePath, final Optional<String> servletPath) throws Exception {
+        log.info("Elastic Job: Start RESTful server");
+        HandlerList handlers = new HandlerList();
+        if (resourcePath.isPresent()) {
+            servletContextHandler.setBaseResource(Resource.newClassPathResource(resourcePath.get()));
+            servletContextHandler.addServlet(new ServletHolder(DefaultServlet.class), "/*");
+        }
+        String servletPathStr = (servletPath.isPresent() ? servletPath.get() : "") + "/*";
+        servletContextHandler.addServlet(getServletHolder(packages), servletPathStr);
+        handlers.addHandler(servletContextHandler);
+        server.setHandler(handlers);
+        server.start();
+    }
+    
+    /**
+     * 添加Filter.
+     *
+     * @param filterClass filter实现类
+     * @param urlPattern 过滤的路径
+     * @return RESTful服务器
+     */
+    public RestfulServer addFilter(final Class<? extends Filter> filterClass, final String urlPattern) {
+        servletContextHandler.addFilter(filterClass, urlPattern, EnumSet.of(DispatcherType.REQUEST));
+        return this;
+    }
+    
+    private ServletContextHandler buildServletContextHandler() {
+        ServletContextHandler result = new ServletContextHandler(ServletContextHandler.SESSIONS);
+        result.setContextPath("/");
+        return result;
+    }
+    
+    private ServletHolder getServletHolder(final String packages) {
+        ServletHolder result = new ServletHolder(ServletContainer.class);
+        result.setInitParameter(PackagesResourceConfig.PROPERTY_PACKAGES, Joiner.on(",").join(RestfulServer.class.getPackage().getName(), packages));
+        result.setInitParameter("com.sun.jersey.config.property.resourceConfigClass", PackagesResourceConfig.class.getName());
+        result.setInitParameter("com.sun.jersey.api.json.POJOMappingFeature", Boolean.TRUE.toString());
+        result.setInitParameter("resteasy.scan.providers", Boolean.TRUE.toString());
+        result.setInitParameter("resteasy.use.builtin.providers", Boolean.FALSE.toString());
+        return result;
+    }
+    
+    /**
+     * 安静停止内嵌的RESTful服务器.
+     * 
+     */
+    public void stop() {
+        log.info("Elastic Job: Stop RESTful server");
+        try {
+            server.stop();
+            // CHECKSTYLE:OFF
+        } catch (final Exception e) {
+            // CHECKSTYLE:ON
+            log.error("Elastic Job: Stop RESTful server error", e);
+        }
+    }
+}
diff --git a/elastic-job-cloud-scheduler/src/main/java/io/elasticjob/cloud/security/WwwAuthFilter.java b/elastic-job-cloud-scheduler/src/main/java/io/elasticjob/cloud/security/WwwAuthFilter.java
new file mode 100644
index 0000000..7a0d2c0
--- /dev/null
+++ b/elastic-job-cloud-scheduler/src/main/java/io/elasticjob/cloud/security/WwwAuthFilter.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.security;
+
+import com.google.common.base.Joiner;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.binary.Base64;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Properties;
+
+/**
+ * 认证过滤器.
+ * 
+ * @author zhangliang 
+ */
+@Slf4j
+public final class WwwAuthFilter implements Filter {
+    
+    private static final String FILE_SEPARATOR = System.getProperty("file.separator");
+    
+    private static final String AUTH_PREFIX = "Basic ";
+    
+    private static final String ROOT_IDENTIFY = "root";
+    
+    private static final String ROOT_DEFAULT_USERNAME = "root";
+    
+    private static final String ROOT_DEFAULT_PASSWORD = "root";
+    
+    private static final String GUEST_IDENTIFY = "guest";
+    
+    private static final String GUEST_DEFAULT_USERNAME = "guest";
+    
+    private static final String GUEST_DEFAULT_PASSWORD = "guest";
+    
+    
+    private String rootUsername;
+    
+    private String rootPassword;
+    
+    private String guestUsername;
+    
+    private String guestPassword;
+    
+    @Override
+    public void init(final FilterConfig filterConfig) throws ServletException {
+        Properties props = new Properties();
+        URL classLoaderURL = Thread.currentThread().getContextClassLoader().getResource("");
+        if (null != classLoaderURL) {
+            String configFilePath = Joiner.on(FILE_SEPARATOR).join(classLoaderURL.getPath(), "conf", "auth.properties");
+            try {
+                props.load(new FileInputStream(configFilePath));
+            } catch (final IOException ex) {
+                log.warn("Cannot found auth config file, use default auth config.");
+            }
+        }
+        rootUsername = props.getProperty("root.username", ROOT_DEFAULT_USERNAME);
+        rootPassword = props.getProperty("root.password", ROOT_DEFAULT_PASSWORD);
+        guestUsername = props.getProperty("guest.username", GUEST_DEFAULT_USERNAME);
+        guestPassword = props.getProperty("guest.password", GUEST_DEFAULT_PASSWORD);
+    }
+    
+    @Override
+    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
+        HttpServletRequest httpRequest = (HttpServletRequest) request;
+        HttpServletResponse httpResponse = (HttpServletResponse) response;
+        String authorization = httpRequest.getHeader("authorization");
+        if (null != authorization && authorization.length() > AUTH_PREFIX.length()) {
+            authorization = authorization.substring(AUTH_PREFIX.length(), authorization.length());
+            if ((rootUsername + ":" + rootPassword).equals(new String(Base64.decodeBase64(authorization)))) {
+                authenticateSuccess(httpResponse, false);
+                chain.doFilter(httpRequest, httpResponse);
+            } else if ((guestUsername + ":" + guestPassword).equals(new String(Base64.decodeBase64(authorization)))) {
+                authenticateSuccess(httpResponse, true);
+                chain.doFilter(httpRequest, httpResponse);
+            } else {
+                needAuthenticate(httpResponse);
+            }
+        } else {
+            needAuthenticate(httpResponse);
+        }
+    }
+    
+    private void authenticateSuccess(final HttpServletResponse response, final boolean isGuest) {
+        response.setStatus(200);
+        response.setHeader("Pragma", "No-cache");
+        response.setHeader("Cache-Control", "no-store");
+        response.setDateHeader("Expires", 0);
+        response.setHeader("identify", isGuest ? GUEST_IDENTIFY : ROOT_IDENTIFY);
+    }
+    
+    private void needAuthenticate(final HttpServletResponse response) {
+        response.setStatus(401);
+        response.setHeader("Cache-Control", "no-store");
+        response.setDateHeader("Expires", 0);
+        response.setHeader("WWW-authenticate", AUTH_PREFIX + "Realm=\"Elastic Job Console Auth\"");
+    }
+    
+    @Override
+    public void destroy() {
+    }
+}
diff --git a/elastic-job-cloud-scheduler/src/test/java/io/elasticjob/cloud/AllTests.java b/elastic-job-cloud-scheduler/src/test/java/io/elasticjob/cloud/AllTests.java
new file mode 100644
index 0000000..f89ee0b
--- /dev/null
+++ b/elastic-job-cloud-scheduler/src/test/java/io/elasticjob/cloud/AllTests.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud;
+
+import io.elasticjob.cloud.restful.AllRestfulTests;
+import io.elasticjob.cloud.scheduler.AllCloudSchedulerTests;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+@RunWith(Suite.class)
+@SuiteClasses({
+        AllCloudSchedulerTests.class,
+        AllRestfulTests.class 
+    })
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class AllTests {
+}
diff --git a/elastic-job-cloud-scheduler/src/test/java/io/elasticjob/cloud/restful/AllRestfulTests.java b/elastic-job-cloud-scheduler/src/test/java/io/elasticjob/cloud/restful/AllRestfulTests.java
new file mode 100644
index 0000000..8bcef95
--- /dev/null
+++ b/elastic-job-cloud-scheduler/src/test/java/io/elasticjob/cloud/restful/AllRestfulTests.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.restful;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+@RunWith(Suite.class)
+@SuiteClasses({
+        RestfulServerTest.class, 
+        RestfulExceptionTest.class 
+    })
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class AllRestfulTests {
+}
diff --git a/elastic-job-cloud-scheduler/src/test/java/io/elasticjob/cloud/restful/RestfulExceptionTest.java b/elastic-job-cloud-scheduler/src/test/java/io/elasticjob/cloud/restful/RestfulExceptionTest.java
new file mode 100644
index 0000000..11bb883
--- /dev/null
+++ b/elastic-job-cloud-scheduler/src/test/java/io/elasticjob/cloud/restful/RestfulExceptionTest.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.restful;
+
+import org.junit.Test;
+
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.junit.Assert.assertThat;
+
+public final class RestfulExceptionTest {
+    
+    @Test
+    public void assertRestfulException() {
+        assertThat(new RestfulException(new RuntimeException()).getCause(), instanceOf(RuntimeException.class));
+    }
+}
diff --git a/elastic-job-cloud-scheduler/src/test/java/io/elasticjob/cloud/restful/RestfulServerTest.java b/elastic-job-cloud-scheduler/src/test/java/io/elasticjob/cloud/restful/RestfulServerTest.java
new file mode 100644
index 0000000..b8fe6e3
--- /dev/null
+++ b/elastic-job-cloud-scheduler/src/test/java/io/elasticjob/cloud/restful/RestfulServerTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.restful;
+
+import com.google.common.base.Optional;
+import io.elasticjob.cloud.restful.fixture.TestFilter;
+import io.elasticjob.cloud.restful.fixture.TestRestfulApi;
+import io.elasticjob.cloud.restful.fixture.Caller;
+import org.eclipse.jetty.client.ContentExchange;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.hamcrest.core.Is;
+import org.hamcrest.core.StringStartsWith;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import javax.ws.rs.core.MediaType;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class RestfulServerTest {
+    
+    private static final String URL = "http://127.0.0.1:17000/api/test/call";
+    
+    private static RestfulServer server;
+    
+    private Caller caller;
+    
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        server = new RestfulServer(17000);
+        server.addFilter(TestFilter.class, "/*");
+        server.start(TestRestfulApi.class.getPackage().getName(), Optional.<String>absent());
+    }
+    
+    @AfterClass
+    public static void tearDown() throws Exception {
+        server.stop();
+    }
+    
+    @Before
+    public void setUp() throws Exception {
+        caller = Mockito.mock(Caller.class);
+        TestRestfulApi.setCaller(caller);
+    }
+    
+    @Test
+    public void assertCallSuccess() throws Exception {
+        ContentExchange actual = sentRequest("{\"string\":\"test\",\"integer\":1}");
+        Assert.assertThat(actual.getResponseStatus(), Is.is(200));
+        Assert.assertThat(actual.getResponseContent(), Is.is("{\"string\":\"test_processed\",\"integer\":\"1_processed\"}"));
+        Mockito.verify(caller).call("test");
+        Mockito.verify(caller).call(1);
+    }
+    
+    @Test
+    public void assertCallFailure() throws Exception {
+        ContentExchange actual = sentRequest("{\"string\":\"test\",\"integer\":\"invalid_number\"}");
+        Assert.assertThat(actual.getResponseStatus(), Is.is(500));
+        Assert.assertThat(actual.getResponseContent(), StringStartsWith.startsWith("java.lang.NumberFormatException"));
+        Mockito.verify(caller).call("test");
+    }
+    
+    private static ContentExchange sentRequest(final String content) throws Exception {
+        HttpClient httpClient = new HttpClient();
+        try {
+            httpClient.start();
+            ContentExchange result = new ContentExchange();
+            result.setMethod("POST");
+            result.setRequestContentType(MediaType.APPLICATION_JSON);
+            result.setRequestContent(new ByteArrayBuffer(content.getBytes("UTF-8")));
+            httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
+            result.setURL(URL);
+            httpClient.send(result);
+            result.waitForDone();
+            return result;
+        } finally {
+            httpClient.stop();
+        }
+    }
+}
diff --git a/elastic-job-cloud-scheduler/src/test/java/io/elasticjob/cloud/restful/fixture/Caller.java b/elastic-job-cloud-scheduler/src/test/java/io/elasticjob/cloud/restful/fixture/Caller.java
new file mode 100644
index 0000000..edee2d8
--- /dev/null
+++ b/elastic-job-cloud-scheduler/src/test/java/io/elasticjob/cloud/restful/fixture/Caller.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.restful.fixture;
+
+public interface Caller {
+    
+    void call(String value);
+    
+    void call(int value);
+}
diff --git a/elastic-job-cloud-scheduler/src/test/java/io/elasticjob/cloud/restful/fixture/TestFilter.java b/elastic-job-cloud-scheduler/src/test/java/io/elasticjob/cloud/restful/fixture/TestFilter.java
new file mode 100644
index 0000000..4f50044
--- /dev/null
+++ b/elastic-job-cloud-scheduler/src/test/java/io/elasticjob/cloud/restful/fixture/TestFilter.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.restful.fixture;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+public final class TestFilter implements Filter {
+    
+    @Override
+    public void init(final FilterConfig filterConfig) throws ServletException {
+    }
+    
+    @Override
+    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
+        HttpServletRequest httpRequest = (HttpServletRequest) request;
+        HttpServletResponse httpResponse = (HttpServletResponse) response;
+        chain.doFilter(httpRequest, httpResponse);
+    }
+    
+    @Override
+    public void destroy() {
+    }
+}
diff --git a/elastic-job-cloud-scheduler/src/test/java/io/elasticjob/cloud/restful/fixture/TestRestfulApi.java b/elastic-job-cloud-scheduler/src/test/java/io/elasticjob/cloud/restful/fixture/TestRestfulApi.java
new file mode 100644
index 0000000..79390f9
--- /dev/null
+++ b/elastic-job-cloud-scheduler/src/test/java/io/elasticjob/cloud/restful/fixture/TestRestfulApi.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 1999-2015 dangdang.com.
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * </p>
+ */
+
+package io.elasticjob.cloud.restful.fixture;
+
+import com.google.common.collect.Maps;
+import lombok.Setter;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.MediaType;
+import java.util.Map;
+
+@Path("/test")
+public final class TestRestfulApi {
+    
+    @Setter
+    private static Caller caller;
+    
+    @POST
+    @Path("/call")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Map<String, String> call(final Map<String, String> map) {
+        caller.call(map.get("string"));
+        caller.call(Integer.valueOf(map.get("integer")));
+        return Maps.transformEntries(map, new Maps.EntryTransformer<String, String, String>() {
+            
+            @Override
+            public String transformEntry(final String key, final String value) {
+                return value + "_processed";
+            }
+        });
+    }
+}