Commits

Zemian Deng  committed 1f7e6c3 Merge

Merged latest default into stable branch.

  • Participants
  • Parent commits 83b18a4, 432e815
  • Branches stable

Comments (0)

Files changed (98)

+target
+.settings
+.project
+.classpath
+.DS_Store
-See documentations on https://bitbucket.org/timemachine/scheduler/wiki/Home
+TimeMachine is an open source Java scheduler that can run high volume of jobs with many different type of 
+schedules. It supports repeating schedule on fixed interval, CRON based expression, or even custom 
+schedules. The scheduler can manage job executions with thread pools, and it can persist job data into 
+different storage. Users may run the scheduler as stand-alone server, or deploy as web application. The 
+scheduler engine is using a stack-based service container that is easy to configure and extend by developers.
+
+Main Features:
+
+* A stand-alone scheduler server with simple Properties configuration file.
+* A web application (war) to run and manage the scheduler.
+* Support job implementation in either Java or scripting language (Eg: Groovy)
+* Support multiple thread pools for isolated jobs execution.
+* Support in-memory or database data store.
+* Support scheduler clustering - multiple scheduler nodes with single logical scheduler.
+* Support job history recording.
+* Built-in JobTask: LoggerJobTask, ScriptingJobTask and OsCommandJobTask.
+* Built-in Schedule: RepeatSchedule, CronSchedule and DateListSchedule.
+* Built-in Service: Crontab that's similar to Linux/Unix OS crontab service.
+* Built-in Service: JobLoader to load any job and schedule with simple properties file.
+
+See documentations on https://bitbucket.org/timemachine/scheduler/wiki/Home
 	<modules>
 		<module>timemachine-scheduler</module>
 		<module>timemachine-hibernate</module>
+		<module>timemachine-web</module>
 		<module>timemachine-dist</module>
 	</modules>
 

File scripts/root-war.sh

+#!/usr/bin/env bash
+#
+# Build ROOT.war for openshift JBoss Application's deployment that contains links to other web applications.
+#
+
+SCRIPT_DIR=$(dirname $0)
+PROJ=$1
+if [[ "$PROJ" == "" ]]; then
+	PROJ=$SCRIPT_DIR/target
+fi
+PROJ=$PROJ/ROOT-war
+
+mkdir -p $PROJ/WEB-INF
+echo '<?xml version="1.0"?>
+<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	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>
+<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</h1>
+<ul>
+<li><a href="http://tmschedulerdemo-zdeng.rhcloud.com/scheduler-demo">Scheduler Demo</a></li>
+<li><a href="http://tmschedulersite-zdeng.rhcloud.com/scheduler-site">Scheduler Maven Site Reports</a></li>
+</ul>
+</body>
+</html> 
+' > $PROJ/index.html
+
+if [[ -e $PROJ/../ROOT.war ]]; then
+	rm -vf $PROJ/../ROOT.war
+fi
+jar -cvf $PROJ/../ROOT.war -C $PROJ .
+rm -rf $PROJ
+
+echo "$PROJ/../ROOT.war is created."

File scripts/scheduler-site.sh

+#!/usr/bin/env bash
+#
+# Build scheduler-site.war from the scheduler project after 'mvn site site:deploy' is called.
+#
+
+PROJ=$1
+if [[ "$PROJ" == "" ]]; then
+	PROJ=$HOME/projects/scheduler/target/checkout/scheduler
+fi
+SCRIPT_DIR=$(cd $(dirname $0) && pwd)
+DEST=$PROJ/target/scheduler-site
+
+if [[ -e $DEST ]]; then
+	rm -rf $DEST
+fi
+
+mkdir -p $DEST/WEB-INF
+echo '<?xml version="1.0"?>
+<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
+</web-app>' > $DEST/WEB-INF/web.xml
+
+mkdir -p $DEST/target
+cp -rf $PROJ/target/site-deploy $DEST/target
+mkdir -p $DEST/timemachine-scheduler/target/site-deploy
+cp -rf $PROJ/timemachine-scheduler/target/site-deploy/timemachine-scheduler $DEST/timemachine-scheduler/target/site-deploy
+mkdir -p $DEST/timemachine-hibernate/target/site-deploy
+cp -rf $PROJ/timemachine-hibernate/target/site-deploy/timemachine-hibernate $DEST/timemachine-hibernate/target/site-deploy
+mkdir -p $DEST/timemachine-dist/target/site-deploy
+cp -rf $PROJ/timemachine-dist/target/site-deploy/timemachine-dist $DEST/timemachine-dist/target/site-deploy
+
+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="target/site-deploy/index.html">Main Index</a></li>
+</ul>
+</body>
+</html> 
+' > $DEST/index.html
+
+if [[ -e $DEST/../scheduler-site.war ]]; then
+	rm -rf $DEST/../scheduler-site.war
+fi
+jar -cf $DEST/../scheduler-site.war -C $DEST .
+
+echo "$DEST/../scheduler-site.war created."
+

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

 			<includes>
 				<include>org.bitbucket.timemachine:timemachine-scheduler</include>
 				<include>org.bitbucket.timemachine:timemachine-hibernate</include>
+				<include>org.bitbucket.timemachine:timemachine-web</include>
+				<include>org.bitbucket.timemachine:timemachine-dist</include>
 			</includes>
 			<sources>
 				<fileSets>
 				</dependencySets>
 			</binaries>
 		</moduleSet>
+		<!-- To avoid duplicated files and avoid large download size, we manually made war and zip separated.
+		<moduleSet>
+			<useAllReactorProjects>true</useAllReactorProjects>
+			<includes>
+				<include>org.bitbucket.timemachine:timemachine-web</include>
+			</includes>
+			<binaries>
+				<outputDirectory>./</outputDirectory>
+				<unpack>false</unpack>
+			</binaries>
+		</moduleSet>
+		-->
 	</moduleSets>
 </assembly>

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

 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.maxIdleTime=300000
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.idleConnectionTestPeriod=250000
 timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.preferredTestQuery=SELECT 1

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

 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.maxIdleTime=300000
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.idleConnectionTestPeriod=250000
 timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.preferredTestQuery=SELECT 1

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

 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.maxIdleTime=300000
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.idleConnectionTestPeriod=250000
 timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.preferredTestQuery=SELECT 1

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

 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.maxIdleTime=300000
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.idleConnectionTestPeriod=250000
 timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.preferredTestQuery=SELECT 1 FROM DUAL

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

 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.maxIdleTime=300000
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.idleConnectionTestPeriod=250000
 timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.preferredTestQuery=SELECT 1 FROM DUAL

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

 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.maxIdleTime=300000
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.idleConnectionTestPeriod=250000
 timemachine.scheduler.dataStore.hibernateDataStore.hibernate.c3p0.preferredTestQuery=SELECT 1 FROM DUAL

File timemachine-hibernate/pom.xml

 			<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>

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

 import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import timemachine.scheduler.CoreServices;
 import timemachine.scheduler.CoreServicesListener;
 import timemachine.scheduler.Data;
+import timemachine.scheduler.EventHistory;
 import timemachine.scheduler.JobDef;
 import timemachine.scheduler.Schedule;
 import timemachine.scheduler.SchedulerData;
 	private Props configProps;
 	private HbmSessionTemplate hbmSessionTemplate;
 	private JobListenerNotifier jobListenerNotifier;
-
+	private Comparator<Schedule> scheduleComparator = new ScheduleComparator();
+	
 	// Getter and Setters
 	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 	public String getHibernateCfgXml() {
 		} else if (dataType.equals(Schedule.class)) {
 			query += ", JobDef e2 where e.jobDefId = e2.id and e2.schedulerId = :schedulerId";
 			if (nonBlockingSchedules) {
-				query += " and e.deleted = false and e.paused = false";
+				query += " and e.deleted = false";
 			}
 		} else if (dataType.equals(SchedulerNode.class)) {
 			query += " where e.schedulerData.id = :schedulerId";
 	public void deleteJobDef(JobDef jobDef) {
 		if (jobDef.getId() == null)
 			throw new SchedulerException("Failed to delete JobDef: missing Id value.");
+
+		// Send job listener first: Call job listener notifier on all Schedule to be remove
+		for (Schedule schedule : jobDef.getSchedules())
+			jobListenerNotifier.onScheduleDeleted(jobDef, schedule);
+		jobListenerNotifier.onJobDefDeleted(jobDef);
 		
+		// Now do the delete
 		try {
 			delete(jobDef.getSchedulerId(), jobDef);
 		} catch (ObjectNotFoundException e) {
 		if (schedule.getId() == null)
 			throw new SchedulerException("Failed to delete Schedule: missing Id value.");
 		
+		// Send job listener first
+		JobDef jobDef = getJobDef(schedulerId, schedule.getJobDefId());
+		jobListenerNotifier.onScheduleDeleted(jobDef, schedule);
+		
+		// Now do the delete
 		try {
 			delete(schedulerId, schedule);
 		} catch (ObjectNotFoundException e) {
 				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 += " and e.paused = :paused and e.state = :state and (e.nextRun between :fromTime and :toTime) " + 
+						"order by e.nextRun asc, e.priority asc, e.lastModified asc";
 				Query selectScheduleQuery = session
 						.createQuery(query)
 						.setParameter("schedulerId", schedulerId)
+						.setParameter("paused", false)
 						.setParameter("state", Schedule.State.WAITING)
 						.setParameter("fromTime", fromTime)
 						.setParameter("toTime", toTime).setMaxResults(maxCount);
 				
 				// Sort the nextRunSchedules before continue
 				if (nextRunSchedules.size() > 0)
-					Collections.sort(nextRunSchedules, new ScheduleComparator());
+					Collections.sort(nextRunSchedules, 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) {
 				// 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 += " and e.paused=:paused and e.state = :state and e.nextRun <= :fromTime order by e.nextRun asc";
 				Query selectScheduleQuery = session
 						.createQuery(query)
 						.setParameter("schedulerId", schedulerId)
+						.setParameter("paused", false)
 						.setParameter("state", Schedule.State.WAITING)
 						.setParameter("fromTime", fromTime)
 						.setMaxResults(maxCount);
 			@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";
+				query += " and paused = :paused and e.state = :state order by e.nextRun";
 				List<?> list = session.createQuery(query)
 						.setParameter("schedulerId", schedulerId)
+						.setParameter("paused", false)
 						.setParameter("state", Schedule.State.WAITING)
 						.setMaxResults(1).list();
 				if (list.size() == 0)
 			}
 		});
 	}
+	/** Set maxCount < 0 to delete all dead schedules! */
 	@Override
 	public void deleteDeadSchedules(final Long schedulerId, final int maxCount) {
 		hbmSessionTemplate.withSession(new SessionAction() {
 				// 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)
+				Query hbmQuery = session.createQuery(query)
 						.setParameter("schedulerId", schedulerId)
-						.setParameter("deleted", true)
-						.setMaxResults(maxCount)
-						.executeUpdate();
+						.setParameter("deleted", true);
+				if (maxCount > 0)
+					hbmQuery.setMaxResults(maxCount);
+				int removedCount = hbmQuery.executeUpdate();
 				logger.debug("Removed {} Schedules that were marked for deletion.", removedCount);
 				
 			}
 		});
 	}
+	
+	// Event History
+	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+	@Override
+	public void createEventHistory(EventHistory eventHistory) {
+		create(eventHistory);
+	}
+	@Override
+	public EventHistory getEventHistory(Long schedulerNodeId, Long eventHistoryId) {
+		return get(eventHistoryId, EventHistory.class);
+	}
+	@Override
+	public List<EventHistory> findEventHistories(final Long schedulerNodeId) {
+		return hbmSessionTemplate.withSessionResult(new SessionResultAction() {
+			@Override
+			public Object onSession(Session session) {
+				String query = "select e from EventHistory e where e.schedulerNodeId = :schedulerNodeId " + 
+						" order by e.createTime desc, e.id desc, e.type asc, e.name asc";
+				List<?> list = session.createQuery(query)
+						.setParameter("schedulerNodeId", schedulerNodeId).list();
+				return list;
+			}
+		});
+	}
+	@Override
+	public List<EventHistory> findEventHistories(final Long schedulerNodeId, final Date fromTime, final Date toTime) {
+		return hbmSessionTemplate.withSessionResult(new SessionResultAction() {
+			@Override
+			public Object onSession(Session session) {
+				String query = "select e from EventHistory e where e.schedulerNodeId = :schedulerNodeId " + 
+						" and e.createTime between :fromTime and :toTime " +
+						" order by e.createTime desc, e.id desc, e.type asc, e.name asc";
+				List<?> list = session.createQuery(query)
+						.setParameter("schedulerNodeId", schedulerNodeId)
+						.setParameter("fromTime", fromTime)
+						.setParameter("toTime", toTime).list();
+				return list;
+			}
+		});
+	}
+	@Override
+	public void deleteEventHistories(final Long schedulerNodeId, final Date olderThanTime) {
+		hbmSessionTemplate.withSession(new SessionAction() {
+			@Override
+			public void onSession(Session session) {
+				String query = "delete EventHistory e where e.schedulerNodeId = :schedulerNodeId " + 
+						" and e.createTime < :olderThanTime";
+				int result = session.createQuery(query)
+						.setParameter("schedulerNodeId", schedulerNodeId)
+						.setParameter("olderThanTime", olderThanTime).executeUpdate();
+				logger.debug("Removed {} EventHistory records with olderThanTime={}.", result, olderThanTime);
+			}
+		});
+	}
 }

File timemachine-hibernate/src/main/resources/timemachine/scheduler/hibernate/EventHistory.hbm.xml

+<?xml version="1.0"?>
+<!-- 
+ * 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.
+-->
+<!DOCTYPE hibernate-mapping PUBLIC
+        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
+
+<hibernate-mapping>
+
+	<class name="timemachine.scheduler.EventHistory" table="EVENT_HISTORY">
+		<id name="id" column="ID" type="long">
+			<generator class="native" />
+		</id>
+		<property name="schedulerNodeId" type="long" column="SCHEDULER_NODE_ID" access="field" index="EVENT_HISTORY_SCHEDULER_NODE_ID"/>
+		<property name="type" type="string" column="TYPE" access="field" index="EVENT_HISTORY_TYPE" length="511"/>
+		<property name="name" type="string" column="NAME" access="field" index="EVENT_HISTORY_NAME" length="255"/>
+		<property name="createTime" type="timestamp" column="CREATE_TIME" access="field" index="EVENT_HISTORY_CREATE_TIME"/>
+		<property name="info1" type="string" column="INFO1" access="field" length="1023" not-null="false"/>
+		<property name="info2" type="string" column="INFO2" access="field" length="1023" not-null="false"/>
+		<property name="info3" type="string" column="INFO3" access="field" length="1023" not-null="false"/>
+		<property name="info4" type="string" column="INFO4" access="field" length="1023" not-null="false"/>
+		<property name="info5" type="string" column="INFO5" access="field" length="1023" not-null="false"/>
+	</class>
+
+</hibernate-mapping>

File timemachine-hibernate/src/main/resources/timemachine/scheduler/hibernate/Schedule.hbm.xml

 		<property name="runCount" type="long" column="RUN_COUNT" access="field" />
 		<property name="missedRunCount" type="long" column="MISSED_RUN_COUNT" access="field" />
 		<property name="missedRunPolicy" type="int" column="MISSED_RUN_POLICY" access="field" />
+		<property name="priority" type="int" column="PRIORITY" access="field" />
 		<property name="endTime" type="timestamp" column="END_TIME" access="field" />
 		<property name="lastModified" type="timestamp" column="LAST_MODIFIED" access="field" />
 		<property name="nextRun" type="timestamp" column="NEXT_RUN" access="field" index="SCHEDULER_NEXT_RUN"/>

File timemachine-hibernate/src/main/resources/timemachine/scheduler/hibernate/hibernate.cfg.xml

         <mapping resource="timemachine/scheduler/hibernate/SchedulerNode.hbm.xml"/>
         <mapping resource="timemachine/scheduler/hibernate/Schedule.hbm.xml"/>
         <mapping resource="timemachine/scheduler/hibernate/JobDef.hbm.xml"/>
+        <mapping resource="timemachine/scheduler/hibernate/EventHistory.hbm.xml"/>
 
     </session-factory>
 

File timemachine-hibernate/src/test/java/integration/timemachine/scheduler/hibernate/HbmSessionTemplateTest.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 integration.timemachine.scheduler.hibernate;
+
+import java.util.List;
+import java.util.Properties;
+
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.hibernate.cfg.Configuration;
+import org.hibernate.service.ServiceRegistry;
+import org.hibernate.service.ServiceRegistryBuilder;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import timemachine.scheduler.hibernate.HbmSessionTemplate;
+import timemachine.scheduler.hibernate.HbmSessionTemplate.SessionAction;
+import timemachine.scheduler.support.ClasspathURLStreamHandler;
+
+/**
+ * @author Zemian Deng
+ */
+public class HbmSessionTemplateTest {
+	
+	private static Logger logger = LoggerFactory.getLogger(HbmSessionTemplateTest.class);
+	private HbmSessionTemplate hbmSessionTemplate;
+	public static final String HIBERNATE_CONFIG = "classpath:///timemachine/scheduler/hibernate/hibernate.cfg.xml";
+	
+	@Before
+	public void setup() {
+		Properties extraProperties = new Properties();
+		extraProperties.setProperty("hibernate.connection.driver_class", "org.h2.Driver");
+		extraProperties.setProperty("hibernate.connection.url", "jdbc:h2:mem:tmscheduler");
+		extraProperties.setProperty("hibernate.connection.username", "admin");
+		extraProperties.setProperty("hibernate.connection.password", "admin123");
+		extraProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
+		extraProperties.setProperty("hibernate.hbm2ddl.auto", "create");
+		Configuration configuration = new Configuration();
+		configuration.addProperties(extraProperties);
+		configuration.configure(ClasspathURLStreamHandler.createURL(HIBERNATE_CONFIG));
+		ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
+			.applySettings(configuration.getProperties())
+			.buildServiceRegistry();
+		SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);
+		hbmSessionTemplate = new HbmSessionTemplate();
+		hbmSessionTemplate.setSessionFactory(sessionFactory);
+	}
+	
+	@Test
+	public void testGetCount() throws Exception {
+		hbmSessionTemplate.withSession(new SessionAction() {
+			@Override
+			public void onSession(Session session) {
+				List<?> list = session.createQuery("select count(*) from JobDef").list();
+				logger.info("Select count(*) type: {}", list.get(0).getClass());
+				Long count = (Long)list.get(0);
+				logger.info("Count: {}", count);
+			}
+		});
+	}
+}

File timemachine-hibernate/src/test/java/integration/timemachine/scheduler/hibernate/HibernateDataStoreTest.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 integration.timemachine.scheduler.hibernate;
+
+import integration.timemachine.scheduler.service.DataStoreTestBase;
+import timemachine.scheduler.hibernate.HibernateDataStore;
+import timemachine.scheduler.service.DataStore;
+import timemachine.scheduler.service.JobListenerNotifier;
+import timemachine.scheduler.support.ClasspathURLStreamHandler;
+import timemachine.scheduler.support.Props;
+
+/**
+ * @author Zemian Deng
+ */
+public class HibernateDataStoreTest  extends DataStoreTestBase {
+	public static final String SCHEDULER_CONFIG = "classpath:///integration/timemachine/scheduler/hibernate/scheduler.properties";
+	
+	@Override
+	protected DataStore createDataStore() {
+		Props configProps = new Props(ClasspathURLStreamHandler.createURL(SCHEDULER_CONFIG));
+		JobListenerNotifier jobListenerNotifier = new JobListenerNotifier();
+		HibernateDataStore store = new HibernateDataStore();
+		store.onConfigProps(configProps);
+		store.setJobListenerNotifier(jobListenerNotifier);
+		store.init();
+		return store;
+	}
+	
+}

File timemachine-hibernate/src/test/java/integration/timemachine/scheduler/hibernate/HibernateSchedulerTest.java

+package integration.timemachine.scheduler.hibernate;
+
+import integration.timemachine.scheduler.SchedulerTestBase;
+import timemachine.scheduler.Scheduler;
+import timemachine.scheduler.SchedulerFactory;
+
+public class HibernateSchedulerTest extends SchedulerTestBase {
+	
+	@Override
+	protected Scheduler createScheduler() {
+		return new SchedulerFactory("classpath:///integration/timemachine/scheduler/hibernate/scheduler.properties").createScheduler();
+	}
+
+	@Override
+	protected Scheduler createScheduler(String config) {
+		return new SchedulerFactory(config).createScheduler();
+	}
+
+}

File timemachine-hibernate/src/test/java/integration/timemachine/scheduler/service/HibernateJobListenerServiceTest.java

+package integration.timemachine.scheduler.service;
+
+import integration.timemachine.scheduler.service.JobListenerServiceTestBase;
+
+public class HibernateJobListenerServiceTest extends JobListenerServiceTestBase {
+
+	@Override
+	public String getJobListenerConfig() {
+		return "classpath:///integration/timemachine/scheduler/service/JobListenerServiceTest.properties";
+	}
+
+	@Override
+	public String getOnJobJefDeletedConfig() {
+		return "classpath:///integration/timemachine/scheduler/service/JobListenerService3Test.properties";
+	}
+
+	@Override
+	public String getOnScheduleDeleted() {
+		return "classpath:///integration/timemachine/scheduler/service/JobListenerService3Test.properties";
+	}
+
+	@Override
+	public String getOnJobMissedRun() {
+		return "classpath:///integration/timemachine/scheduler/service/JobListenerService3Test.properties";
+	}
+
+	@Override
+	public String getOnJobRunException() {
+		return "classpath:///integration/timemachine/scheduler/service/JobListenerService2Test.properties";
+	}
+
+}

File timemachine-hibernate/src/test/java/timemachine/scheduler/hibernate/HbmSessionTemplateTest.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.util.List;
-import java.util.Properties;
-
-import org.hibernate.Session;
-import org.hibernate.SessionFactory;
-import org.hibernate.cfg.Configuration;
-import org.hibernate.service.ServiceRegistry;
-import org.hibernate.service.ServiceRegistryBuilder;
-import org.junit.Before;
-import org.junit.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import timemachine.scheduler.hibernate.HbmSessionTemplate.SessionAction;
-import timemachine.scheduler.support.ClasspathURLStreamHandler;
-
-/**
- * @author Zemian Deng
- */
-public class HbmSessionTemplateTest {
-	
-	private static Logger logger = LoggerFactory.getLogger(HbmSessionTemplateTest.class);
-	private HbmSessionTemplate hbmSessionTemplate;
-	public static final String HIBERNATE_CONFIG = "classpath:///timemachine/scheduler/hibernate/hibernate.cfg.xml";
-	
-	@Before
-	public void setup() {
-		Properties extraProperties = new Properties();
-		extraProperties.setProperty("hibernate.connection.driver_class", "org.h2.Driver");
-		extraProperties.setProperty("hibernate.connection.url", "jdbc:h2:mem:tmscheduler");
-		extraProperties.setProperty("hibernate.connection.username", "admin");
-		extraProperties.setProperty("hibernate.connection.password", "admin123");
-		extraProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
-		extraProperties.setProperty("hibernate.hbm2ddl.auto", "create");
-		Configuration configuration = new Configuration();
-		configuration.addProperties(extraProperties);
-		configuration.configure(ClasspathURLStreamHandler.createURL(HIBERNATE_CONFIG));
-		ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
-			.applySettings(configuration.getProperties())
-			.buildServiceRegistry();
-		SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);
-		hbmSessionTemplate = new HbmSessionTemplate();
-		hbmSessionTemplate.setSessionFactory(sessionFactory);
-	}
-	
-	@Test
-	public void testGetCount() throws Exception {
-		hbmSessionTemplate.withSession(new SessionAction() {
-			@Override
-			public void onSession(Session session) {
-				List<?> list = session.createQuery("select count(*) from JobDef").list();
-				logger.info("Select count(*) type: {}", list.get(0).getClass());
-				Long count = (Long)list.get(0);
-				logger.info("Count: {}", count);
-			}
-		});
-	}
-}

File timemachine-hibernate/src/test/java/timemachine/scheduler/hibernate/HibernateDataStoreTest.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 integration.timemachine.scheduler.service.DataStoreTestBase;
-import timemachine.scheduler.service.DataStore;
-import timemachine.scheduler.service.JobListenerNotifier;
-import timemachine.scheduler.support.ClasspathURLStreamHandler;
-import timemachine.scheduler.support.Props;
-
-/**
- * @author Zemian Deng
- */
-public class HibernateDataStoreTest  extends DataStoreTestBase {
-	public static final String SCHEDULER_CONFIG = "classpath:///timemachine/scheduler/hibernate/scheduler.properties";
-	
-	@Override
-	protected DataStore createDataStore() {
-		Props configProps = new Props(ClasspathURLStreamHandler.createURL(SCHEDULER_CONFIG));
-		JobListenerNotifier jobListenerNotifier = new JobListenerNotifier();
-		HibernateDataStore store = new HibernateDataStore();
-		store.onConfigProps(configProps);
-		store.setJobListenerNotifier(jobListenerNotifier);
-		store.init();
-		return store;
-	}
-	
-}

File timemachine-hibernate/src/test/resources/integration/timemachine/scheduler/SchedulerTest-eventhistory.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.
+
+# Override test config from timemachine-scheduler because we need to configure database
+timemachine.scheduler.eventHistory.class = timemachine.scheduler.service.EventHistoryService
+timemachine.scheduler.eventHistory.removeInterval = 3000
+
+# local db test config
+timemachine.scheduler.dataStore.class = timemachine.scheduler.hibernate.HibernateDataStore
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.cfg.xml = classpath:///timemachine/scheduler/hibernate/hibernate.cfg.xml
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.driver_class = org.h2.Driver
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.url = jdbc:h2:mem: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.H2Dialect
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.show_sql = true
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.hbm2ddl.auto = create

File timemachine-hibernate/src/test/resources/integration/timemachine/scheduler/SchedulerTest-eventhistory2.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.
+
+# Override test config from timemachine-scheduler because we need to configure database
+timemachine.scheduler.eventHistory.class = timemachine.scheduler.service.EventHistoryService
+timemachine.scheduler.eventHistory.removeInterval = 3000
+
+# test with filters enabled.
+timemachine.scheduler.eventHistory.filterTypes = SCHEDULER,DATA
+timemachine.scheduler.eventHistory.filterNames = jobRunAfter
+
+# local db test config
+timemachine.scheduler.dataStore.class = timemachine.scheduler.hibernate.HibernateDataStore
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.cfg.xml = classpath:///timemachine/scheduler/hibernate/hibernate.cfg.xml
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.driver_class = org.h2.Driver
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.url = jdbc:h2:mem: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.H2Dialect
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.show_sql = true
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.hbm2ddl.auto = create

File timemachine-hibernate/src/test/resources/integration/timemachine/scheduler/hibernate/scheduler-simple.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.dataStore.class = timemachine.scheduler.hibernate.HibernateDataStore
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.url = jdbc:h2:mem:tmscheduler
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.hbm2ddl.auto = create-drop

File timemachine-hibernate/src/test/resources/integration/timemachine/scheduler/hibernate/scheduler.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.
+
+# local db test config
+timemachine.scheduler.dataStore.class = timemachine.scheduler.hibernate.HibernateDataStore
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.cfg.xml = classpath:///timemachine/scheduler/hibernate/hibernate.cfg.xml
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.driver_class = org.h2.Driver
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.url = jdbc:h2:mem: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.H2Dialect
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.show_sql = true
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.hbm2ddl.auto = create

File timemachine-hibernate/src/test/resources/integration/timemachine/scheduler/service/JobListenerService2Test.properties

+
+timemachine.scheduler.userservice.jobLoader.class = timemachine.scheduler.userservice.JobLoaderService
+JobLoaderService.myJob.integration.timemachine.scheduler.service.JobListenerServiceTestBase$BadJob = RepeatSchedule{endCount=1}
+timemachine.scheduler.userservice.myJobListener.class = integration.timemachine.scheduler.service.JobListenerServiceTestBase$MyListener
+
+timemachine.scheduler.dataStore.class = timemachine.scheduler.hibernate.HibernateDataStore
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.cfg.xml = classpath:///timemachine/scheduler/hibernate/hibernate.cfg.xml
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.driver_class = org.h2.Driver
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.url = jdbc:h2:mem: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.H2Dialect
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.show_sql = true
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.hbm2ddl.auto = create

File timemachine-hibernate/src/test/resources/integration/timemachine/scheduler/service/JobListenerService3Test.properties

+
+timemachine.scheduler.userservice.myJobListener.class = integration.timemachine.scheduler.service.JobListenerServiceTestBase$MyListener
+
+timemachine.scheduler.dataStore.class = timemachine.scheduler.hibernate.HibernateDataStore
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.cfg.xml = classpath:///timemachine/scheduler/hibernate/hibernate.cfg.xml
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.driver_class = org.h2.Driver
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.url = jdbc:h2:mem: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.H2Dialect
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.show_sql = true
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.hbm2ddl.auto = create

File timemachine-hibernate/src/test/resources/integration/timemachine/scheduler/service/JobListenerServiceTest.properties

+
+timemachine.scheduler.userservice.jobLoader.class = timemachine.scheduler.userservice.JobLoaderService
+JobLoaderService.myJob.integration.timemachine.scheduler.service.JobListenerServiceTestBase$GoodJob = RepeatSchedule{endCount=1}
+timemachine.scheduler.userservice.myJobListener.class = integration.timemachine.scheduler.service.JobListenerServiceTestBase$MyListener
+
+timemachine.scheduler.dataStore.class = timemachine.scheduler.hibernate.HibernateDataStore
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.cfg.xml = classpath:///timemachine/scheduler/hibernate/hibernate.cfg.xml
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.driver_class = org.h2.Driver
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.url = jdbc:h2:mem: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.H2Dialect
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.show_sql = true
+timemachine.scheduler.dataStore.hibernateDataStore.hibernate.hbm2ddl.auto = create

File timemachine-hibernate/src/test/resources/timemachine/scheduler/hibernate/scheduler-simple.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.dataStore.class = timemachine.scheduler.hibernate.HibernateDataStore
-timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.url = jdbc:h2:mem:tmscheduler
-timemachine.scheduler.dataStore.hibernateDataStore.hibernate.hbm2ddl.auto = create-drop

File timemachine-hibernate/src/test/resources/timemachine/scheduler/hibernate/scheduler.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.dataStore.class = timemachine.scheduler.hibernate.HibernateDataStore
-timemachine.scheduler.dataStore.hibernateDataStore.hibernate.cfg.xml = classpath:///timemachine/scheduler/hibernate/hibernate.cfg.xml
-timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.driver_class = org.h2.Driver
-timemachine.scheduler.dataStore.hibernateDataStore.hibernate.connection.url = jdbc:h2:mem: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.H2Dialect
-timemachine.scheduler.dataStore.hibernateDataStore.hibernate.show_sql = true
-timemachine.scheduler.dataStore.hibernateDataStore.hibernate.hbm2ddl.auto = create

File timemachine-scheduler/config/crontab-windows.properties

 CrontabService.02 = 0/5 * * * * ?      | cmd /c echo "Heart beat."
 CrontabService.03 = 0 0/5 * * * ?      | cmd /c echo "Five minutes job."
 CrontabService.04 = 0 0 12 * JAN,JUN ? | cmd /c echo "We should clean up every 6 months."
-CrontabService.05 = 0 0 8 ? * 1-5      | cmd /c echo "Every workday at 8AM."
+CrontabService.05 = 0 0 8 ? * 1-5      | cmd /c echo "Every workday at 8AM."

File timemachine-scheduler/config/crontab.properties

 CrontabService.02 = 0/5 * * * * ?      | sh -c echo "Heart beat."
 CrontabService.03 = 0 0/5 * * * ?      | sh -c echo "Five minutes job."
 CrontabService.04 = 0 0 12 * JAN,JUN ? | sh -c echo "We should clean up every 6 months."
-CrontabService.05 = 0 0 8 ? * 1-5      | sh -c echo "Every workday at 8AM."
+CrontabService.05 = 0 0 8 ? * 1-5      | sh -c echo "Every workday at 8AM."

File timemachine-scheduler/config/event-history.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.dataStore.class = timemachine.scheduler.service.MemoryDataStore
+
+# Scheduler Event History Service
+timemachine.scheduler.eventHistory.class = timemachine.scheduler.service.EventHistoryService
+timemachine.scheduler.eventHistory.removeInterval = 60000
+
+# Load a sample job
+timemachine.scheduler.userservice.jobLoader.class = timemachine.scheduler.userservice.JobLoaderService
+JobLoaderService.00myJob.timemachine.scheduler.jobtask.LoggerJobTask = RepeatSchedule{interval=3|intervalUnit=SECOND}

File timemachine-scheduler/config/scheduler.properties

 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-timemachine.scheduler.name = SimpleScheduler
+timemachine.scheduler.schedulerName = TimeMachineScheduler
 timemachine.scheduler.jobTaskThreadPool.DEFAULT.maxSize = 8
-timemachine.scheduler.dataStore.class = timemachine.scheduler.service.MemoryDataStore
+timemachine.scheduler.dataStore.class = timemachine.scheduler.service.MemoryDataStore

File timemachine-scheduler/config/user-services.properties

 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-timemachine.scheduler.name = MyUserServiceScheduler
+timemachine.scheduler.schedulerName = MyUserServiceScheduler
 # Example of using custom services
 # timemachine.scheduler.userservice.<serviceName>.class = <yourServiceClass>
 #timemachine.scheduler.userservice.01MyFirst.class = timemachine.scheduler.example.MyFirstUserService

File timemachine-scheduler/pom.xml

 		<!-- We set groovy engine as optional run time. -->
 		<dependency>
 			<groupId>org.codehaus.groovy</groupId>
-			<artifactId>groovy</artifactId>
-			<version>1.8.6</version>
+			<artifactId>groovy-jsr223</artifactId>
+			<version>2.0.0</version>
 			<scope>runtime</scope>
 			<optional>true</optional>
 		</dependency>

File timemachine-scheduler/src/main/java/timemachine/scheduler/EventHistory.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;
+
+import java.util.Date;
+
+import timemachine.scheduler.support.AbstractData;
+
+/**
+ * A data model to capture scheduler event as history record.
+ * @author Zemian Deng
+ */
+public class EventHistory extends AbstractData {
+	// inherited id and name already.
+	private Long schedulerNodeId;
+	private String type;
+	private Date createTime;
+	private String info1;
+	private String info2;
+	private String info3;
+	private String info4;
+	private String info5;
+	
+	public Long getSchedulerNodeId() {
+		return schedulerNodeId;
+	}
+	public void setSchedulerNodeId(Long schedulerNodeId) {
+		this.schedulerNodeId = schedulerNodeId;
+	}
+	public String getType() {
+		return type;
+	}
+	public void setType(String type) {
+		this.type = type;
+	}
+	public Date getCreateTime() {
+		return createTime;
+	}
+	public void setCreateTime(Date createTime) {
+		this.createTime = createTime;
+	}
+	public String getInfo1() {
+		return info1;
+	}
+	public void setInfo1(String info1) {
+		this.info1 = info1;
+	}
+	public String getInfo2() {
+		return info2;
+	}
+	public void setInfo2(String info2) {
+		this.info2 = info2;
+	}
+	public String getInfo3() {
+		return info3;
+	}
+	public void setInfo3(String info3) {
+		this.info3 = info3;
+	}
+	public String getInfo4() {
+		return info4;
+	}
+	public void setInfo4(String info4) {
+		this.info4 = info4;
+	}
+	public String getInfo5() {
+		return info5;
+	}
+	public void setInfo5(String info5) {
+		this.info5 = info5;
+	}
+	
+	@Override
+	public String toString() {
+		return "EventHistory[id=" + id + ", createTime=" + createTime + ", type=" + type + ", name=" + name + "]";
+	}
+}

File timemachine-scheduler/src/main/java/timemachine/scheduler/Schedule.java

 import org.slf4j.LoggerFactory;
 
 import timemachine.scheduler.support.AbstractData;
+import timemachine.scheduler.support.ScheduleComparator;
 
 /**
  * A Schedule would tell scheduler when and how often to run a {@link JobTask} in its {@link JobDef} definition.
  * write your own {@link Schedule} implementation. Instead you will use one of our built-in ready to use
  * implementations. Many of these can be easily created by using the {@link Schedules} factory class.
  * @see Schedules
+ * <p>
+ * The Schedule supports priority value range from 1 - 9. The lower number will have more higher priority to be run
+ * first by scheduler. The default priority is 5. For example, when two schedules have same nextRun time, the one with
+ * priority 4 will get run before priority 5.
+ * 
+ * @see ScheduleComparator {@link Schedules}
  * @author Zemian Deng
  */
 public abstract class Schedule extends AbstractData implements Cloneable {
 	protected Logger logger = LoggerFactory.getLogger(getClass());
 
-	/** Schedule state that changed while scheduler runs the jobDef. Initial state is WAITING. */
+	/** Schedule state that changed while scheduler runs the jobDef. Initial state is WAITING. 
+	 * NOTE: the order of these enum ORD value are important, and it's used in ScheduleComparator for sorting!*/
 	public static enum State {
 		/** Schedule is ready and waiting nextRun datetime to arrive. */
 		WAITING, 
 	protected Long jobDefId;
 	protected boolean deleted;
 	protected boolean paused;
+	protected int priority = 5;
+	
+	public int getPriority() {
+		return priority;
+	}
 	
 	public boolean isPaused() {
 		return paused;
 		return this;
 	}
 	
+	public Schedule setPriority(int priority) {
+		this.priority = priority;
+		return this;
+	}
+	
+	
 	abstract public Date getNextRun(Date after);
 
 }

File timemachine-scheduler/src/main/java/timemachine/scheduler/Scheduler.java

 	 */
 	public void schedule(JobDef jobDef);
 
-	/** Remove a jobDef from schedulder and unschedule any associated Schedule it might have. */
+	/** Remove a jobDef from scheduler and unschedule any associated Schedule it might have. */
 	public void unschedule(JobDef jobDef);
 
 	/** Remove a single Schedule of a jobDef. */
 	public Service getUserService(String name);
 	
 	public List<String> getUserServiceNames();
+	
+	public CoreServices getCoreServices();
 
 	public SchedulerContext getSchedulerContext();
 

File timemachine-scheduler/src/main/java/timemachine/scheduler/SchedulerFactory.java

 import timemachine.scheduler.service.ClassLoaderService;
 import timemachine.scheduler.service.ConfigPropsService;
 import timemachine.scheduler.service.DataStore;
+import timemachine.scheduler.service.EventHistoryService;
 import timemachine.scheduler.service.JobListenerNotifier;
 import timemachine.scheduler.service.JobTaskFactory;
 import timemachine.scheduler.service.JobTaskPoolNameResolver;
 
 			DataStore dataStoreService = createSystemService("timemachine.scheduler.dataStore.class", configProps);
 			systemServiceContainer.addDataStoreService(dataStoreService);
+
+			SchedulerNodeService schedulerNodeService = createSchedulerNodeService(systemServiceContainer);
+			systemServiceContainer.addSchedulerNodeService(schedulerNodeService);
 			
 			Map<String, ThreadPool> jobTaskThreadPools = createJobTaskThreadPoolServices(configProps);
 			for (Map.Entry<String, ThreadPool> entry : jobTaskThreadPools.entrySet()) {
 					configProps);
 			systemServiceContainer.addScheduleRunnerService(scheduleRunnerService);
 
-			SchedulerNodeService schedulerNodeService = createSchedulerNodeService(systemServiceContainer);
-			systemServiceContainer.addSchedulerNodeService(schedulerNodeService);
-
+			// Add event history service if enable
+			if (configProps.containsKey("timemachine.scheduler.eventHistory.class")) {
+				EventHistoryService eventHistoryService = createSystemService("timemachine.scheduler.eventHistory.class",
+						configProps);
+				systemServiceContainer.addService(eventHistoryService);
+			}
+			
 			afterSystemServiceSetup(systemServiceContainer);
 
 			// Create user services

File timemachine-scheduler/src/main/java/timemachine/scheduler/jobtask/ScriptingJobTask.java

 package timemachine.scheduler.jobtask;
 
 import java.io.File;
-import java.io.FileReader;
-
-import javax.script.Bindings;
-import javax.script.ScriptEngine;
-import javax.script.ScriptEngineManager;
+import java.util.HashMap;
+import java.util.Map;
 
 import org.apache.commons.lang.StringUtils;
 import org.slf4j.Logger;
 import timemachine.scheduler.JobDef;
 import timemachine.scheduler.JobTask;
 import timemachine.scheduler.SchedulerException;
+import timemachine.scheduler.support.ScriptingUtils;
 import timemachine.scheduler.support.Utils;
 
 /**
 			// Extract jobDef data map to setup script engine.
 			String engineName = null;
 			String scriptText = null;
-			String filename = null;
+			String scriptFile = null;
 			String scriptType = null;
 			
 			engineName = jobDef.getStringProp(SCRIPT_ENGINE_NAME_KEY, DEFAULT_SCRIPT_ENGINE_NAME);
-			engineName = engineName.toLowerCase(); // it's more safe to use lowercase for lookup.
-			if (engineName.equals("jruby")) {
-				// If JRuby script engine, we need to use transient variable bindings so we do not need to prefix '$'
-				System.setProperty("org.jruby.embed.localvariable.behavior", "transient");
-			}
-						
 			if (jobDef.hasProp(SCRIPT_TEXT_KEY)) {
 				scriptText = jobDef.getStringProp(SCRIPT_TEXT_KEY);
 				scriptType = SCRIPT_TEXT_KEY;
 			} else if (jobDef.hasProp(SCRIPT_FILE_KEY)) {
-				filename = jobDef.getStringProp(SCRIPT_FILE_KEY);
+				scriptFile = jobDef.getStringProp(SCRIPT_FILE_KEY);
 				scriptType = SCRIPT_FILE_KEY;
 			} else {
 				throw new SchedulerException("Neither " + SCRIPT_TEXT_KEY + " nor " + SCRIPT_FILE_KEY
 			
 			logger.debug("Creating ScriptEngine {} to evaluate {}.", engineName, scriptType);
 
-			// Create a Java ScriptEngine
-			ScriptEngineManager factory = new ScriptEngineManager();
-			ScriptEngine scriptEngine = factory.getEngineByName(engineName);
-			if (scriptEngine == null) {
-				throw new SchedulerException("Failed to find ScriptEngine " + engineName);
-			}
-
-			// Script engine binding variables.
-			Bindings bindings = scriptEngine.createBindings();
+			// Create bindingVars
+			Map<String, Object> bindings = new HashMap<String, Object>();
 			bindings.put("jobContext", jobContext);
 			bindings.put("logger", logger);
 			
 
 				// Evaluate script text.
 				logger.trace("Evaluating scriptText: {}.", scriptText);
-				result = scriptEngine.eval(scriptText, bindings);
+				result = ScriptingUtils.runScriptText(scriptText, engineName, bindings);
 			} else {
 				// We only have two options, and this is the last case.
-				bindings.put("scriptFile", filename);
+				bindings.put("scriptFile", scriptFile);
 				logger.debug("Binding variables added: jobExecutionContext, logger, scriptFile");
 
 				// Evaluate script file.
-				logger.debug("Evaluating scriptFile {}.", filename);
-				FileReader reader = new FileReader(filename);
-				try {
-					result = scriptEngine.eval(reader, bindings);
-				} finally {
-					reader.close();
-				}
+				logger.debug("Evaluating scriptFile {}.", scriptFile);
+				result = ScriptingUtils.runScriptFile(scriptFile, engineName, bindings);
 			}
 
 			// Store the result in case there is JobListener or TriggerListener setup to retrieve it.

File timemachine-scheduler/src/main/java/timemachine/scheduler/schedule/ScriptingDateListProvider.java

  */
 package timemachine.scheduler.schedule;
 
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.net.URL;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import javax.script.Bindings;
 import javax.script.ScriptEngine;
-import javax.script.ScriptEngineManager;
-import javax.script.ScriptException;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import timemachine.scheduler.SchedulerException;
 import timemachine.scheduler.schedule.DateListSchedule.DateListProvider;
-import timemachine.scheduler.support.ClasspathURLStreamHandler;
+import timemachine.scheduler.support.ScriptingUtils;
 import timemachine.scheduler.support.Utils;
 
 /**
 		scriptEngineName = providerDataMap.get("scriptEngineName");
 		if (scriptEngineName == null) 
 			scriptEngineName = DEFUALT_SCRIPTING_ENGINE_NAME;
-		
-		logger.debug("Loading scripting engine: {}", scriptEngineName);
-		ScriptEngineManager factory = new ScriptEngineManager();
-		scriptEngine = factory.getEngineByName(scriptEngineName);
-		if (scriptEngine == null) {
-			throw new SchedulerException("Failed to generate date list: not found ScriptEngine " + scriptEngineName);
-		}
 	}
 	
 	@SuppressWarnings("unchecked")
 		if (scriptEngine == null)
 			init(schedule);
 		
-		Bindings bindings = scriptEngine.createBindings();
+		Map<String, Object> bindings = new HashMap<String, Object>();
 		bindings.put("dateListSchedule", schedule);
 		bindings.put("dateListProvider", this);
 		bindings.put("dateListProviderDataMap", providerDataMap);
 		Object result = null;
 		if (scriptText != null) {
 			try {
-				result = scriptEngine.eval(scriptText, bindings);
-			} catch (ScriptException e) {
+				logger.debug("Running scriptText");
+				result = ScriptingUtils.runScriptText(scriptText, scriptEngineName, bindings);
+			} catch (RuntimeException e) {
 				throw new SchedulerException("Failed to generate date list: failed to evaluate script to date list.", e);
 			}
 		} else if (scriptFile != null){
-			Reader reader = null;
 			try {
-				URL url = ClasspathURLStreamHandler.createURL(scriptFile);
-				logger.debug("Reading scriptFile {}", url);
-				InputStream inStream = url.openStream();
-				reader = new InputStreamReader(inStream);
-				result = scriptEngine.eval(reader, bindings);
-			} catch (FileNotFoundException e) {
+				logger.debug("Running scriptFile {}", scriptFile);
+				result = ScriptingUtils.runScriptFile(scriptFile, scriptEngineName, bindings);
+			} catch (RuntimeException e) {
 				throw new SchedulerException("Failed to generate date list: failed to find script " + scriptFile, e);
-			} catch (ScriptException e) {
-				throw new SchedulerException("Failed to generate date list: failed to run script " + scriptFile, e);
-			} catch (IOException e) {
-				throw new SchedulerException("Failed to generate date list: failed to read script " + scriptFile, e);
-			} finally {
-				try {
-					if (reader != null) {
-						reader.close();
-					}
-				} catch (IOException e) {
-					throw new SchedulerException("Failed to generate date list: failed to close reader for script " + scriptFile, e);
-				}
 			}
 		} else {
 			throw new SchedulerException("Failed to generate date list: missing scriptText or scriptFile" +

File timemachine-scheduler/src/main/java/timemachine/scheduler/service/DataStore.java

 import java.util.List;