Zemian Deng avatar Zemian Deng committed bb7f045

Renamed all servlet and view names to be better and consistent matching the main menu links.

Comments (0)

Files changed (38)

timemachine-web/src/main/java/timemachine/scheduler/web/ConfigServlet.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.web;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import timemachine.scheduler.Scheduler;
+import timemachine.scheduler.SchedulerFactory;
+import timemachine.scheduler.service.ConfigPropsService;
+import timemachine.scheduler.support.ClasspathURLStreamHandler;
+import timemachine.scheduler.support.Props;
+
+/**
+ * Serve the config edit form pages.
+ *
+ * @author Zemian Deng
+ */
+public class ConfigServlet extends HttpServlet
+{
+	private static Logger logger = LoggerFactory.getLogger(ConfigServlet.class);
+	
+	private static final long serialVersionUID = 1L;
+	
+	private static final String VIEW = "config.jsp";
+	
+	@Override
+	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+	{
+		logger.trace("Getting scheduler config edit form.");
+		String viewDefaultConfig = req.getParameter("viewDefaultConfig");
+		if ("true".equals(viewDefaultConfig)) {
+			InputStream instream = getClass().getResourceAsStream("/timemachine/scheduler/default.properties");
+			String text = IOUtils.toString(instream);
+			instream.close();
+			
+			resp.setContentType("text/html");
+			resp.getWriter().println("<html><body><pre>");
+			resp.getWriter().println(text);
+			resp.getWriter().println("</pre></body></html>");
+			resp.getWriter().flush();
+			return;
+		} else {
+			String configPropsText = (String)getServletContext().getAttribute("configPropsText");
+			if (configPropsText == null) {
+				String configUrl = "classpath:///timemachine/scheduler/default.properties";
+				if (System.getProperties().containsKey(ConfigPropsService.SYS_PROPS_KEY)) {
+					configUrl = System.getProperty(ConfigPropsService.SYS_PROPS_KEY);
+				}
+				URL url = ClasspathURLStreamHandler.createURL(configUrl);
+				InputStream instream = url.openStream();
+				configPropsText = IOUtils.toString(instream);
+				instream.close();
+				getServletContext().setAttribute("configPropsText", configPropsText);
+			}
+			req.getRequestDispatcher(VIEW).forward(req, resp);
+		}
+	}
+	
+	@Override
+	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+	{
+		// Loading default scheduler config first
+		Props props = new Props();
+		URL url = ClasspathURLStreamHandler.createURL(ConfigPropsService.DEFAULT_CONFIG_FILE);
+		props.load(url);
+		props.expandVariables();
+				
+		// Load user provided config from web form
+		logger.debug("Updating scheduler config.");
+		String configPropsText = req.getParameter("configPropsText");
+		ByteArrayInputStream inStream = new ByteArrayInputStream(configPropsText.getBytes());		
+		props.load(inStream);
+		props.expandVariables();
+	
+		// Destroy existing scheduler
+		logger.debug("Destroying existing scheduler.");
+		Scheduler scheduler = (Scheduler)getServletContext().getAttribute("scheduler");
+		scheduler.destroy();
+		
+		// Reload new scheduler instance with new config object.
+		scheduler = new SchedulerFactory(props).createScheduler();
+		scheduler.getSchedulerContext().putValue("servletContext", getServletContext());
+		scheduler.start();
+		getServletContext().setAttribute("scheduler", scheduler);
+		getServletContext().setAttribute("configPropsText", configPropsText);
+		req.setAttribute("postSuccessful", true);
+		req.getRequestDispatcher(VIEW).forward(req, resp);
+	}
+}

timemachine-web/src/main/java/timemachine/scheduler/web/ConsoleServlet.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.web;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import timemachine.scheduler.support.ScriptingUtils;
+
+/**
+ * Serve the script editing form pages.
+ * @author Zemian Deng
+ */
+public class ConsoleServlet extends HttpServlet
+{
+	private static Logger logger = LoggerFactory.getLogger(ConsoleServlet.class);
+	
+	private static final long serialVersionUID = 1L;
+	
+	private static final String VIEW = "console.jsp";
+	
+	@Override
+	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+	{
+		logger.trace("Getting scripting shell form.");
+		req.getRequestDispatcher(VIEW).forward(req, resp);
+	}
+	
+	@Override
+	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+	{
+		logger.debug("Processing scripting shell.");
+		ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+		PrintWriter webWriter = new PrintWriter(outStream);
+		
+		Map<String, Object> bindings = new HashMap<String, Object>();
+		bindings.put("webout", webWriter);
+		bindings.put("logger", logger);
+		bindings.put("servletContext", getServletContext());
+		bindings.put("scheduler", getServletContext().getAttribute("scheduler"));
+		bindings.put("request", req);
+		String scriptText = req.getParameter("code");
+		String scriptEngineName = "Groovy";
+		ScriptingUtils.runScriptText(scriptText, scriptEngineName, bindings);
+		
+		// cleanup
+		webWriter.close();
+		
+		// Forward to view if script didn't write their own output
+		if(!resp.isCommitted()) {
+			String webWriterResult = outStream.toString();
+			req.setAttribute("code", scriptText);
+			req.setAttribute("webWriterResult", webWriterResult);
+			req.setAttribute("postSuccessful", true);
+			req.getRequestDispatcher(VIEW).forward(req, resp);
+		}
+	}
+}

timemachine-web/src/main/java/timemachine/scheduler/web/HealthServlet.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.web;
+
+import java.io.IOException;
+import java.util.Date;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang.time.DurationFormatUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Serve health page.
+ *
+ * @author Zemian Deng
+ */
+public class HealthServlet extends HttpServlet
+{
+	private static Logger logger = LoggerFactory.getLogger(HealthServlet.class);
+	
+	private static final long serialVersionUID = 1L;
+	
+	private static final String VIEW = "health.jsp";
+	
+	@Override
+	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+	{
+		long startTime = ((Date)getServletContext().getAttribute("webappStartTime")).getTime();
+		String webappUpTime = DurationFormatUtils.formatDurationHMS(System.currentTimeMillis() - startTime); 
+		logger.trace("Getting health info.");
+		req.setAttribute("serverInfo", getServletContext().getServerInfo());
+		req.setAttribute("serverTime", new Date().getTime());
+		req.setAttribute("webappUpTime", webappUpTime);
+		req.getRequestDispatcher(VIEW).forward(req, resp);
+	}
+}

timemachine-web/src/main/java/timemachine/scheduler/web/HistoriesServlet.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.web;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import timemachine.scheduler.EventHistory;
+import timemachine.scheduler.Scheduler;
+import timemachine.scheduler.service.DataStore;
+import timemachine.scheduler.service.SchedulerEngine;
+
+/**
+ * Serve the scheduler event histories page.
+ * 
+ * @author Zemian Deng
+ */
+public class HistoriesServlet extends HttpServlet
+{
+	private static Logger logger = LoggerFactory.getLogger(HistoriesServlet.class);
+	
+	private static final long serialVersionUID = 1L;
+	
+	private static final String VIEW = "histories.jsp";
+	
+	@Override
+	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+	{
+		logger.trace("Getting scheduler event histories page.");
+		Scheduler scheduler = (Scheduler)getServletContext().getAttribute("scheduler");
+		// We assume we use the one and only SchedulerEngine impl.
+		SchedulerEngine schedulerEngine = (SchedulerEngine)scheduler;
+		DataStore dataStore = schedulerEngine.getSystemServiceContainer().getDataStoreService();
+		List<EventHistory> eventHistories = dataStore.findEventHistories(scheduler.getSchedulerNode().getId());
+		req.setAttribute("eventHistories", eventHistories);
+		req.getRequestDispatcher(VIEW).forward(req, resp);
+	}
+}

timemachine-web/src/main/java/timemachine/scheduler/web/JobDetailServlet.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.web;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import timemachine.scheduler.JobDef;
+import timemachine.scheduler.Schedule;
+import timemachine.scheduler.Scheduler;
+
+/**
+ * Serve job schedule or job definition detail pages.
+ *
+ * @author Zemian Deng
+ */
+public class JobDetailServlet extends HttpServlet
+{
+	private static Logger logger = LoggerFactory.getLogger(JobDetailServlet.class);
+	
+	private static final long serialVersionUID = 1L;
+		
+	@Override
+	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+	{
+		logger.trace("Getting job/schedule detail.");
+		Scheduler scheduler = (Scheduler)getServletContext().getAttribute("scheduler");
+		Long jobDefId = Long.parseLong(req.getParameter("jobDefId"));
+		JobDef jobDef = scheduler.getJobDef(jobDefId);
+		req.setAttribute("jobDef", jobDef);
+				
+		String type = req.getParameter("type");
+		if (type != null &&  "jobDef".equals(type)) {
+			req.getRequestDispatcher("jobdetail-jobdef.jsp").forward(req, resp);
+		} else if (type != null && "schedule".equals(type)) {
+			Long scheduleId = Long.parseLong(req.getParameter("scheduleId"));
+			Schedule schedule = scheduler.getSchedule(scheduleId);
+			req.setAttribute("schedule", schedule);
+			req.getRequestDispatcher("jobdetail-schedule.jsp").forward(req, resp);			
+		} else if (type != null && "previewNextRun".equals(type)) {
+			Long scheduleId = Long.parseLong(req.getParameter("scheduleId"));
+			Integer maxCount = Integer.parseInt(req.getParameter("maxCount"));
+			Schedule schedule = scheduler.getSchedule(scheduleId);
+			List<Date> nextRunDates = schedule.getNextRuns(new Date(), maxCount);
+			req.setAttribute("schedule", schedule);
+			req.setAttribute("nextRunDates", nextRunDates);
+			req.getRequestDispatcher("jobdetail-preview.jsp").forward(req, resp);
+		}
+	}
+}

timemachine-web/src/main/java/timemachine/scheduler/web/JobsServlet.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.web;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import timemachine.scheduler.JobDef;
+import timemachine.scheduler.Schedule;
+import timemachine.scheduler.Scheduler;
+import timemachine.scheduler.Schedules;
+import timemachine.scheduler.support.ScheduleComparator;
+
+/**
+ * Serve the job list pages.
+ *
+ * @author Zemian Deng
+ */
+public class JobsServlet extends HttpServlet
+{
+	private static final long serialVersionUID = 1L;
+	
+	private static Logger logger = LoggerFactory.getLogger(JobsServlet.class);
+	
+	private static final String VIEW = "jobs.jsp";
+	
+	private Comparator<Schedule> schedulerComoparator = new ScheduleComparator();
+	
+	@Override
+	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+	{
+		Scheduler scheduler = (Scheduler)getServletContext().getAttribute("scheduler");
+		
+		String action = req.getParameter("action");
+		if ("runItNow".equals(action)) {
+			Long jobDefId = Long.parseLong(req.getParameter("jobDefId"));
+			JobDef jobDef = scheduler.getJobDef(jobDefId);
+			Schedule schedule = Schedules.once().setName("RunItNowJob");
+			jobDef.addSchedule(schedule);
+			scheduler.updateJobDef(jobDef);
+			logger.info("Added new {} for jobDefId={}", schedule, jobDefId);
+			req.setAttribute("actionStatus", "A one time Schedule (id=" + schedule.getId() + ") has been added.");
+		} else if ("pauseSchedule".equals(action)) {
+			Long scheduleId = Long.parseLong(req.getParameter("scheduleId"));
+			Schedule schedule = scheduler.getSchedule(scheduleId);
+			scheduler.pauseSchedule(schedule);
+			logger.info("Paused {} for jobDefId={}", schedule, schedule.getJobDefId());
+			req.setAttribute("actionStatus", "Schedule (id=" + schedule.getId() + ") has been paused.");
+		} else if ("resumeSchedule".equals(action)) {
+			Long scheduleId = Long.parseLong(req.getParameter("scheduleId"));
+			Schedule schedule = scheduler.getSchedule(scheduleId);
+			scheduler.resumeSchedule(schedule);
+			logger.info("Resumed {} for jobDefId={}", schedule, schedule.getJobDefId());
+			req.setAttribute("actionStatus", "Schedule (id=" + schedule.getId() + ") has been resumed.");
+		} else if ("deleteSchedule".equals(action)) {
+			Long scheduleId = Long.parseLong(req.getParameter("scheduleId"));
+			Schedule schedule = scheduler.getSchedule(scheduleId);
+			scheduler.unschedule(schedule);
+			logger.info("Removed {} for jobDefId={}", schedule, schedule.getJobDefId());
+			req.setAttribute("actionStatus", "Schedule (id=" + schedule.getId() + ") has been removed.");
+		}
+		
+		logger.trace("Getting job list.");
+		List<ScheduleUi> scheduleUiList = new ArrayList<ScheduleUi>();
+		List<Schedule> scheduleList = scheduler.findSchedules();
+		Collections.sort(scheduleList, schedulerComoparator);
+		for (Schedule schedule : scheduleList) {
+			JobDef jobDef = scheduler.getJobDef(schedule.getJobDefId());
+			ScheduleUi scheduleUi = new ScheduleUi(schedule, jobDef);
+			scheduleUiList.add(scheduleUi);
+		}
+					
+		List<JobDefUi> noScheduleJobDefUiList = new ArrayList<JobDefUi>();
+		List<JobDef> jobDefList = scheduler.findJobDefs();
+		for (JobDef jd : jobDefList)
+			if (jd.getSchedules().size() == 0)
+				noScheduleJobDefUiList.add(new JobDefUi(jd));
+		
+		req.setAttribute("scheduleUiList", scheduleUiList);
+		req.setAttribute("noScheduleJobDefUiList", noScheduleJobDefUiList);
+		req.getRequestDispatcher(VIEW).forward(req, resp);
+	}
+	
+	public static class JobDefUi {
+		private JobDef jobDef;
+		public JobDef getJobDef() {
+			return jobDef;
+		}
+		public JobDefUi(JobDef jobDef) {
+			this.jobDef = jobDef;
+		}
+		public String getNameAndId() {
+			if (jobDef.getName() != null)
+				return jobDef.getName();
+			else 
+				return "JobDef#" + jobDef.getId();
+		}
+		public String getJobTaskName() {
+			String className = jobDef.getJobTaskClassName();
+			return className.substring(className.lastIndexOf(".") + 1);			
+		}
+	}
+	
+	public static class ScheduleUi {
+		private Schedule schedule;
+		private JobDefUi jobDefUi;
+		public ScheduleUi(Schedule schedule, JobDef jobDef) {
+			this.schedule = schedule;
+			this.jobDefUi = new JobDefUi(jobDef); 
+		}
+		public Schedule getSchedule() {
+			return schedule;
+		}
+		public JobDefUi getJobDefUi() {
+			return jobDefUi;
+		}
+		public String getNameAndId() {
+			if (schedule.getName() != null)
+				return schedule.getName();
+			else 
+				return "Schedule#" + schedule.getId();
+		}
+	}
+}

timemachine-web/src/main/java/timemachine/scheduler/web/SchedulerContextListener.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.web;
+
+import java.util.Date;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import timemachine.scheduler.Scheduler;
+import timemachine.scheduler.SchedulerFactory;
+import timemachine.scheduler.support.VersionUtils;
+
+/**
+ * A context listener to start the TimeMachine scheduler.
+ *
+ * @author Zemian Deng
+ */
+public class SchedulerContextListener implements ServletContextListener
+{
+	private static Logger logger = LoggerFactory.getLogger(SchedulerContextListener.class);
+	
+	@Override
+	public void contextInitialized(ServletContextEvent sce) {
+		String schedulerConfigProps = sce.getServletContext().getInitParameter("schedulerConfigProps");
+		logger.info("Loading TimeMachine Scheduler config from: {}", schedulerConfigProps);
+		sce.getServletContext().setAttribute("webappStartTime", new Date());
+		
+		Scheduler scheduler = new SchedulerFactory(schedulerConfigProps).createScheduler();
+		scheduler.getSchedulerContext().putValue("servletContext", sce.getServletContext());
+		String schedulerVersion = VersionUtils.getSchedulerVersion();
+		sce.getServletContext().setAttribute("scheduler", scheduler);
+		sce.getServletContext().setAttribute("schedulerVersion", schedulerVersion);
+		scheduler.start();
+	}
+
+	@Override
+	public void contextDestroyed(ServletContextEvent sce) {
+		Scheduler scheduler = (Scheduler)sce.getServletContext().getAttribute("scheduler");
+		logger.info("Destroying {}", scheduler);
+		scheduler.destroy();
+	}
+}

timemachine-web/src/main/java/timemachine/scheduler/web/ServicesServlet.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.web;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import timemachine.scheduler.Scheduler;
+import timemachine.scheduler.Service;
+import timemachine.scheduler.service.SchedulerEngine;
+import timemachine.scheduler.service.SystemServiceContainer;
+
+/**
+ * Serve scheduler system information pages.
+ *
+ * @author Zemian Deng
+ */
+public class ServicesServlet extends HttpServlet
+{
+	private static Logger logger = LoggerFactory.getLogger(ServicesServlet.class);
+	
+	private static final long serialVersionUID = 1L;
+	
+	private static final String VIEW = "services.jsp";
+	
+	@Override
+	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+	{
+		logger.trace("Getting system info");
+		Scheduler scheduler = (Scheduler)getServletContext().getAttribute("scheduler");
+		Map<String, Service> systemServices = new TreeMap<String, Service>();
+		Map<String, Service> userServices = new TreeMap<String, Service>();
+		
+		if (scheduler instanceof SchedulerEngine) {
+			SystemServiceContainer systemServiceContainer = ((SchedulerEngine)scheduler).getSystemServiceContainer();
+			for (String name : systemServiceContainer.getServiceNames()) {
+				systemServices.put(name, systemServiceContainer.getService(name));
+			}
+		} else {
+			logger.warn("Scheduler is not instance of SchedulerEngine! But " + scheduler.getClass());
+		}
+		
+		for (String name : scheduler.getUserServiceNames()) {
+			userServices.put(name, scheduler.getUserService(name));
+		}
+
+		req.setAttribute("systemServices", systemServices);
+		req.setAttribute("userServices", userServices);
+		req.getRequestDispatcher(VIEW).forward(req, resp);
+	}
+}

timemachine-web/src/main/java/timemachine/schedulerweb/EditConfigServlet.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.schedulerweb;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.commons.io.IOUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import timemachine.scheduler.Scheduler;
-import timemachine.scheduler.SchedulerFactory;
-import timemachine.scheduler.service.ConfigPropsService;
-import timemachine.scheduler.support.ClasspathURLStreamHandler;
-import timemachine.scheduler.support.Props;
-
-/**
- * Serve the config edit form pages.
- *
- * @author Zemian Deng
- */
-public class EditConfigServlet extends HttpServlet
-{
-	private static Logger logger = LoggerFactory.getLogger(EditConfigServlet.class);
-	
-	private static final long serialVersionUID = 1L;
-	
-	private static final String VIEW = "edit-config.jsp";
-	
-	@Override
-	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
-	{
-		logger.trace("Getting scheduler config edit form.");
-		String viewDefaultConfig = req.getParameter("viewDefaultConfig");
-		if ("true".equals(viewDefaultConfig)) {
-			InputStream instream = getClass().getResourceAsStream("/timemachine/scheduler/default.properties");
-			String text = IOUtils.toString(instream);
-			instream.close();
-			
-			resp.setContentType("text/html");
-			resp.getWriter().println("<html><body><pre>");
-			resp.getWriter().println(text);
-			resp.getWriter().println("</pre></body></html>");
-			resp.getWriter().flush();
-			return;
-		} else {
-			String configPropsText = (String)getServletContext().getAttribute("configPropsText");
-			if (configPropsText == null) {
-				String configUrl = "classpath:///timemachine/scheduler/default.properties";
-				if (System.getProperties().containsKey(ConfigPropsService.SYS_PROPS_KEY)) {
-					configUrl = System.getProperty(ConfigPropsService.SYS_PROPS_KEY);
-				}
-				URL url = ClasspathURLStreamHandler.createURL(configUrl);
-				InputStream instream = url.openStream();
-				configPropsText = IOUtils.toString(instream);
-				instream.close();
-				getServletContext().setAttribute("configPropsText", configPropsText);
-			}
-			req.getRequestDispatcher(VIEW).forward(req, resp);
-		}
-	}
-	
-	@Override
-	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
-	{
-		// Loading default scheduler config first
-		Props props = new Props();
-		URL url = ClasspathURLStreamHandler.createURL(ConfigPropsService.DEFAULT_CONFIG_FILE);
-		props.load(url);
-		props.expandVariables();
-				
-		// Load user provided config from web form
-		logger.debug("Updating scheduler config.");
-		String configPropsText = req.getParameter("configPropsText");
-		ByteArrayInputStream inStream = new ByteArrayInputStream(configPropsText.getBytes());		
-		props.load(inStream);
-		props.expandVariables();
-	
-		// Destroy existing scheduler
-		logger.debug("Destroying existing scheduler.");
-		Scheduler scheduler = (Scheduler)getServletContext().getAttribute("scheduler");
-		scheduler.destroy();
-		
-		// Reload new scheduler instance with new config object.
-		scheduler = new SchedulerFactory(props).createScheduler();
-		scheduler.getSchedulerContext().putValue("servletContext", getServletContext());
-		scheduler.start();
-		getServletContext().setAttribute("scheduler", scheduler);
-		getServletContext().setAttribute("configPropsText", configPropsText);
-		req.setAttribute("postSuccessful", true);
-		req.getRequestDispatcher(VIEW).forward(req, resp);
-	}
-}

timemachine-web/src/main/java/timemachine/schedulerweb/EventHistoryServlet.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.schedulerweb;
-
-import java.io.IOException;
-import java.util.List;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import timemachine.scheduler.EventHistory;
-import timemachine.scheduler.Scheduler;
-import timemachine.scheduler.service.DataStore;
-import timemachine.scheduler.service.SchedulerEngine;
-
-/**
- * Serve the scheduler event histories page.
- * 
- * @author Zemian Deng
- */
-public class EventHistoryServlet extends HttpServlet
-{
-	private static Logger logger = LoggerFactory.getLogger(EventHistoryServlet.class);
-	
-	private static final long serialVersionUID = 1L;
-	
-	private static final String VIEW = "event-history.jsp";
-	
-	@Override
-	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
-	{
-		logger.trace("Getting scheduler event histories page.");
-		Scheduler scheduler = (Scheduler)getServletContext().getAttribute("scheduler");
-		// We assume we use the one and only SchedulerEngine impl.
-		SchedulerEngine schedulerEngine = (SchedulerEngine)scheduler;
-		DataStore dataStore = schedulerEngine.getSystemServiceContainer().getDataStoreService();
-		List<EventHistory> eventHistories = dataStore.findEventHistories(scheduler.getSchedulerNode().getId());
-		req.setAttribute("eventHistories", eventHistories);
-		req.getRequestDispatcher(VIEW).forward(req, resp);
-	}
-}

timemachine-web/src/main/java/timemachine/schedulerweb/HealthServlet.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.schedulerweb;
-
-import java.io.IOException;
-import java.util.Date;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.commons.lang.time.DurationFormatUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Serve health page.
- *
- * @author Zemian Deng
- */
-public class HealthServlet extends HttpServlet
-{
-	private static Logger logger = LoggerFactory.getLogger(HealthServlet.class);
-	
-	private static final long serialVersionUID = 1L;
-	
-	private static final String VIEW = "health.jsp";
-	
-	@Override
-	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
-	{
-		long startTime = ((Date)getServletContext().getAttribute("webappStartTime")).getTime();
-		String webappUpTime = DurationFormatUtils.formatDurationHMS(System.currentTimeMillis() - startTime); 
-		logger.trace("Getting health info.");
-		req.setAttribute("serverInfo", getServletContext().getServerInfo());
-		req.setAttribute("serverTime", new Date().getTime());
-		req.setAttribute("webappUpTime", webappUpTime);
-		req.getRequestDispatcher(VIEW).forward(req, resp);
-	}
-}

timemachine-web/src/main/java/timemachine/schedulerweb/JobDetailServlet.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.schedulerweb;
-
-import java.io.IOException;
-import java.util.Date;
-import java.util.List;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import timemachine.scheduler.JobDef;
-import timemachine.scheduler.Schedule;
-import timemachine.scheduler.Scheduler;
-
-/**
- * Serve job schedule or job definition detail pages.
- *
- * @author Zemian Deng
- */
-public class JobDetailServlet extends HttpServlet
-{
-	private static Logger logger = LoggerFactory.getLogger(JobDetailServlet.class);
-	
-	private static final long serialVersionUID = 1L;
-		
-	@Override
-	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
-	{
-		logger.trace("Getting job/schedule detail.");
-		Scheduler scheduler = (Scheduler)getServletContext().getAttribute("scheduler");
-		Long jobDefId = Long.parseLong(req.getParameter("jobDefId"));
-		JobDef jobDef = scheduler.getJobDef(jobDefId);
-		req.setAttribute("jobDef", jobDef);
-				
-		String type = req.getParameter("type");
-		if (type != null &&  "jobDef".equals(type)) {
-			req.getRequestDispatcher("job-detail-jobdef.jsp").forward(req, resp);
-		} else if (type != null && "schedule".equals(type)) {
-			Long scheduleId = Long.parseLong(req.getParameter("scheduleId"));
-			Schedule schedule = scheduler.getSchedule(scheduleId);
-			req.setAttribute("schedule", schedule);
-			req.getRequestDispatcher("job-detail-schedule.jsp").forward(req, resp);			
-		} else if (type != null && "previewNextRun".equals(type)) {
-			Long scheduleId = Long.parseLong(req.getParameter("scheduleId"));
-			Integer maxCount = Integer.parseInt(req.getParameter("maxCount"));
-			Schedule schedule = scheduler.getSchedule(scheduleId);
-			List<Date> nextRunDates = schedule.getNextRuns(new Date(), maxCount);
-			req.setAttribute("schedule", schedule);
-			req.setAttribute("nextRunDates", nextRunDates);
-			req.getRequestDispatcher("job-detail-preview-next-runs.jsp").forward(req, resp);
-		}
-	}
-}

timemachine-web/src/main/java/timemachine/schedulerweb/JobListServlet.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.schedulerweb;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import timemachine.scheduler.JobDef;
-import timemachine.scheduler.Schedule;
-import timemachine.scheduler.Scheduler;
-import timemachine.scheduler.Schedules;
-import timemachine.scheduler.support.ScheduleComparator;
-
-/**
- * Serve the job list pages.
- *
- * @author Zemian Deng
- */
-public class JobListServlet extends HttpServlet
-{
-	private static final long serialVersionUID = 1L;
-	
-	private static Logger logger = LoggerFactory.getLogger(JobListServlet.class);
-	
-	private static final String VIEW = "job-list.jsp";
-	
-	private Comparator<Schedule> schedulerComoparator = new ScheduleComparator();
-	
-	@Override
-	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
-	{
-		Scheduler scheduler = (Scheduler)getServletContext().getAttribute("scheduler");
-		
-		String action = req.getParameter("action");
-		if ("runItNow".equals(action)) {
-			Long jobDefId = Long.parseLong(req.getParameter("jobDefId"));
-			JobDef jobDef = scheduler.getJobDef(jobDefId);
-			Schedule schedule = Schedules.once().setName("RunItNowJob");
-			jobDef.addSchedule(schedule);
-			scheduler.updateJobDef(jobDef);
-			logger.info("Added new {} for jobDefId={}", schedule, jobDefId);
-			req.setAttribute("actionStatus", "A one time Schedule (id=" + schedule.getId() + ") has been added.");
-		} else if ("pauseSchedule".equals(action)) {
-			Long scheduleId = Long.parseLong(req.getParameter("scheduleId"));
-			Schedule schedule = scheduler.getSchedule(scheduleId);
-			scheduler.pauseSchedule(schedule);
-			logger.info("Paused {} for jobDefId={}", schedule, schedule.getJobDefId());
-			req.setAttribute("actionStatus", "Schedule (id=" + schedule.getId() + ") has been paused.");
-		} else if ("resumeSchedule".equals(action)) {
-			Long scheduleId = Long.parseLong(req.getParameter("scheduleId"));
-			Schedule schedule = scheduler.getSchedule(scheduleId);
-			scheduler.resumeSchedule(schedule);
-			logger.info("Resumed {} for jobDefId={}", schedule, schedule.getJobDefId());
-			req.setAttribute("actionStatus", "Schedule (id=" + schedule.getId() + ") has been resumed.");
-		} else if ("deleteSchedule".equals(action)) {
-			Long scheduleId = Long.parseLong(req.getParameter("scheduleId"));
-			Schedule schedule = scheduler.getSchedule(scheduleId);
-			scheduler.unschedule(schedule);
-			logger.info("Removed {} for jobDefId={}", schedule, schedule.getJobDefId());
-			req.setAttribute("actionStatus", "Schedule (id=" + schedule.getId() + ") has been removed.");
-		}
-		
-		logger.trace("Getting job list.");
-		List<ScheduleUi> scheduleUiList = new ArrayList<ScheduleUi>();
-		List<Schedule> scheduleList = scheduler.findSchedules();
-		Collections.sort(scheduleList, schedulerComoparator);
-		for (Schedule schedule : scheduleList) {
-			JobDef jobDef = scheduler.getJobDef(schedule.getJobDefId());
-			ScheduleUi scheduleUi = new ScheduleUi(schedule, jobDef);
-			scheduleUiList.add(scheduleUi);
-		}
-					
-		List<JobDefUi> noScheduleJobDefUiList = new ArrayList<JobDefUi>();
-		List<JobDef> jobDefList = scheduler.findJobDefs();
-		for (JobDef jd : jobDefList)
-			if (jd.getSchedules().size() == 0)
-				noScheduleJobDefUiList.add(new JobDefUi(jd));
-		
-		req.setAttribute("scheduleUiList", scheduleUiList);
-		req.setAttribute("noScheduleJobDefUiList", noScheduleJobDefUiList);
-		req.getRequestDispatcher(VIEW).forward(req, resp);
-	}
-	
-	public static class JobDefUi {
-		private JobDef jobDef;
-		public JobDef getJobDef() {
-			return jobDef;
-		}
-		public JobDefUi(JobDef jobDef) {
-			this.jobDef = jobDef;
-		}
-		public String getNameAndId() {
-			if (jobDef.getName() != null)
-				return jobDef.getName();
-			else 
-				return "JobDef#" + jobDef.getId();
-		}
-		public String getJobTaskName() {
-			String className = jobDef.getJobTaskClassName();
-			return className.substring(className.lastIndexOf(".") + 1);			
-		}
-	}
-	
-	public static class ScheduleUi {
-		private Schedule schedule;
-		private JobDefUi jobDefUi;
-		public ScheduleUi(Schedule schedule, JobDef jobDef) {
-			this.schedule = schedule;
-			this.jobDefUi = new JobDefUi(jobDef); 
-		}
-		public Schedule getSchedule() {
-			return schedule;
-		}
-		public JobDefUi getJobDefUi() {
-			return jobDefUi;
-		}
-		public String getNameAndId() {
-			if (schedule.getName() != null)
-				return schedule.getName();
-			else 
-				return "Schedule#" + schedule.getId();
-		}
-	}
-}

timemachine-web/src/main/java/timemachine/schedulerweb/SchedulerContextListener.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.schedulerweb;
-
-import java.util.Date;
-
-import javax.servlet.ServletContextEvent;
-import javax.servlet.ServletContextListener;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import timemachine.scheduler.Scheduler;
-import timemachine.scheduler.SchedulerFactory;
-import timemachine.scheduler.support.VersionUtils;
-
-/**
- * A context listener to start the TimeMachine scheduler.
- *
- * @author Zemian Deng
- */
-public class SchedulerContextListener implements ServletContextListener
-{
-	private static Logger logger = LoggerFactory.getLogger(SchedulerContextListener.class);
-	
-	@Override
-	public void contextInitialized(ServletContextEvent sce) {
-		String schedulerConfigProps = sce.getServletContext().getInitParameter("schedulerConfigProps");
-		logger.info("Loading TimeMachine Scheduler config from: {}", schedulerConfigProps);
-		sce.getServletContext().setAttribute("webappStartTime", new Date());
-		
-		Scheduler scheduler = new SchedulerFactory(schedulerConfigProps).createScheduler();
-		scheduler.getSchedulerContext().putValue("servletContext", sce.getServletContext());
-		String schedulerVersion = VersionUtils.getSchedulerVersion();
-		sce.getServletContext().setAttribute("scheduler", scheduler);
-		sce.getServletContext().setAttribute("schedulerVersion", schedulerVersion);
-		scheduler.start();
-	}
-
-	@Override
-	public void contextDestroyed(ServletContextEvent sce) {
-		Scheduler scheduler = (Scheduler)sce.getServletContext().getAttribute("scheduler");
-		logger.info("Destroying {}", scheduler);
-		scheduler.destroy();
-	}
-}

timemachine-web/src/main/java/timemachine/schedulerweb/ScriptingShellServlet.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.schedulerweb;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import timemachine.scheduler.support.ScriptingUtils;
-
-/**
- * Serve the script editing form pages.
- * @author Zemian Deng
- */
-public class ScriptingShellServlet extends HttpServlet
-{
-	private static Logger logger = LoggerFactory.getLogger(ScriptingShellServlet.class);
-	
-	private static final long serialVersionUID = 1L;
-	
-	private static final String VIEW = "scripting-shell.jsp";
-	
-	@Override
-	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
-	{
-		logger.trace("Getting scripting shell form.");
-		req.getRequestDispatcher(VIEW).forward(req, resp);
-	}
-	
-	@Override
-	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
-	{
-		logger.debug("Processing scripting shell.");
-		ByteArrayOutputStream outStream = new ByteArrayOutputStream();
-		PrintWriter webWriter = new PrintWriter(outStream);
-		
-		Map<String, Object> bindings = new HashMap<String, Object>();
-		bindings.put("webout", webWriter);
-		bindings.put("logger", logger);
-		bindings.put("servletContext", getServletContext());
-		bindings.put("scheduler", getServletContext().getAttribute("scheduler"));
-		bindings.put("request", req);
-		String scriptText = req.getParameter("code");
-		String scriptEngineName = "Groovy";
-		ScriptingUtils.runScriptText(scriptText, scriptEngineName, bindings);
-		
-		// cleanup
-		webWriter.close();
-		
-		// Forward to view if script didn't write their own output
-		if(!resp.isCommitted()) {
-			String webWriterResult = outStream.toString();
-			req.setAttribute("code", scriptText);
-			req.setAttribute("webWriterResult", webWriterResult);
-			req.setAttribute("postSuccessful", true);
-			req.getRequestDispatcher(VIEW).forward(req, resp);
-		}
-	}
-}

timemachine-web/src/main/java/timemachine/schedulerweb/SystemInfoServlet.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.schedulerweb;
-
-import java.io.IOException;
-import java.util.Map;
-import java.util.TreeMap;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import timemachine.scheduler.Scheduler;
-import timemachine.scheduler.Service;
-import timemachine.scheduler.service.SchedulerEngine;
-import timemachine.scheduler.service.SystemServiceContainer;
-
-/**
- * Serve scheduler system information pages.
- *
- * @author Zemian Deng
- */
-public class SystemInfoServlet extends HttpServlet
-{
-	private static Logger logger = LoggerFactory.getLogger(SystemInfoServlet.class);
-	
-	private static final long serialVersionUID = 1L;
-	
-	private static final String VIEW = "system-info.jsp";
-	
-	@Override
-	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
-	{
-		logger.trace("Getting system info");
-		Scheduler scheduler = (Scheduler)getServletContext().getAttribute("scheduler");
-		Map<String, Service> systemServices = new TreeMap<String, Service>();
-		Map<String, Service> userServices = new TreeMap<String, Service>();
-		
-		if (scheduler instanceof SchedulerEngine) {
-			SystemServiceContainer systemServiceContainer = ((SchedulerEngine)scheduler).getSystemServiceContainer();
-			for (String name : systemServiceContainer.getServiceNames()) {
-				systemServices.put(name, systemServiceContainer.getService(name));
-			}
-		} else {
-			logger.warn("Scheduler is not instance of SchedulerEngine! But " + scheduler.getClass());
-		}
-		
-		for (String name : scheduler.getUserServiceNames()) {
-			userServices.put(name, scheduler.getUserService(name));
-		}
-
-		req.setAttribute("systemServices", systemServices);
-		req.setAttribute("userServices", userServices);
-		req.getRequestDispatcher(VIEW).forward(req, resp);
-	}
-}

timemachine-web/src/main/webapp/WEB-INF/web.xml

 	</welcome-file-list>
 
 	<servlet>
-		<servlet-name>JobListServlet</servlet-name>
-		<servlet-class>timemachine.schedulerweb.JobListServlet</servlet-class>
+		<servlet-name>JobsServlet</servlet-name>
+		<servlet-class>timemachine.scheduler.web.JobsServlet</servlet-class>
 	</servlet>
 	<servlet-mapping>
-		<servlet-name>JobListServlet</servlet-name>
-		<url-pattern>/job-list</url-pattern>
+		<servlet-name>JobsServlet</servlet-name>
+		<url-pattern>/jobs</url-pattern>
 	</servlet-mapping>
 	<servlet>
 		<servlet-name>JobDetailServlet</servlet-name>
-		<servlet-class>timemachine.schedulerweb.JobDetailServlet</servlet-class>
+		<servlet-class>timemachine.scheduler.web.JobDetailServlet</servlet-class>
 	</servlet>
 	<servlet-mapping>
 		<servlet-name>JobDetailServlet</servlet-name>
-		<url-pattern>/job-detail</url-pattern>
+		<url-pattern>/jobdetail</url-pattern>
 	</servlet-mapping>
 	<servlet>
-		<servlet-name>ScriptingShellServlet</servlet-name>
-		<servlet-class>timemachine.schedulerweb.ScriptingShellServlet</servlet-class>
+		<servlet-name>ConsoleServlet</servlet-name>
+		<servlet-class>timemachine.scheduler.web.ConsoleServlet</servlet-class>
 	</servlet>
 	<servlet-mapping>
-		<servlet-name>ScriptingShellServlet</servlet-name>
-		<url-pattern>/scripting-shell</url-pattern>
+		<servlet-name>ConsoleServlet</servlet-name>
+		<url-pattern>/console</url-pattern>
 	</servlet-mapping>
 	<servlet>
-		<servlet-name>EditConfigServlet</servlet-name>
-		<servlet-class>timemachine.schedulerweb.EditConfigServlet</servlet-class>
+		<servlet-name>ConfigServlet</servlet-name>
+		<servlet-class>timemachine.scheduler.web.ConfigServlet</servlet-class>
 	</servlet>
 	<servlet-mapping>
-		<servlet-name>EditConfigServlet</servlet-name>
-		<url-pattern>/edit-config</url-pattern>
+		<servlet-name>ConfigServlet</servlet-name>
+		<url-pattern>/config</url-pattern>
 	</servlet-mapping>
 	<servlet>
-		<servlet-name>SystemInfoServlet</servlet-name>
-		<servlet-class>timemachine.schedulerweb.SystemInfoServlet</servlet-class>
+		<servlet-name>ServicesServlet</servlet-name>
+		<servlet-class>timemachine.scheduler.web.ServicesServlet</servlet-class>
 	</servlet>
 	<servlet-mapping>
-		<servlet-name>SystemInfoServlet</servlet-name>
-		<url-pattern>/system-info</url-pattern>
+		<servlet-name>ServicesServlet</servlet-name>
+		<url-pattern>/services</url-pattern>
 	</servlet-mapping>
 	<servlet>
-		<servlet-name>EventHistoryServlet</servlet-name>
-		<servlet-class>timemachine.schedulerweb.EventHistoryServlet</servlet-class>
+		<servlet-name>HistoriesServlet</servlet-name>
+		<servlet-class>timemachine.scheduler.web.HistoriesServlet</servlet-class>
 	</servlet>
 	<servlet-mapping>
-		<servlet-name>EventHistoryServlet</servlet-name>
-		<url-pattern>/event-history</url-pattern>
+		<servlet-name>HistoriesServlet</servlet-name>
+		<url-pattern>/histories</url-pattern>
 	</servlet-mapping>
 	<servlet>
 		<servlet-name>HealthServlet</servlet-name>
-		<servlet-class>timemachine.schedulerweb.HealthServlet</servlet-class>
+		<servlet-class>timemachine.scheduler.web.HealthServlet</servlet-class>
 	</servlet>
 	<servlet-mapping>
 		<servlet-name>HealthServlet</servlet-name>
 	</context-param>
 
 	<listener>
-		<listener-class>timemachine.schedulerweb.SchedulerContextListener</listener-class>
+		<listener-class>timemachine.scheduler.web.SchedulerContextListener</listener-class>
 	</listener>
 
 </web-app>

timemachine-web/src/main/webapp/config.jsp

+<%@ include file="header.inc" %>
+
+<h2>Edit Config Properties</h2>
+<p>See the <a href="?viewDefaultConfig=true">Default Config</a>.</p>
+<div>
+<form name="config-form" method="post" action="${pageContext.request.contextPath}/config">
+<textarea name="configPropsText" rows="20" cols="120">${configPropsText}</textarea>
+<br/>
+<input type="submit" value="Update" name="Update">
+<p>(Updating config will destroy the current scheduler, and then re-create a new scheduler instance.)</p>
+</form>
+</div>
+
+<c:if test="${postSuccessful}">
+<p style="color: green;">New scheduler with above config properties has been created.</p> 
+</c:if>
+
+<%@ include file="footer.inc" %>

timemachine-web/src/main/webapp/console-samples.txt

+// =================
+// Creating new jobs
+// =================
+
+// Create some repeating jobs
+import timemachine.scheduler.*
+jobDef = JobDefs.loggerJobDef()
+jobDef.addSchedule(Schedules.secondly(1).setName('job1-schedule1'))
+jobDef.addSchedule(Schedules.minutely(1).setName('job1-schedule2'))
+jobDef.addSchedule(Schedules.hourly(1).setName('job1-schedule3'))
+jobDef.addSchedule(Schedules.daily(1).setName('job1-schedule4'))
+jobDef.addSchedule(Schedules.weekly(1).setName('job1-schedule5'))
+scheduler.schedule(jobDef)
+webout.println("Done: $jobDef has been scheduled.")
+
+// Create some cron jobs
+import timemachine.scheduler.*
+jobDef = JobDefs.sleepyJobDef(1500)
+jobDef.addSchedule(Schedules.cron("0/5 * * * * ?").setName('job2-5secs'))
+jobDef.addSchedule(Schedules.cron("0 0/3 * * * ?").setName('job2-3minsA'))
+jobDef.addSchedule(Schedules.cron("0 0/3 * * * ?").setName('job2-3minsB'))
+jobDef.addSchedule(Schedules.cron("0 0 * * * ?").setName('job2-1min'))
+jobDef.addSchedule(Schedules.cron("0 0 8 * * ?").setName('job2-8AM'))
+jobDef.addSchedule(Schedules.cron("0 0 8 1,5,10,15,20,25 * ?").setName('job2-8AM_5th_days'))
+jobDef.addSchedule(Schedules.cron("0 0 8 ? * SAT,SUN").setName('job2-sun_sat'))
+jobDef.addSchedule(Schedules.cron("0 0 8,17 ? * 1-5").setName('job2-mon_to_fri'))
+jobDef.addSchedule(Schedules.cron("0 0 * 25 DEC ?").setName('job2-XMas'))
+jobDef.addSchedule(Schedules.cron("0 0 0 * * ? 2013").setName('job2-year13'))
+scheduler.schedule(jobDef)
+webout.println("Done: $jobDef has been scheduled.")
+
+// Create one-time job that has a startTime delay of 30 mins.
+import timemachine.scheduler.*
+jobDef = JobDefs.groovyJobDef("logger.info('JVM free memory: ' + Runtime.getRuntime().freeMemory())")
+jobDef.setName('MemoryChecker')
+jobDef.addSchedule(Schedules.once().setStartTime(Schedules.startTimeDelay(30*60*1000L)))
+scheduler.schedule(jobDef)
+webout.println("Done: $jobDef has been scheduled.")
+
+// Create End of month DateListSchedule
+import timemachine.scheduler.*
+import timemachine.scheduler.schedule.*
+s = new DateListSchedule()
+s.setName("job4-end-of-month")
+s.setDateListProviderClassName(EndOfMonthDateListProvider.class)
+jobDef = JobDefs.groovyJobDef("logger.info('JVM free memory: ' + Runtime.getRuntime().freeMemory())")
+jobDef.addSchedule(s)
+scheduler.schedule(jobDef)
+webout.println("Done: $jobDef has been scheduled.")
+
+// Custom ScriptingDateListProvider
+import timemachine.scheduler.*
+import timemachine.scheduler.schedule.*
+s = new DateListSchedule()
+s.setName("job5-script-schedule")
+s.setDates([new Date()])
+s.setDateListProviderClassName(ScriptingDateListProvider.class)
+s.setDateListProviderData('''scriptEngineName=Groovy,scriptText=
+	prevDate = dateListSchedule.getDates()[0]
+	return [new Date(prevDate.getTime() + 3000L)]
+''')
+jobDef = JobDefs.groovyJobDef("logger.info('Health Check.')")
+jobDef.addSchedule(s)
+scheduler.schedule(jobDef)
+webout.println("Done: $jobDef has been scheduled.")
+
+
+// ======================
+// Querying and Reporting
+// ======================
+// Show top most run counts
+schedules = scheduler.findSchedules().sort{s1,s2->s2.runCount <=> s1.runCount}
+schedules.each{ webout.println(it.id+' '+it.runCount+' '+it.missedRunCount) }
+webout.println("Done: ${schedules.size()} schedules.")
+
+// Show top missed run counts
+schedules = scheduler.findSchedules().grep{s->if(s.missedRunCount > 0) s }
+schedules.sort{s1,s2->s2.missedRunCount <=> s1.missedRunCount}
+schedules.each{ webout.println(it.id+' '+it.missedRunCount+' '+it.runCount) }
+webout.println("Done: ${schedules.size()} schedules.")
+
+// 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 ->
+  webout.println(s.toString() + ", nextRun: " + s.getNextRun() + ", state: " + s.getState() + ", startTime: " + s.getStartTime())
+}
+webout.println("Done: ${schedules.size()} schedules.")
+
+
+// ===========
+// Cleaning up
+// ===========
+
+// Delete a Schedule by name
+import timemachine.scheduler.*
+schedules = scheduler.findSchedules('job1-schedule4')
+schedule = schedules[0]
+scheduler.unschedule(schedule)
+webout.println("Done: $schedule has been removed.")
+
+// Delete all jobs
+import timemachine.scheduler.*
+jobDefs = scheduler.findJobDefs()
+jobDefs.each{ jobDef -> scheduler.unschedule(jobDef) }
+webout.println("Done: $jobDefs.size JobDef has been removed.")

timemachine-web/src/main/webapp/console.jsp

+<%@ include file="header.inc" %>
+
+<h2>Groovy Console Shell</h2>
+<div>
+<p>You may use <a href="http://groovy.codehaus.org">Groovy Scripting</a> to manager the scheduler. These are couple 
+built-in implicit variables you may use.</p> 
+<pre>
+webout - A <a href="http://docs.oracle.com/javase/6/docs/api/java/io/PrintWriter.html">java.io.PrintWriter</a> that you can display output back onto this webpage.
+scheduler - A <a href="http://tmschedulersite-zdeng.rhcloud.com/scheduler-site/timemachine-scheduler/target/site-deploy/timemachine-scheduler/apidocs/timemachine/scheduler/Scheduler.html">timemachine.scheduler.Scheduler</a> that you may control the scheduler.
+</pre>
+</div>
+
+<p>Not sure what to do? Check out some <a href="console-samples.txt">samples</a>.</p>
+
+<div>
+<form name="console-form" method="post" action="${pageContext.request.contextPath}/console">
+<textarea name="code" rows="15" cols="120"><% if (request.getAttribute("code") == null) { %><% } else { %>${code}<% } %>
+</textarea>
+<br/>
+<input type="submit" value="Run" name="Run">
+</form>
+</div>
+
+<c:if test="${postSuccessful}">
+<p style="color: green;">Script processed successfully.</p> 
+</c:if>
+<hr/>
+
+<pre>
+<code>${webWriterResult}</code>
+</pre>
+
+<%@ include file="footer.inc" %>

timemachine-web/src/main/webapp/edit-config.jsp

-<%@ include file="header.inc" %>
-
-<h2>Edit Config Properties</h2>
-<p>See the <a href="?viewDefaultConfig=true">Default Config</a>.</p>
-<div>
-<form name="edit-config-form" method="post" action="${pageContext.request.contextPath}/edit-config">
-<textarea name="configPropsText" rows="20" cols="120">${configPropsText}</textarea>
-<br/>
-<input type="submit" value="Update" name="Update">
-<p>(Updating config will destroy the current scheduler, and then re-create a new scheduler instance.)</p>
-</form>
-</div>
-
-<c:if test="${postSuccessful}">
-<p style="color: green;">New scheduler with above config properties has been created.</p> 
-</c:if>
-
-<%@ include file="footer.inc" %>

timemachine-web/src/main/webapp/event-history.jsp

-<%@ include file="header.inc" %>
-
-<h2>Scheduler Event Histories</h2>
-<p>There are ${ fn:length(eventHistories) } events found.</p>
-<table>
-	<thead>
-	<tr>
-		<th> EventId </th>
-		<th> SchedulerNodeId </th>
-		<th> EventType </th>
-		<th> Name </th>
-		<th> CreateTime </th>
-		<th> Info1 </th>
-		<th> Info2 </th>
-		<th> Info3 </th>
-		<th> Info4 </th>
-		<th> Info5 </th>
-	</tr>
-	</thead>
-	<tbody>
-	<c:forEach items="${ eventHistories }" var="eventHistory">
-	<tr>
-		<td>${eventHistory.id}</td>
-		<td>${eventHistory.schedulerNodeId}</td>
-		<td>${eventHistory.type}</td>
-		<td>${eventHistory.name}</td>
-		<td><fmt:formatDate pattern="MM/dd/yy HH:mm:ss" value="${eventHistory.createTime}"/></td>
-		<td>${eventHistory.info1}</td>
-		<td>${eventHistory.info2}</td>
-		<td>${eventHistory.info3}</td>
-		<td>${eventHistory.info4}</td>
-		<td>${eventHistory.info5}</td>
-	</tr>
-	</c:forEach>
-	</tbody>
-</table>
-
-<%@ include file="footer.inc" %>

timemachine-web/src/main/webapp/footer.inc

 <hr/>
-Brought to you by <a href="https://bitbucket.org/timemachine/scheduler/wiki/Home">TimeMachine Scheduler</a>
+Powered by <a href="https://bitbucket.org/timemachine/scheduler/wiki/Home">timemachine-scheduler-${ schedulerVersion }</a>
 
 </body>
 </html>

timemachine-web/src/main/webapp/header.inc

 <h1>TimeMachine Scheduler</h1>
 
 <ul id="menu">
-	<li><a href="${ pageContext.request.contextPath }/job-list">Jobs</a></li>
-	<li><a href="${ pageContext.request.contextPath }/event-history">Histories</a></li>
-	<li><a href="${ pageContext.request.contextPath }/system-info">Services</a></li>
-	<li><a href="${ pageContext.request.contextPath }/edit-config">Config</a></li>
-	<li><a href="${ pageContext.request.contextPath }/scripting-shell">Console</a></li>
+	<li><a href="${ pageContext.request.contextPath }/jobs">Jobs</a></li>
+	<li><a href="${ pageContext.request.contextPath }/histories">Histories</a></li>
+	<li><a href="${ pageContext.request.contextPath }/services">Services</a></li>
+	<li><a href="${ pageContext.request.contextPath }/config">Config</a></li>
+	<li><a href="${ pageContext.request.contextPath }/console">Console</a></li>
 </ul>
 
 <div>

timemachine-web/src/main/webapp/histories.jsp

+<%@ include file="header.inc" %>
+
+<h2>Scheduler Event Histories</h2>
+<p>There are ${ fn:length(eventHistories) } events found.</p>
+<table>
+	<thead>
+	<tr>
+		<th> EventId </th>
+		<th> SchedulerNodeId </th>
+		<th> EventType </th>
+		<th> Name </th>
+		<th> CreateTime </th>
+		<th> Info1 </th>
+		<th> Info2 </th>
+		<th> Info3 </th>
+		<th> Info4 </th>
+		<th> Info5 </th>
+	</tr>
+	</thead>
+	<tbody>
+	<c:forEach items="${ eventHistories }" var="eventHistory">
+	<tr>
+		<td>${eventHistory.id}</td>
+		<td>${eventHistory.schedulerNodeId}</td>
+		<td>${eventHistory.type}</td>
+		<td>${eventHistory.name}</td>
+		<td><fmt:formatDate pattern="MM/dd/yy HH:mm:ss" value="${eventHistory.createTime}"/></td>
+		<td>${eventHistory.info1}</td>
+		<td>${eventHistory.info2}</td>
+		<td>${eventHistory.info3}</td>
+		<td>${eventHistory.info4}</td>
+		<td>${eventHistory.info5}</td>
+	</tr>
+	</c:forEach>
+	</tbody>
+</table>
+
+<%@ include file="footer.inc" %>

timemachine-web/src/main/webapp/index.jsp

-<% response.sendRedirect(request.getContextPath() + "/job-list"); %>
+<% response.sendRedirect(request.getContextPath() + "/jobs"); %>

timemachine-web/src/main/webapp/job-detail-jobdef.jsp

-<%@ include file="header.inc" %>
-
-<h2>Job Definition</h2>
-
-<table>
-	<tr>
-		<td>Id</td><td>${ jobDef.id }</td>
-	</tr>
-	<tr>
-		<td>Name</td><td>${ jobDef.name }</td>
-	</tr>
-	<tr>
-		<td>JobTask Class Name</td><td>${ jobDef.jobTaskClassName }</td>
-	</tr>
-	<tr>
-		<td>Schedule Count</td><td>${fn:length(jobDef.schedules) }</td>
-	</tr>
-	<c:forEach items="${ jobDef.props }" var="item">
-	<tr>
-		<td>Props: <code>${item.key}</code></td><td><code>${item.value}</code></td>
-	</tr>
-	</c:forEach>
-</table>
-
-<h2>Schedules</h2>
-<c:forEach items="${ jobDef.schedules }" var="schedule">
-<table>
-	<tr>
-		<td>Id</td><td>${ schedule.id }</td>
-	</tr>
-	<tr>
-		<td>Name</td><td>${ schedule.name }</td>
-	</tr>
-	<tr>
-		<td>Type</td><td>${ schedule['class'].name }</td>
-	</tr>