Commits

timemachine committed ec9b5fc Merge

Merged 1.1.x work - scheduler-hibernate.

  • Participants
  • Parent commits b6fffed, e360931

Comments (0)

Files changed (63)

-ae9b33cd62a426fe87a13ec1b5aa47f43b4ebaf1 timemachine-parent-1.0.0
+ae9b33cd62a426fe87a13ec1b5aa47f43b4ebaf1 1.0.0
 
 	<groupId>org.bitbucket.timemachine</groupId>
 	<artifactId>timemachine-parent</artifactId>
-	<version>1.0.1-SNAPSHOT</version>
+	<version>1.1.0-SNAPSHOT</version>
 	<packaging>pom</packaging>
 
 	<modules>
 		<module>timemachine-scheduler</module>
+		<module>timemachine-hibernate</module>
 		<module>timemachine-dist</module>
 	</modules>
 
 			<version>1.8.5</version>
 			<scope>test</scope>
 		</dependency>
+		
+		<!-- Logging lib -->
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-api</artifactId>
+			<version>1.6.1</version>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-log4j12</artifactId>
+			<version>1.6.1</version>
+			<scope>runtime</scope>
+			<optional>true</optional>
+		</dependency>
 	</dependencies>
 
 	<build>

scripts/openshift/root-war.sh

 	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
 </web-app>' > $PROJ/WEB-INF/web.xml
 
-echo '<html>
+echo '
+<html>
 <body>
+<script type="text/javascript">
+
+  var _gaq = _gaq || [];
+  _gaq.push(["_setAccount", "UA-29830061-2"]);
+  _gaq.push(["_trackPageview"]);
+
+  (function() {
+    var ga = document.createElement("script"); ga.type = "text/javascript"; ga.async = true;
+    ga.src = ("https:" == document.location.protocol ? "https://ssl" : "http://www") + ".google-analytics.com/ga.js";
+    var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(ga, s);
+  })();
+
+</script>
 <h1>TimeMachine Scheduler Demo Server</h1>
 <ul>
 <li><a href="scheduler-web">Scheduler Demo</a></li>
 <li><a href="scheduler-site/staging/index.html">Scheduler Maven Site Reports</a></li>
 </ul>
 </body>
-</html>' > $PROJ/index.html
+</html> 
+' > $PROJ/index.html
 
 cd $PROJ
 rm ../ROOT.war

scripts/openshift/scheduler-site.sh

 mkdir -p $PROJ//timemachine-dist/target
 cp -r target/timemachine-dist/target/timemachine-dist $PROJ//timemachine-dist/target
 
+echo '
+<html>
+<body>
+<script type="text/javascript">
+
+  var _gaq = _gaq || [];
+  _gaq.push(["_setAccount", "UA-29830061-2"]);
+  _gaq.push(["_trackPageview"]);
+
+  (function() {
+    var ga = document.createElement("script"); ga.type = "text/javascript"; ga.async = true;
+    ga.src = ("https:" == document.location.protocol ? "https://ssl" : "http://www") + ".google-analytics.com/ga.js";
+    var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(ga, s);
+  })();
+
+</script>
+<h1>TimeMachine Scheduler Maven Reports Site</h1>
+<ul>
+<li><a href="staging/index.html">Main Index</a></li>
+</ul>
+</body>
+</html> 
+' > $PROJ/index.html
 
 cd $PROJ
 rm ../scheduler-site.war

timemachine-dist/pom.xml

 	<parent>
 		<groupId>org.bitbucket.timemachine</groupId>
 		<artifactId>timemachine-parent</artifactId>
-		<version>1.0.1-SNAPSHOT</version>
+		<version>1.1.0-SNAPSHOT</version>
 	</parent>
 
 	<artifactId>timemachine-dist</artifactId>

timemachine-dist/src/main/assembly/bin.xml

 			<useAllReactorProjects>true</useAllReactorProjects>
 			<includes>
 				<include>org.bitbucket.timemachine:timemachine-scheduler</include>
+				<include>org.bitbucket.timemachine:timemachine-hibernate</include>
 			</includes>
 			<sources>
 				<fileSets>

timemachine-hibernate/config/invokeMissedRunSchedules.groovy

+
+// reset all schedule's state to WAITING
+import timemachine.scheduler.*
+import timemachine.scheduler.support.*
+jobDefs = scheduler.findJobDefs()
+jobDefs.each{ jobDef ->
+	schedules = jobDef.getSchedules()
+	schedules.each{ s ->
+		s.onMissedRun()
+		println("Invoke $s onMissedRun().")
+	}
+	scheduler.updateJobDef(jobDef)
+}
+
+schedules = scheduler.findSchedules()
+Collections.sort(schedules, new ScheduleComparator())
+schedules.each{ s ->
+  println(s.toString() + ", nextRun: " + s.getNextRun() + ", state: " + s.getState() + ", startTime: " + s.getStartTime())
+}

timemachine-hibernate/config/resetSchedules.groovy

+
+// reset all schedule's state to WAITING
+import timemachine.scheduler.*
+import timemachine.scheduler.support.*
+jobDefs = scheduler.findJobDefs()
+jobDefs.each{ jobDef ->
+	schedules = jobDef.getSchedules()
+	schedules.each{ s ->
+		s.setState(Schedule.State.WAITING)
+		println("Reseting $s state to WAITING.")
+	}
+	scheduler.updateJobDef(jobDef)
+}
+
+schedules = scheduler.findSchedules()
+Collections.sort(schedules, new ScheduleComparator())
+schedules.each{ s ->
+  println(s.toString() + ", nextRun: " + s.getNextRun() + ", state: " + s.getState() + ", startTime: " + s.getStartTime())
+}

timemachine-hibernate/config/scheduler-h2-create-schema.properties

+# Copyright 2012 Zemian Deng
+# 
+# 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.
+
+# Use default H2 database that use ~/h2-tmscheduler as data store
+timemachine.scheduler.dataStore.class = timemachine.scheduler.hibernate.HibernateDataStore
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.hbm2ddl.auto = create

timemachine-hibernate/config/scheduler-h2-jobs.properties

+# Copyright 2012 Zemian Deng
+# 
+# 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.
+
+# Use default H2 database that use ~/h2-tmscheduler as data store
+timemachine.scheduler.dataStore.class = timemachine.scheduler.hibernate.HibernateDataStore
+
+# Load some sample jobs
+timemachine.scheduler.userservice.jobLoader.class = timemachine.scheduler.userservice.JobLoaderService
+JobLoaderService.00myJob.timemachine.scheduler.jobtask.LoggerJobTask = RepeatSchedule{interval=3|intervalUnit=SECOND}
+JobLoaderService.01myJob.timemachine.scheduler.jobtask.LoggerJobTask = RepeatSchedule{interval=2|intervalUnit=SECOND|endCount=2}
+JobLoaderService.02myJob.timemachine.scheduler.jobtask.LoggerJobTask = RepeatSchedule{interval=1|intervalUnit=MINUTE|startTime=23:15:00}
+JobLoaderService.03myJob.timemachine.scheduler.jobtask.LoggerJobTask = JobProps{msg=hello|test=foo}; RepeatSchedule{interval=17|intervalUnit=SECOND|endTime=1/1/2099 00:00:00}
+JobLoaderService.04myJob.timemachine.scheduler.jobtask.LoggerJobTask = CronSchedule{expression=* * * * * ?}
+JobLoaderService.05myJob.timemachine.scheduler.jobtask.LoggerJobTask = CronSchedule{expression=0 * * * * ?}
+JobLoaderService.06myJob.timemachine.scheduler.jobtask.LoggerJobTask = CronSchedule{expression=0 0 8 ? * MON,FRI}
+JobLoaderService.07myJob.timemachine.scheduler.jobtask.LoggerJobTask = CronSchedule{expression=0,15,30,45 * * * * ? | endCount=5}
+JobLoaderService.08myJob.timemachine.scheduler.jobtask.SleepyJobTask = DateListSchedule{datetime.1=1/1/2012 00:00:00}
+JobLoaderService.09myJob.timemachine.scheduler.jobtask.LoggerJobTask = DateListSchedule{datetime.1=1/1/2012 00:00:00|datetime.2=2/1/2012 23:59:59}
+JobLoaderService.10myJob.timemachine.scheduler.jobtask.ScriptingJobTask = JobProps{scriptEgnineName=JavaScript|scriptText=1+1}; RepeatSchedule{endCount=1}
+JobLoaderService.11myJob.timemachine.scheduler.jobtask.OsCommandJobTask = JobProps{commandLine=echo "Hello World!"}; RepeatSchedule{endCount=1}

timemachine-hibernate/config/scheduler-h2.properties

+# Copyright 2012 Zemian Deng
+# 
+# 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.
+
+# Use default H2 database that use ~/h2-tmscheduler as data store
+timemachine.scheduler.dataStore.class = timemachine.scheduler.hibernate.HibernateDataStore

timemachine-hibernate/config/scheduler-mysql-create-schema.properties

+# Copyright 2012 Zemian Deng
+# 
+# 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.
+
+# Use MySQL for scheduler as data store
+timemachine.scheduler.dataStore.class = timemachine.scheduler.hibernate.HibernateDataStore
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.hbm2ddl.auto = update
+
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.driver_class = com.mysql.jdbc.Driver
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.url = jdbc:mysql://localhost:3306/tmscheduler
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.username = admin
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.password = admin123
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.dialect = org.hibernate.dialect.MySQLDialect
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.show_sql = false
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.pool_size = 5

timemachine-hibernate/config/scheduler-mysql-jobs.properties

+# Copyright 2012 Zemian Deng
+# 
+# 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.
+
+# Use MySQL for scheduler as data store
+timemachine.scheduler.dataStore.class = timemachine.scheduler.hibernate.HibernateDataStore
+
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.driver_class = com.mysql.jdbc.Driver
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.url = jdbc:mysql://localhost:3306/tmscheduler
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.username = admin
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.password = admin123
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.dialect = org.hibernate.dialect.MySQLDialect
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.show_sql = false
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.pool_size = 5
+
+# Load some sample jobs
+timemachine.scheduler.userservice.jobLoader.class = timemachine.scheduler.userservice.JobLoaderService
+JobLoaderService.00myJob.timemachine.scheduler.jobtask.LoggerJobTask = RepeatSchedule{interval=3|intervalUnit=SECOND}
+JobLoaderService.01myJob.timemachine.scheduler.jobtask.LoggerJobTask = RepeatSchedule{interval=2|intervalUnit=SECOND|endCount=2}
+JobLoaderService.02myJob.timemachine.scheduler.jobtask.LoggerJobTask = RepeatSchedule{interval=1|intervalUnit=MINUTE|startTime=23:15:00}
+JobLoaderService.03myJob.timemachine.scheduler.jobtask.LoggerJobTask = JobProps{msg=hello|test=foo}; RepeatSchedule{interval=17|intervalUnit=SECOND|endTime=1/1/2099 00:00:00}
+JobLoaderService.04myJob.timemachine.scheduler.jobtask.LoggerJobTask = CronSchedule{expression=* * * * * ?}
+JobLoaderService.05myJob.timemachine.scheduler.jobtask.LoggerJobTask = CronSchedule{expression=0 * * * * ?}
+JobLoaderService.06myJob.timemachine.scheduler.jobtask.LoggerJobTask = CronSchedule{expression=0 0 8 ? * MON,FRI}
+JobLoaderService.07myJob.timemachine.scheduler.jobtask.LoggerJobTask = CronSchedule{expression=0,15,30,45 * * * * ? | endCount=5}
+JobLoaderService.08myJob.timemachine.scheduler.jobtask.SleepyJobTask = DateListSchedule{datetime.1=1/1/2012 00:00:00}
+JobLoaderService.09myJob.timemachine.scheduler.jobtask.LoggerJobTask = DateListSchedule{datetime.1=1/1/2012 00:00:00|datetime.2=2/1/2012 23:59:59}
+JobLoaderService.10myJob.timemachine.scheduler.jobtask.ScriptingJobTask = JobProps{scriptEgnineName=JavaScript|scriptText=1+1}; RepeatSchedule{endCount=1}
+JobLoaderService.11myJob.timemachine.scheduler.jobtask.OsCommandJobTask = JobProps{commandLine=echo "Hello World!"}; RepeatSchedule{endCount=1}
+JobLoaderService.12myJob.timemachine.scheduler.jobtask.LoggerJobTask = RepeatSchedule{interval=1|intervalUnit=SECOND}

timemachine-hibernate/config/scheduler-mysql-node1.properties

+# Copyright 2012 Zemian Deng
+# 
+# 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.
+
+timemachine.scheduler.schedulerName = TimeMachineScheduler
+timemachine.scheduler.nodeName = Node1
+
+# Use MySQL for scheduler as data store
+timemachine.scheduler.dataStore.class = timemachine.scheduler.hibernate.HibernateDataStore
+
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.driver_class = com.mysql.jdbc.Driver
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.url = jdbc:mysql://localhost:3306/tmscheduler
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.username = admin
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.password = admin123
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.dialect = org.hibernate.dialect.MySQLDialect
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.show_sql = false
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.min_size=1
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.max_size=5
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.preferredTestQuery=SELECT 1

timemachine-hibernate/config/scheduler-mysql-node2.properties

+# Copyright 2012 Zemian Deng
+# 
+# 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.
+
+# Setup multiple scheduler nodes
+timemachine.scheduler.schedulerName = TimeMachineScheduler
+timemachine.scheduler.nodeName = Node2
+
+# Use MySQL for scheduler as data store
+timemachine.scheduler.dataStore.class = timemachine.scheduler.hibernate.HibernateDataStore
+
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.driver_class = com.mysql.jdbc.Driver
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.url = jdbc:mysql://localhost:3306/tmscheduler
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.username = admin
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.password = admin123
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.dialect = org.hibernate.dialect.MySQLDialect
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.show_sql = false
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.min_size=1
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.max_size=5
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.preferredTestQuery=SELECT 1

timemachine-hibernate/config/scheduler-mysql-node3.properties

+# Copyright 2012 Zemian Deng
+# 
+# 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.
+
+# Setup multiple scheduler nodes
+timemachine.scheduler.schedulerName = TimeMachineScheduler
+timemachine.scheduler.nodeName = Node3
+
+# Use MySQL for scheduler as data store
+timemachine.scheduler.dataStore.class = timemachine.scheduler.hibernate.HibernateDataStore
+
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.driver_class = com.mysql.jdbc.Driver
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.url = jdbc:mysql://localhost:3306/tmscheduler
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.username = admin
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.password = admin123
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.dialect = org.hibernate.dialect.MySQLDialect
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.show_sql = false
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.min_size=1
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.max_size=5
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.preferredTestQuery=SELECT 1

timemachine-hibernate/config/scheduler-oracle-create-schema.properties

+# Copyright 2012 Zemian Deng
+# 
+# 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.
+
+# Setup multiple scheduler nodes
+timemachine.scheduler.schedulerName = TimeMachineScheduler
+timemachine.scheduler.nodeName = TimeMachineScheduler
+
+# Use MySQL for scheduler as data store
+timemachine.scheduler.dataStore.class = timemachine.scheduler.hibernate.HibernateDataStore
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.hbm2ddl.auto = create
+
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.driver_class = oracle.jdbc.driver.OracleDriver
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.url = jdbc:oracle:thin:@localhost:1521:XE
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.username = admin
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.password = admin123
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.dialect = org.hibernate.dialect.Oracle10gDialect
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.show_sql = false
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.pool_size = 5

timemachine-hibernate/config/scheduler-oracle-jobs.properties

+# Copyright 2012 Zemian Deng
+# 
+# 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.
+
+# Use MySQL for scheduler as data store
+timemachine.scheduler.dataStore.class = timemachine.scheduler.hibernate.HibernateDataStore
+
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.driver_class = oracle.jdbc.driver.OracleDriver
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.url = jdbc:oracle:thin:@localhost:1521:XE
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.username = admin
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.password = admin123
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.dialect = org.hibernate.dialect.Oracle10gDialect
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.show_sql = false
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.pool_size = 5
+
+# Load some sample jobs
+timemachine.scheduler.userservice.jobLoader.class = timemachine.scheduler.userservice.JobLoaderService
+JobLoaderService.00myJob.timemachine.scheduler.jobtask.LoggerJobTask = RepeatSchedule{interval=3|intervalUnit=SECOND}
+JobLoaderService.01myJob.timemachine.scheduler.jobtask.LoggerJobTask = RepeatSchedule{interval=2|intervalUnit=SECOND|endCount=2}
+JobLoaderService.02myJob.timemachine.scheduler.jobtask.LoggerJobTask = RepeatSchedule{interval=1|intervalUnit=MINUTE|startTime=23:15:00}
+JobLoaderService.03myJob.timemachine.scheduler.jobtask.LoggerJobTask = JobProps{msg=hello|test=foo}; RepeatSchedule{interval=17|intervalUnit=SECOND|endTime=1/1/2099 00:00:00}
+JobLoaderService.04myJob.timemachine.scheduler.jobtask.LoggerJobTask = CronSchedule{expression=* * * * * ?}
+JobLoaderService.05myJob.timemachine.scheduler.jobtask.LoggerJobTask = CronSchedule{expression=0 * * * * ?}
+JobLoaderService.06myJob.timemachine.scheduler.jobtask.LoggerJobTask = CronSchedule{expression=0 0 8 ? * MON,FRI}
+JobLoaderService.07myJob.timemachine.scheduler.jobtask.LoggerJobTask = CronSchedule{expression=0,15,30,45 * * * * ? | endCount=5}
+JobLoaderService.08myJob.timemachine.scheduler.jobtask.SleepyJobTask = DateListSchedule{datetime.1=1/1/2012 00:00:00}
+JobLoaderService.09myJob.timemachine.scheduler.jobtask.LoggerJobTask = DateListSchedule{datetime.1=1/1/2012 00:00:00|datetime.2=2/1/2012 23:59:59}
+JobLoaderService.10myJob.timemachine.scheduler.jobtask.ScriptingJobTask = JobProps{scriptEgnineName=JavaScript|scriptText=1+1}; RepeatSchedule{endCount=1}
+JobLoaderService.11myJob.timemachine.scheduler.jobtask.OsCommandJobTask = JobProps{commandLine=echo "Hello World!"}; RepeatSchedule{endCount=1}
+JobLoaderService.12myJob.timemachine.scheduler.jobtask.LoggerJobTask = RepeatSchedule{interval=1|intervalUnit=SECOND}

timemachine-hibernate/config/scheduler-oracle-node1.properties

+# Copyright 2012 Zemian Deng
+# 
+# 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.
+
+# Setup multiple scheduler nodes
+timemachine.scheduler.schedulerName = TimeMachineScheduler
+timemachine.scheduler.nodeName = Node1
+
+# Use MySQL for scheduler as data store
+timemachine.scheduler.dataStore.class = timemachine.scheduler.hibernate.HibernateDataStore
+
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.driver_class = oracle.jdbc.driver.OracleDriver
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.url = jdbc:oracle:thin:@localhost:1521:XE
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.username = admin
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.password = admin123
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.dialect = org.hibernate.dialect.Oracle10gDialect
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.show_sql = false
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.min_size=1
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.max_size=5
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.preferredTestQuery=SELECT 1 FROM DUAL

timemachine-hibernate/config/scheduler-oracle-node2.properties

+# Copyright 2012 Zemian Deng
+# 
+# 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.
+
+# Setup multiple scheduler nodes
+timemachine.scheduler.schedulerName = TimeMachineScheduler
+timemachine.scheduler.nodeName = Node2
+
+# Use MySQL for scheduler as data store
+timemachine.scheduler.dataStore.class = timemachine.scheduler.hibernate.HibernateDataStore
+
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.driver_class = oracle.jdbc.driver.OracleDriver
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.url = jdbc:oracle:thin:@localhost:1521:XE
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.username = admin
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.password = admin123
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.dialect = org.hibernate.dialect.Oracle10gDialect
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.show_sql = false
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.min_size=1
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.max_size=5
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.preferredTestQuery=SELECT 1 FROM DUAL

timemachine-hibernate/config/scheduler-oracle-node3.properties

+# Copyright 2012 Zemian Deng
+# 
+# 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.
+
+# Setup multiple scheduler nodes
+timemachine.scheduler.schedulerName = TimeMachineScheduler
+timemachine.scheduler.nodeName = Node3
+
+# Use MySQL for scheduler as data store
+timemachine.scheduler.dataStore.class = timemachine.scheduler.hibernate.HibernateDataStore
+
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.driver_class = oracle.jdbc.driver.OracleDriver
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.url = jdbc:oracle:thin:@localhost:1521:XE
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.username = admin
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.password = admin123
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.dialect = org.hibernate.dialect.Oracle10gDialect
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.show_sql = false
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.min_size=1
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.max_size=5
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.preferredTestQuery=SELECT 1 FROM DUAL

timemachine-hibernate/config/scripting-scheduler.properties

+timemachine.scheduler.userservice.scriptingService.class = timemachine.scheduler.userservice.ScriptingService
+ScriptingService.scriptEngineName = Groovy
+ScriptingService.initScript = ${script}
+
+timemachine.scheduler.schedulerName = TimeMachineScheduler
+timemachine.scheduler.nodeName = Node1
+
+# Use MySQL for scheduler as data store
+timemachine.scheduler.dataStore.class = timemachine.scheduler.hibernate.HibernateDataStore
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.hbm2ddl.auto = update
+
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.driver_class = com.mysql.jdbc.Driver
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.url = jdbc:mysql://localhost:3306/tmscheduler
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.username = admin
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.password = admin123
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.pool_size = 5
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.dialect = org.hibernate.dialect.MySQLDialect
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.show_sql = false

timemachine-hibernate/config/showSchedules.groovy

+
+// Show all schedules using the system sorter
+import timemachine.scheduler.*
+import timemachine.scheduler.support.*
+schedules = scheduler.findSchedules()
+Collections.sort(schedules, new ScheduleComparator())
+schedules.each{ s ->
+  println(s.toString() + ", nextRun: " + s.getNextRun() + ", state: " + s.getState() + ", startTime: " + s.getStartTime()
+   + ", runCount: " + s.getRunCount()  + ", missedRunCount: " + s.getMissedRunCount())
+}
+println("Done: ${schedules.size()} schedules.")

timemachine-hibernate/pom.xml

+<?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/maven-v4_0_0.xsd">
+	
+	<modelVersion>4.0.0</modelVersion>
+	<name>TimeMachine Hibernate</name>
+	
+	<parent>
+		<groupId>org.bitbucket.timemachine</groupId>
+		<artifactId>timemachine-parent</artifactId>
+		<version>1.1.0-SNAPSHOT</version>
+	</parent>
+	
+	<artifactId>timemachine-hibernate</artifactId>
+	<packaging>jar</packaging>
+
+	<dependencies>		
+		<!-- Use timemachine-scheduler integration base classes for testing. -->
+		<dependency>
+			<groupId>org.bitbucket.timemachine</groupId>
+			<artifactId>timemachine-scheduler</artifactId>
+			<version>1.1.0-SNAPSHOT</version>
+			<type>test-jar</type>
+			<scope>test</scope>
+		</dependency>
+
+		<dependency>
+			<groupId>org.bitbucket.timemachine</groupId>
+			<artifactId>timemachine-scheduler</artifactId>
+			<version>1.1.0-SNAPSHOT</version>
+		</dependency>
+
+		<!-- Hibernate -->
+		<dependency>
+			<groupId>org.hibernate</groupId>
+			<artifactId>hibernate-core</artifactId>
+			<version>4.1.3.Final</version>
+		</dependency>
+		<dependency>
+			<groupId>c3p0</groupId>
+			<artifactId>c3p0</artifactId>
+			<version>0.9.1.2</version>
+			<scope>runtime</scope>
+			<optional>true</optional>
+		</dependency>
+		
+		<!-- H2Database -->
+		<dependency>
+			<groupId>com.h2database</groupId>
+			<artifactId>h2</artifactId>
+			<version>1.3.163</version>
+			<scope>runtime</scope>
+			<optional>true</optional>
+		</dependency>
+		
+		<!-- We set groovy engine as optional run time. -->
+		<dependency>
+			<groupId>org.codehaus.groovy</groupId>
+			<artifactId>groovy</artifactId>
+			<version>1.8.6</version>
+			<scope>runtime</scope>
+			<optional>true</optional>
+		</dependency>
+
+	</dependencies>
+	
+	<profiles>
+		<profile>
+			<id>mysql</id>
+			<dependencies>			
+				<!-- MySQL driver -->
+				<dependency>
+					<groupId>mysql</groupId>
+					<artifactId>mysql-connector-java</artifactId>
+					<version>5.1.16</version>
+				</dependency>
+			</dependencies>
+		</profile>
+		<profile>
+			<id>oracle</id>
+			<dependencies>	
+				<!-- Oracle driver -->
+				<dependency>
+					<groupId>com.oracle</groupId>
+					<artifactId>ojdbc6</artifactId>
+					<version>11g</version>
+				</dependency>
+			</dependencies>
+		</profile>
+	</profiles>
+</project>

timemachine-hibernate/src/main/java/timemachine/scheduler/hibernate/HbmSessionTemplate.java

+/*
+ * Copyright 2012 Zemian Deng
+ * 
+ * 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.
+ */
+package timemachine.scheduler.hibernate;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.hibernate.jdbc.Work;
+
+/**
+ * @author Zemian Deng
+ */
+public class HbmSessionTemplate {	
+	private SessionFactory sessionFactory;
+	
+	public SessionFactory getSessionFactory() {
+		return sessionFactory;
+	}
+	
+	public void setSessionFactory(SessionFactory sessionFactory) {
+		this.sessionFactory = sessionFactory;
+	}
+	
+	public void close() {
+		sessionFactory.close();
+	}
+
+	@SuppressWarnings("unchecked")
+	public <T> T withSessionResult(SessionResultAction action) {
+		Session session = sessionFactory.openSession();
+		T result = null;
+		try {
+			session.getTransaction().begin();
+			result = (T) action.onSession(session);
+			session.getTransaction().commit();
+		} catch (RuntimeException e) {
+			if (session.getTransaction().isActive())
+				session.getTransaction().rollback();
+			throw e;
+		} finally {
+			session.close();
+		}
+		return result;
+	}
+
+	public void withSession(SessionAction action) {
+		Session session = sessionFactory.openSession();
+		try {
+			session.getTransaction().begin();
+			action.onSession(session);
+			session.getTransaction().commit();
+		} catch (RuntimeException e) {
+			if (session.getTransaction().isActive())
+				session.getTransaction().rollback();
+			throw e;
+		} finally {
+			session.close();
+		}
+	}
+	
+	public  <T> T withConnectionResult(final ConnectionResultAction action) {
+		Session session = sessionFactory.openSession();
+		try {
+			final List<T> resultList = new ArrayList<T>();
+			session.doWork(new Work() {							
+				@SuppressWarnings("unchecked")
+				@Override
+				public void execute(Connection connection) throws SQLException {
+					Object result = action.onConnection(connection);
+					resultList.add((T)result);
+				}
+			});
+			return resultList.get(0);
+		} finally {
+			session.close();
+		}
+	}
+
+	public static interface SessionAction {
+		public void onSession(Session session);
+	}
+	
+	public static interface SessionResultAction {
+		public Object onSession(Session session);
+	}
+	
+	public static interface ConnectionResultAction {
+		public Object onConnection(Connection connection) throws SQLException;
+	}
+}

timemachine-hibernate/src/main/java/timemachine/scheduler/hibernate/HibernateDataStore.java

+/*
+ * Copyright 2012 Zemian Deng
+ * 
+ * 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.
+ */
+package timemachine.scheduler.hibernate;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import org.hibernate.LockMode;
+import org.hibernate.LockOptions;
+import org.hibernate.ObjectNotFoundException;
+import org.hibernate.Query;
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.hibernate.cfg.Configuration;
+import org.hibernate.service.ServiceRegistry;
+import org.hibernate.service.ServiceRegistryBuilder;
+
+import timemachine.scheduler.ConfigPropsListener;
+import timemachine.scheduler.CoreServices;
+import timemachine.scheduler.CoreServicesListener;
+import timemachine.scheduler.Data;
+import timemachine.scheduler.JobDef;
+import timemachine.scheduler.Schedule;
+import timemachine.scheduler.SchedulerData;
+import timemachine.scheduler.SchedulerException;
+import timemachine.scheduler.SchedulerNode;
+import timemachine.scheduler.hibernate.HbmSessionTemplate.SessionAction;
+import timemachine.scheduler.hibernate.HbmSessionTemplate.SessionResultAction;
+import timemachine.scheduler.service.DataStore;
+import timemachine.scheduler.service.JobListenerNotifier;
+import timemachine.scheduler.support.AbstractService;
+import timemachine.scheduler.support.ClasspathURLStreamHandler;
+import timemachine.scheduler.support.Props;
+import timemachine.scheduler.support.ScheduleComparator;
+import timemachine.scheduler.support.Tuple;
+
+/**
+ * @author Zemian Deng
+ */
+public class HibernateDataStore extends AbstractService implements DataStore, ConfigPropsListener, CoreServicesListener {
+
+	// Fields
+	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	private String hibernateCfgXml;
+	private Props configProps;
+	private HbmSessionTemplate hbmSessionTemplate;
+	private JobListenerNotifier jobListenerNotifier;
+
+	// Getter and Setters
+	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	public String getHibernateCfgXml() {
+		return hibernateCfgXml;
+	}
+	public JobListenerNotifier getJobListenerNotifier() {
+		return jobListenerNotifier;
+	}
+	public HbmSessionTemplate getHbmSessionTemplate() {
+		return hbmSessionTemplate;
+	}		
+	public void setJobListenerNotifier(JobListenerNotifier jobListenerNotifier) {
+		this.jobListenerNotifier = jobListenerNotifier;
+	}
+	public void setHibernateCfgXml(String hibernateCfgXml) {
+		this.hibernateCfgXml = hibernateCfgXml;
+	}
+
+	// Service and Listener code
+	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	@Override
+	protected void initService() {
+		if (hibernateCfgXml == null)
+			hibernateCfgXml = configProps.getString("timemachine.scheduler.dataStore.hibernateDataStore.hibernate.cfg.xml", 
+					"classpath:///timemachine/scheduler/hibernate/hibernate.cfg.xml");
+		if (hbmSessionTemplate == null) {
+			// We load default values first, then override by any scheduler config props.
+			String defaultSchedulerConfig = "classpath:///timemachine/scheduler/hibernate/default-hibernate.properties";
+			Properties defHbmProps = extractHiberateProperties(new Props(ClasspathURLStreamHandler.createURL(defaultSchedulerConfig)));
+			Properties scheProps = extractHiberateProperties(configProps);
+			
+			Configuration configuration = new Configuration();
+			configuration.addProperties(defHbmProps);
+			configuration.addProperties(scheProps);
+			configuration.configure(ClasspathURLStreamHandler.createURL(hibernateCfgXml));
+			ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
+				.applySettings(configuration.getProperties())
+				.buildServiceRegistry();
+			SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);
+			hbmSessionTemplate = new HbmSessionTemplate();
+			hbmSessionTemplate.setSessionFactory(sessionFactory);
+		}
+	}
+	
+	private Properties extractHiberateProperties(Props configProps) {
+		Properties props = new Properties();
+		String groupKey = "timemachine.scheduler.dataStore.hibernateDataStore.";
+		int groupKeyLen = groupKey.length();
+		for (String key : configProps.getGroupKeys(groupKey)) {
+			String strippedKey = key.substring(groupKeyLen); // strip groupKey away
+			String val = configProps.getString(key);
+			props.put(strippedKey, val);
+		}
+		return props;
+	}
+
+	@Override
+	protected void destroyService() {
+		if (hbmSessionTemplate != null) {
+			// Issue#6: Exception is thrown upon CTRL+C when using H2Database
+			// Due to H2Database is used as default for our scheduler, we want to detect it, and ensure a proper shutdown
+			// is issued. User should always use ";DB_CLOSE_ON_EXIT=FALSE" for their conn.
+			String url = configProps.getString("timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.url", null);
+			if (url == null || url.trim().startsWith("jdbc:h2")) {
+				hbmSessionTemplate.withConnectionResult(new HbmSessionTemplate.ConnectionResultAction() {					
+					@Override
+					public Object onConnection(Connection connection) throws SQLException {
+						int result = connection.createStatement().executeUpdate("SHUTDOWN");
+						logger.debug("H2Database has been shutdown: {}", result);
+						return result;
+					}
+				});
+			}			
+			hbmSessionTemplate.close();
+		}
+	}
+	
+	@Override
+	public void onConfigProps(Props configProps) {
+		this.configProps = configProps;
+	}
+
+	@Override
+	public void onCoreServices(CoreServices coreServices) {
+		this.jobListenerNotifier = coreServices.getJobListenerNotifierService();
+	}
+	
+	// Private supporting operations
+	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	/**
+	 * This is how we can support multiple scheduler in the store. Assuming the main entity in baseQuery has the "e"
+	 * alias. If dataType is not scheduler related, then it will throw unknown type exception.
+	 */
+	private String appendSchedulerIdWhereClause(String baseQuery, Class<?> dataType) {
+		return appendSchedulerIdWhereClause(baseQuery, dataType, true);
+	}
+	private String appendSchedulerIdWhereClause(String baseQuery, Class<?> dataType, boolean filterDeletedSchedule) {
+		String query = baseQuery;
+		if (dataType.equals(JobDef.class)) {
+			query += " where e.schedulerId = :schedulerId";
+		} else if (dataType.equals(Schedule.class)) {
+			query += ", JobDef e2 where e.jobDefId = e2.id and e2.schedulerId = :schedulerId";
+			if (filterDeletedSchedule) {
+				query += " and e.deleted = false";
+			}
+		} else if (dataType.equals(SchedulerNode.class)) {
+			query += " where e.schedulerData.id = :schedulerId";
+		} else {
+			throw new SchedulerException("Unknow data type to append schedulerId with: " + dataType.getName());
+		}
+		return query;
+	}
+	
+	@SuppressWarnings("unchecked")
+	private <T extends Data> List<T> findByName(final Long schedulerId, final String searchName, final Class<T> dataType) {
+		return (List<T>)hbmSessionTemplate.withSessionResult(new SessionResultAction() {
+			@Override
+			public Object onSession(Session session) {
+				return findByName(session, schedulerId, searchName, dataType);
+			}
+		});
+	}
+	
+	@SuppressWarnings("unchecked")
+	private <T extends Data> List<T> findByName(Session session, Long schedulerId, String searchName, final Class<T> dataType) {
+		if (!searchName.startsWith("%")) {
+			searchName = "%" + searchName;
+		}
+		if (!searchName.endsWith("%")) {
+			searchName = searchName + "%";
+		}
+
+		final String searchNameParam = searchName;
+		List<?> result = null;
+		String query = "select e from " + dataType.getSimpleName() + " e";
+		if (dataType.equals(SchedulerData.class)) {
+			query += " where e.name like :name";
+			result = session.createQuery(query).setParameter("name", searchNameParam).list();
+		} else {
+			query = appendSchedulerIdWhereClause(query, dataType);
+			query += " and e.name like :name";
+			result = session.createQuery(query).setParameter("name", searchNameParam).setParameter("schedulerId", schedulerId).list();
+		}
+		return (List<T>)result;
+	}
+	
+	@SuppressWarnings("unchecked")
+	public <T extends Data> T getByName(final Long schedulerId, final String exactName, final Class<T> dataType) {
+		return (T)hbmSessionTemplate.withSessionResult(new SessionResultAction() {
+			@Override
+			public Object onSession(Session session) {
+				return getByName(session, schedulerId, exactName, dataType);
+			}
+		});
+	}
+	
+	@SuppressWarnings("unchecked")
+	public <T extends Data> T getByName(Session session, Long schedulerId, String exactName, Class<T> dataType) {
+		List<?> result = null;
+		String query = "select e from " + dataType.getSimpleName() + " e";
+		if (dataType.equals(SchedulerData.class)) {
+			query += " where e.name = :name";
+			result = session.createQuery(query).setParameter("name", exactName).list();
+		} else {
+			query = appendSchedulerIdWhereClause(query, dataType);
+			query += " and e.name = :name";
+			result = session.createQuery(query).setParameter("name", exactName).setParameter("schedulerId", schedulerId).list();
+		}
+		
+		if (result.size() == 0)
+			return null;
+		
+		return (T)result.get(0);
+	}
+	
+	@SuppressWarnings("unchecked")
+	private <T extends Data> T get(final Long id, final Class<? extends Data> dataType) {
+		return (T)hbmSessionTemplate.withSessionResult(new SessionResultAction() {
+			@Override
+			public Object onSession(Session session) {
+				return get(session, id, dataType);
+			}
+		});
+	}
+	@SuppressWarnings("unchecked")
+	private <T extends Data> T get(Session session, Long id, Class<? extends Data> dataType) {
+		try {
+			Data result = (Data)session.get(dataType, id);
+			if (result == null) 
+				throw new SchedulerException("Failed to get " + dataType.getSimpleName() + ": Id " + id + " not found.");
+			return (T)result;
+		} catch (RuntimeException e) {
+			throw new SchedulerException("Failed to get " + dataType.getSimpleName() + ": Id " + id + " not found.", e);
+		}
+	}
+	
+	@SuppressWarnings("unchecked")
+	private <T extends Data> T update(final Long schedulerId, final T data) {
+		try {
+			if (data instanceof Schedule) {
+				((Schedule)data).setLastModified(new Date());
+			}
+			return (T)hbmSessionTemplate.withSessionResult(new SessionResultAction() {	
+				@Override
+				public Object onSession(Session session) {
+					// Get a scheduler wide lock before operation.
+					LockOptions lockOptions = new LockOptions(LockMode.PESSIMISTIC_WRITE);
+					session.get(SchedulerData.class, schedulerId, lockOptions);
+					
+					return session.merge(data);
+				}
+			});
+		} catch (RuntimeException e) {
+			throw new SchedulerException("Unable to update data : " + data.getClass().getSimpleName() + 
+					", id=" + data.getId(), e);
+		}
+	}	
+	private void delete(final Long schedulerId, final Data data) {
+		hbmSessionTemplate.withSession(new SessionAction() {
+			@Override
+			public void onSession(Session session) {
+				// Get a scheduler wide lock before operation.
+				LockOptions lockOptions = new LockOptions(LockMode.PESSIMISTIC_WRITE);
+				session.get(SchedulerData.class, schedulerId, lockOptions);
+				
+				Data entity = (Data)session.load(data.getClass(), data.getId());
+				session.delete(entity);
+			}
+		});
+	}	
+	private boolean exists(final Long id, final Class<? extends Data> dataType) {
+		return (Boolean)hbmSessionTemplate.withSessionResult(new SessionResultAction() {
+			@Override
+			public Object onSession(Session session) {
+				List<?> list = session
+						.createQuery("select count(*) from " + dataType.getSimpleName() + " e where e.id= :id")
+						.setParameter("id", id).list();
+				Long count = (Long) list.get(0);
+				return count > 0;
+			}
+		});
+	}	
+	private void create(final Data data) {
+		try {
+			hbmSessionTemplate.withSession(new SessionAction() {
+				@Override
+				public void onSession(Session session) {
+					session.persist(data);
+				}
+			});
+		} catch (RuntimeException e) {
+			throw new SchedulerException("Unable to store new data : " + data.getClass().getSimpleName(), e);
+		}
+	}
+	private long getCount(final Long schedulerId, final Class<? extends Data> dataType) {
+		return (Long)hbmSessionTemplate.withSessionResult(new SessionResultAction() {
+			@Override
+			public Object onSession(Session session) {
+				Long count = null;
+				String query = "select count(*) from " + dataType.getSimpleName() + " e";
+				if (dataType.equals(SchedulerData.class)) {
+					count = (Long)session.createQuery(query).list().get(0);
+				} else {
+					query = appendSchedulerIdWhereClause("select count(*) from " + dataType.getSimpleName() + " e", dataType);
+					count = (Long)session.createQuery(query).setParameter("schedulerId", schedulerId).list().get(0);
+				}
+				return count;
+			}
+		});
+	}	
+	@SuppressWarnings("unchecked")
+	private <T extends Data> List<T> findAll(final Long schedulerId, final Class<T> dataType) {
+		return (List<T>)hbmSessionTemplate.withSessionResult(new SessionResultAction() {
+			@Override
+			public Object onSession(Session session) {
+				return findAll(session, schedulerId, dataType);
+			}
+		});
+	}
+	@SuppressWarnings("unchecked")
+	private <T extends Data> List<T> findAll(Session session, final Long schedulerId, final Class<T> dataType) {
+		List<?> result = null;
+		String query = "select e from " + dataType.getSimpleName() + " e";
+		if (dataType.equals(SchedulerData.class)) {
+			result = session.createQuery(query).list();
+		} else {
+			query = appendSchedulerIdWhereClause(query, dataType);
+			result = session.createQuery(query).setParameter("schedulerId", schedulerId).list();
+		}
+		return (List<T>)result;
+	}
+	
+	// Data CRUD operations
+	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+	@Override
+	public SchedulerNode getOrCreateSchedulerNode(final String nodeName, final String schedulerName) {
+		try {
+			return (SchedulerNode)hbmSessionTemplate.withSessionResult(new SessionResultAction() {
+				@Override
+				public Object onSession(Session session) {
+					SchedulerNode schedulerNode = null;
+					SchedulerData schedulerData = getByName(session, null, schedulerName, SchedulerData.class);
+					if (schedulerData == null) {
+						schedulerData = new SchedulerData();
+						schedulerData.setName(schedulerName);
+						schedulerNode = new SchedulerNode();
+						schedulerNode.setName(nodeName);
+						schedulerNode.setSchedulerData(schedulerData);
+						session.persist(schedulerNode);
+					} else {
+						schedulerNode = getByName(session, schedulerData.getId(), nodeName, SchedulerNode.class);						
+						if (schedulerNode == null) {
+							schedulerNode = new SchedulerNode();
+							schedulerNode.setName(nodeName);
+							schedulerNode.setSchedulerData(schedulerData);	
+							session.persist(schedulerNode);
+						}
+					}
+					
+					return schedulerNode;
+				}
+			});
+		} catch (RuntimeException e) {
+			throw new SchedulerException("Failed to get or create nodeName=" + nodeName + ", schedulerName=" + schedulerName, e);
+		}
+	}
+	@Override
+	synchronized public SchedulerData getSchedulerData(String name) {
+		return getByName(null, name, SchedulerData.class);
+	}
+	@Override
+	synchronized public SchedulerNode getSchedulerNode(Long id) {
+		SchedulerNode node = get(id, SchedulerNode.class);
+		if (node == null)
+			throw new SchedulerException("Failed to get SchedulerNode: Id " + id + " not found.");
+		return node;
+	}
+	@Override
+	public SchedulerNode updateSchedulerNode(SchedulerNode schedulerNode) {
+		if (schedulerNode.getId() == null)
+			throw new SchedulerException("Failed to update: missing Id value.");
+		return update(schedulerNode.getSchedulerData().getId(), schedulerNode);
+	}
+	@Override
+	public void deleteSchedulerData(final SchedulerData schedulerData) {
+		if (schedulerData.getId() == null)
+			throw new SchedulerException("Failed to delete: missing Id value.");
+
+		try {
+			hbmSessionTemplate.withSession(new SessionAction() {
+				@Override
+				public void onSession(Session session) {
+					// Try to retrieve the data first.
+					Data data = (Data)session.load(SchedulerData.class, schedulerData.getId());
+					
+					// Remove all JobDef (it should cascade to all Schedule's) that belongs to this scheduler.
+					List<JobDef> jobDefs = findAll(session, schedulerData.getId(), JobDef.class);
+					for (JobDef jobDef : jobDefs)
+						session.delete(jobDef);
+					
+					// Remove all scheduler nodes that belongs to this scheduler.
+					session.createQuery("delete from SchedulerNode e where e.schedulerData.id = :schedulerId")
+						.setParameter("schedulerId", schedulerData.getId()).executeUpdate();
+				
+					// Remove actual data
+					session.delete(data);
+				}
+			});		
+		} catch (ObjectNotFoundException e) {
+			throw new SchedulerException("Failed to delete SchedulerData: Id " + schedulerData.getId() + " not found.");
+		}
+	}
+	@Override
+	public void deleteSchedulerNode(SchedulerNode schedulerNode) {
+		if (schedulerNode.getId() == null)
+			throw new SchedulerException("Failed to delete: missing Id value.");
+		
+		try {
+			delete(schedulerNode.getSchedulerData().getId(), schedulerNode);
+		} catch (ObjectNotFoundException e) {
+			throw new SchedulerException("Failed to delete SchedulerNode: Id " + schedulerNode.getId() + " not found.");
+		}
+	}
+	@Override
+	public boolean existsSchedulerNode(final String nodeName, final String schedulerName) {
+		return (Boolean)hbmSessionTemplate.withSessionResult(new SessionResultAction() {
+			@Override
+			public Object onSession(Session session) {
+				List<?> list = session
+						.createQuery("select count(*) from SchedulerNode e where e.name = :nodeName and e.schedulerData.name = :schedulerName")
+						.setParameter("nodeName", nodeName)
+						.setParameter("schedulerName", schedulerName).list();
+				Long count = (Long) list.get(0);
+				return count > 0;
+			}
+		});
+	}
+	
+	@Override
+	public void createJobDef(JobDef jobDef) {
+		if (jobDef.getId() != null)
+			throw new SchedulerException("Failed to create: New JobDef must have id set to null.");
+		if (jobDef.getSchedulerId() == null)
+			throw new SchedulerException("Failed to create: New JobDef must have schedulerId set.");
+
+		// Need to initialized schedules first.
+		for (Schedule schedule : jobDef.getSchedules()) {
+			schedule.initOnce();
+		}
+		
+		create(jobDef);
+		logger.debug("New {} stored.", jobDef);
+		jobListenerNotifier.onJobDefAdded(jobDef);
+		
+		// Call notifier and update jobDefId
+		for (Schedule schedule : jobDef.getSchedules()) {
+			schedule.setJobDefId(jobDef.getId());
+			logger.debug("New {} created. jobDefId={}, nextRun={}, desc={}",
+					new Object[] { schedule, schedule.getJobDefId(), schedule.getNextRun(), schedule.getDesc() });
+			jobListenerNotifier.onScheduleAdded(jobDef, schedule);
+		}
+	}
+	@Override
+	public JobDef getJobDef(Long schedulerId, Long id) {
+		JobDef jobDef = get(id, JobDef.class);
+		if (jobDef == null)
+			throw new SchedulerException("Failed to get JobDef: Id " + id + " not found.");
+		return jobDef;
+	}
+	@Override
+	public JobDef getJobDef(Long schedulerId, String name) {
+		return getByName(schedulerId, name, JobDef.class);
+	}
+	@Override
+	public JobDef updateJobDef(JobDef jobDef) {
+		if (jobDef.getId() == null)
+			throw new SchedulerException("Failed to update JobDef: missing Id value.");
+		for (Schedule schedule: jobDef.getSchedules()) {
+			if (!schedule.isInitOnceDone())
+				schedule.initOnce();
+		}
+		return update(jobDef.getSchedulerId(), jobDef);
+	}
+	@Override
+	public void deleteJobDef(JobDef jobDef) {
+		if (jobDef.getId() == null)
+			throw new SchedulerException("Failed to delete JobDef: missing Id value.");
+		
+		try {
+			delete(jobDef.getSchedulerId(), jobDef);
+		} catch (ObjectNotFoundException e) {
+			throw new SchedulerException("Failed to delete JobDef: Id " + jobDef.getId() + " not found.");
+		}
+	}
+	@Override
+	public boolean existsJobDef(Long schedulerId, Long id) {
+		return exists(id, JobDef.class);
+	}
+	@Override
+	public long getJobDefCount(Long schedulerId) {
+		return getCount(schedulerId, JobDef.class);
+	}
+
+	@Override
+	public Schedule getSchedule(Long schedulerId, Long id) {
+		Schedule schedule = get(id, Schedule.class);
+		if (schedule == null)
+			throw new SchedulerException("Failed to get Schedule: Id " + id + " not found.");
+		return schedule;
+	}
+	@Override
+	public Schedule getSchedule(Long schedulerId, String name) {
+		return getByName(schedulerId, name, Schedule.class);
+	}
+	@Override
+	public Schedule updateSchedule(Long schedulerId, Schedule schedule) {
+		if (schedule.getId() == null)
+			throw new SchedulerException("Failed to update Schedule: missing Id value.");
+		return update(schedulerId, schedule);
+	}
+	@Override
+	public void deleteSchedule(Long schedulerId, Schedule schedule) {
+		if (schedule.getId() == null)
+			throw new SchedulerException("Failed to delete Schedule: missing Id value.");
+		
+		try {
+			delete(schedulerId, schedule);
+		} catch (ObjectNotFoundException e) {
+			throw new SchedulerException("Failed to delete Schedule: Id " + schedule.getId() + " not found.");
+		}
+	}
+	@Override
+	public boolean existsSchedule(Long schedulerId, Long id) {
+		return exists(id, Schedule.class);
+	}
+	@Override
+	public long getScheduleCount(Long schedulerId) {
+		return getCount(schedulerId, Schedule.class);
+	}
+	
+	// Search operations
+	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	@Override
+	public List<SchedulerData> findSchedulerDataList() {
+		return findAll(null, SchedulerData.class);
+	}
+	@Override
+	public List<SchedulerNode> findSchedulerNodes(Long schedulerId) {
+		return findAll(schedulerId, SchedulerNode.class);
+	}
+	@Override
+	public List<JobDef> findJobDefs(Long schedulerId) {
+		return findAll(schedulerId, JobDef.class);
+	}
+	@Override
+	public List<JobDef> findJobDefs(Long schedulerId, String searchName) {
+		return findByName(schedulerId, searchName, JobDef.class);
+	}
+	@Override
+	public List<Schedule> findSchedules(Long schedulerId) {
+		return findAll(schedulerId, Schedule.class);
+	}
+	@Override
+	public List<Schedule> findSchedules(Long schedulerId, String searchName) {
+		return findByName(schedulerId, searchName, Schedule.class);
+	}
+	@Override
+	public List<Schedule> findSchedules(final Long schedulerId, final Schedule.State state) {
+		return hbmSessionTemplate.withSessionResult(new SessionResultAction() {
+			@Override
+			public Object onSession(Session session) {
+				String query = appendSchedulerIdWhereClause("select e from Schedule e", Schedule.class);
+				query += " and e.state = :state order by e.nextRun asc";
+				List<?> list = session.createQuery(query)
+						.setParameter("schedulerId", schedulerId).setParameter("state", state).list();
+				return list;
+			}
+		});
+	}
+	
+	// Scheduler operations
+	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	@Override
+	public List<Schedule> findAndStageSchedulesToRun(final Long schedulerId, final int maxCount, final Date fromTime, final Date toTime) {
+		final List<Schedule> processed = new ArrayList<Schedule>(maxCount);
+		hbmSessionTemplate.withSession(new SessionAction() {	
+			@Override
+			public void onSession(Session session) {
+				// Get a scheduler wide lock before operation.
+				LockOptions lockOptions = new LockOptions(LockMode.PESSIMISTIC_WRITE);
+				session.get(SchedulerData.class, schedulerId, lockOptions);
+								
+				// Now query for next run Schedules.
+				logger.debug("findSchedulesToRun: maxCount={}, fromTime={}, toTime={}", new Object[] { maxCount, fromTime, toTime });
+				List<Schedule> nextRunSchedules = new ArrayList<Schedule>(maxCount);
+				
+				String query = appendSchedulerIdWhereClause("select e from Schedule e", Schedule.class);
+				query += " and e.state = :state and (e.nextRun between :fromTime and :toTime) order by e.nextRun asc";
+				Query selectScheduleQuery = session
+						.createQuery(query)
+						.setParameter("schedulerId", schedulerId)
+						.setParameter("state", Schedule.State.WAITING)
+						.setParameter("fromTime", fromTime)
+						.setParameter("toTime", toTime).setMaxResults(maxCount);
+				while (nextRunSchedules.size() < maxCount) {
+					List<?> list = selectScheduleQuery.list();
+
+					// Stop the loop if we no longer have schedule to run
+					if (list.size() == 0)
+						break;
+
+					for (Object obj : list) {
+						Schedule schedule = (Schedule) obj;
+						// Delete Schedule if it already reach the end.
+						Date nextRun = schedule.getNextRun();
+						if (nextRun == null) {
+							logger.debug("Schedule {} is done and will be removed. There is no more nextRun.", schedule);
+							session.delete(schedule);
+							continue;
+						}
+						nextRunSchedules.add(schedule);
+					}
+				}
+				
+				// Sort the nextRunSchedules before continue
+				if (nextRunSchedules.size() > 0)
+					Collections.sort(nextRunSchedules, new ScheduleComparator());
+				
+				// Process schedule to be run and move state to STAGING so other may not run it at the same time.
+				for (Schedule schedule : nextRunSchedules) {
+					if (schedule.getState() == Schedule.State.WAITING) {
+						// hibernate should auto update this upon end of this method/tx.
+						schedule.setState(Schedule.State.STAGING);
+						processed.add(schedule);
+					}
+				}				
+			}
+		});
+
+		// Return those staged schedules
+		return processed;
+	}
+	@Override
+	public void updateMissedRunSchedules(final Long schedulerId, final int maxCount, final Date fromTime) {
+		hbmSessionTemplate.withSession(new SessionAction() {
+			@Override
+			public void onSession(Session session) {
+				// Get a scheduler wide lock before operation.
+				LockOptions lockOptions = new LockOptions(LockMode.PESSIMISTIC_WRITE);
+				session.get(SchedulerData.class, schedulerId, lockOptions);
+
+				// Select the missed run schedules
+				List<Schedule> missedRunSchedules = new ArrayList<Schedule>(maxCount);
+				String query = appendSchedulerIdWhereClause("select e from Schedule e", Schedule.class);
+				query += " and e.state = :state and e.nextRun <= :fromTime order by e.nextRun asc";
+				Query selectScheduleQuery = session
+						.createQuery(query)
+						.setParameter("schedulerId", schedulerId)
+						.setParameter("state", Schedule.State.WAITING)
+						.setParameter("fromTime", fromTime)
+						.setMaxResults(maxCount);
+
+				while (missedRunSchedules.size() < maxCount) {
+					List<?> list = selectScheduleQuery.list();
+
+					// Stop the loop if we no longer have schedule to run
+					if (list.size() == 0)
+						break;
+
+					// Else try to update it's state to STAGING so other may not run it at the same time.
+					for (Object obj : list) {
+						Schedule schedule = (Schedule) obj;
+
+						// Delete Schedule if it already reach the end.
+						Date nextRun = schedule.getNextRun();
+						if (nextRun == null) {
+							continue;
+						}
+
+						missedRunSchedules.add(schedule);
+					}
+				}
+
+				// Process the missed run schedules and call its onMissedRun().
+				final Map<Long, JobDef> processedJobDefs = new HashMap<Long, JobDef>();
+				for (Schedule schedule : missedRunSchedules) {
+					Date missedRun = schedule.getNextRun();
+					schedule.onMissedRun();
+					Long jobDefId = schedule.getJobDefId();
+					JobDef jobDef = processedJobDefs.get(jobDefId); 
+					if (jobDef == null) {
+						// retrieve and save it so not to re query if already found.
+						jobDef = (JobDef)session.get(JobDef.class, jobDefId);
+						processedJobDefs.put(jobDefId, jobDef);
+					}
+					jobListenerNotifier.onJobMissedRun(jobDef, schedule);
+					logger.debug("{} missed run: {}, nextRun: {}", new Object[] { schedule, missedRun, schedule.getNextRun() });
+				}
+			}
+		});
+	}
+	@Override
+	public Date findEarliestScheduleRunTime(final Long schedulerId) {
+		return hbmSessionTemplate.withSessionResult(new SessionResultAction() {
+			@Override
+			public Object onSession(Session session) {
+				String query = appendSchedulerIdWhereClause("select e from Schedule e", Schedule.class, false);
+				query += " and e.deleted = :deleted and e.state = :state order by e.nextRun";
+				List<?> list = session.createQuery(query)
+						.setParameter("schedulerId", schedulerId)
+						.setParameter("deleted", false)
+						.setParameter("state", Schedule.State.WAITING)
+						.setMaxResults(1).list();
+				if (list.size() == 0)
+					return new Date(Long.MAX_VALUE);
+
+				Schedule schedule = (Schedule) list.get(0);
+				Date result = schedule.getNextRun();
+				if (result == null)
+					return new Date(Long.MAX_VALUE);
+				return result;
+			}
+		});
+	}
+	@Override
+	public Tuple<Schedule, JobDef> getScheduleWithJobDef(final Long schedulerId, final Long scheduleId, final Long jobDefId) {
+		return hbmSessionTemplate.withSessionResult(new SessionResultAction() {
+			@Override
+			public Object onSession(Session session) {
+				JobDef jobDef = null;
+				Schedule schedule = null;
+				try {
+					jobDef = get(session, jobDefId, JobDef.class);
+					schedule = get(session, scheduleId, Schedule.class);
+				} catch (SchedulerException e) {
+					// One of data must have been deleted. This method will purposely allow this and return null.
+					// So do nothing here.
+				}
+				return new Tuple<Schedule, JobDef>(schedule, jobDef);
+			}
+		});
+	}
+	@Override
+	public void deleteDeadSchedules(final Long schedulerId, final int maxCount) {
+		hbmSessionTemplate.withSession(new SessionAction() {
+			@Override
+			public void onSession(Session session) {
+				// Get a scheduler wide lock before operation.
+				LockOptions lockOptions = new LockOptions(LockMode.PESSIMISTIC_WRITE);
+				session.get(SchedulerData.class, schedulerId, lockOptions);
+				
+				logger.debug("Delete dead schedules.");
+				// TODO: Can we improve this DELETE query? the sub-select might cause performance issue.
+				// Remove any dead Schedule
+				String query = "delete Schedule e where e.deleted = :deleted and e.jobDefId in (from JobDef e2 where e2.schedulerId = :schedulerId) order by e.nextRun asc";
+				int removedCount = session.createQuery(query)
+						.setParameter("schedulerId", schedulerId)
+						.setParameter("deleted", true)
+						.setMaxResults(maxCount)
+						.executeUpdate();
+				logger.debug("Removed {} Schedules that were marked for deletion.", removedCount);
+				
+			}
+		});
+	}
+}

timemachine-hibernate/src/main/resources/timemachine/scheduler/hibernate/JobDef.hbm.xml

+<?xml version="1.0"?>
+<!-- 
+ * Copyright 2012 Zemian Deng
+ *