Project

General

Profile

1
/**
2
 *  '$RCSfile$'
3
 *    Purpose: A Class that handles scheduling tasks 
4
 *  Copyright: 2009 Regents of the University of California and the
5
 *             National Center for Ecological Analysis and Synthesis
6
 *    Authors: Michael Daigle
7
 * 
8
 *   '$Author: daigle $'
9
 *     '$Date: 2009-03-25 13:41:15 -0800 (Wed, 25 Mar 2009) $'
10
 * '$Revision: 4861 $'
11
 *
12
 * This program is free software; you can redistribute it and/or modify
13
 * it under the terms of the GNU General Public License as published by
14
 * the Free Software Foundation; either version 2 of the License, or
15
 * (at your option) any later version.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
 * GNU General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU General Public License
23
 * along with this program; if not, write to the Free Software
24
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
25
 */
26

    
27
package edu.ucsb.nceas.metacat.scheduler;
28

    
29
import java.text.DateFormat;
30
import java.text.ParseException;
31
import java.util.Arrays;
32
import java.util.Calendar;
33
import java.util.Date;
34
import java.util.Enumeration;
35
import java.util.Hashtable;
36
import java.util.Vector;
37

    
38
import javax.servlet.http.HttpServletRequest;
39
import javax.servlet.http.HttpServletResponse;
40

    
41
import org.apache.log4j.Logger;
42

    
43
import org.quartz.Job;
44
import org.quartz.JobDetail;
45
import org.quartz.Scheduler;
46
import org.quartz.SchedulerException;
47
import org.quartz.SchedulerFactory;
48
import org.quartz.Trigger;
49
import org.quartz.TriggerUtils;
50

    
51
import edu.ucsb.nceas.metacat.service.BaseService;
52
import edu.ucsb.nceas.metacat.service.ServiceException;
53
import edu.ucsb.nceas.shared.AccessException;
54

    
55
public class SchedulerService extends BaseService {
56
	
57
	private static SchedulerService schedulerService = null;
58
	
59
	private static Logger logMetacat = Logger.getLogger(SchedulerService.class);
60
	
61
	private static Scheduler sched = null;
62
	
63
	static Hashtable<String, String> jobClassNames = new Hashtable<String, String>();
64
	static {
65
		jobClassNames.put("workflow", "edu.ucsb.nceas.metacat.workflow.WorkflowJob");
66
	}
67

    
68
	/**
69
	 * private constructor since this is a singleton
70
	 */
71
	private SchedulerService() throws ServiceException {}
72
	
73
	/**
74
	 * Get the single instance of SchedulerService.
75
	 * 
76
	 * @return the single instance of SchedulerService
77
	 */
78
	public static SchedulerService getInstance() throws ServiceException {
79
		if (schedulerService == null) {
80
			schedulerService = new SchedulerService();
81
		}
82
		return schedulerService;
83
	}
84
	
85
	public boolean refreshable() {
86
		return true;
87
	}
88
	
89
	protected void doRefresh() throws ServiceException {
90
		stop();
91
	}
92

    
93
	protected void start() throws ServiceException {
94
		try {
95
			SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
96
			
97
			sched = schedFact.getScheduler();
98
			// read jobs from db here.
99
			// then add jobs to sched.
100
			sched.start();
101
		} catch (SchedulerException se) {
102
			throw new ServiceException("Could not start scheduler: " + se.getMessage());
103
		}		
104
	}
105
	
106
	protected void stop() throws ServiceException {
107
		try {
108
			sched.shutdown(true);
109
			sched = null;
110
		} catch (SchedulerException se) {
111
			throw new ServiceException("Could not shut down scheduler: " + se.getMessage());
112
		}		
113
	}
114
	
115
	protected Vector<String> getStatus() throws ServiceException {
116
		return new Vector<String>();
117
	}
118
	
119
	public String scheduleHandler(Hashtable<String, String[]> params,
120
            HttpServletRequest request, HttpServletResponse response,
121
            String username, String[] groups) throws ServiceException {
122
        
123
		String returnString = "";
124
        String[] categories = params.get("category");
125
        if (categories == null || categories.length == 0) {
126
        	throw new ServiceException("SchedulerService.scheduleHandler - Category field " 
127
        			+ "must be populated in scheduler parameters");
128
        }
129
        
130
        String category = categories[0];
131
        
132
        if (category.equals("schedule")) {
133
    		Calendar startCal = null;
134
            Hashtable<String, String> jobParams = new Hashtable<String, String>();
135
        	
136
    		String jobTypes[] = params.get("type");
137
            if (jobTypes == null || jobTypes.length == 0) {
138
            	throw new ServiceException("SchedulerService.scheduleJob - Type field must be populated " 
139
            			+ "in scheduler parameters when scheduling job.");
140
            }
141
    		
142
            String delays[] = params.get("delay");
143
    		String startTimes[] = params.get("starttime");       
144
            try {
145
    			if (delays != null && delays.length > 0) {
146
    				startCal = getStartDateFromDelay(delays[0]);
147
    			} else if (startTimes != null && startTimes.length > 0) {
148
    				Date startDate = DateFormat.getInstance().parse(startTimes[0]);
149
    				startCal = Calendar.getInstance();
150
    				startCal.setTime(startDate);
151
    			} else {
152
    				throw new ServiceException("SchedulerService.scheduleJob -  Either delay or starttime  " 
153
    						+ "field must be populated in scheduler parameters when scheduling job.");
154
    			}
155
    		} catch (ParseException pe) {
156
    			throw new ServiceException("SchedulerService.scheduleJob - Could not parse start time " 
157
    					+ startTimes[0] + " : " + pe.getMessage());
158
    		}
159
            
160
            String intervals[] = params.get("interval"); 
161
            if (intervals == null || intervals.length == 0) {
162
            	throw new ServiceException("SchedulerService.scheduleJob - Interval field must be populated " 
163
            			+ "in scheduler parameters when scheduling job.");
164
            } 
165
            String interval = intervals[0];
166
            
167
            Enumeration<String> paramNames = params.keys();
168
            while (paramNames.hasMoreElements()) {
169
            	String paramName = paramNames.nextElement();
170
            	if (paramName.startsWith("jobparam_")) {
171
            		jobParams.put(paramName.substring(8), params.get(paramName)[0]);
172
            	}
173
            }
174
                   
175
            String jobType = jobTypes[0];
176
            String jobName = jobType + Calendar.getInstance().getTimeInMillis();
177
        	
178
        	returnString = scheduleJob(jobName, startCal, interval, jobType, jobParams);
179
        } else if (category.equals("unschedule")) {
180
        	String jobNames[] = params.get("jobName");
181
        	if (jobNames == null || jobNames.length == 0) {
182
        		throw new ServiceException("SchedulerService.scheduleJob - Job ID field must be populated " 
183
            			+ "in scheduler parameters when unscheduling job.");
184
        	}
185
        	        	
186
        	unscheduleJob(jobNames[0]);
187
        } else if (category.equals("delete")) {
188
//        	deleteJob(params);
189
        }
190
        
191
        return returnString;
192
	}
193
	
194
	public String scheduleJob(String jobName, Calendar startCal, String interval,
195
			String jobType, Hashtable<String, String> jobParams) throws ServiceException {
196

    
197
        Class<Job> jobClass = null;
198
       
199
        String className = jobClassNames.get(jobType);
200
        if (className == null) {
201
        	throw new ServiceException("SchedulerService.scheduleJob - Type: " + jobType 
202
        			+ " is not registered as a valid job type");
203
        }
204
        
205
        logMetacat.info("Scheduling job -- name: " + jobName + ", class: " + className 
206
        		+ ", start time: " + startCal.toString() + ", interval: " + interval);
207
       	 
208
        try {        	
209
        	jobClass = (Class<Job>)Class.forName(jobType + "Job");     	
210
        } catch (ClassNotFoundException cnfe) {
211
        	throw new ServiceException("SchedulerService.scheduleJob - Could not find class with name: " 
212
        			+ jobType + "Job");
213
        }    
214
        
215
		char intervalUnit = interval.trim().charAt(interval.length());
216
		String intervalStrValue = interval.trim().substring(0, interval.length() - 1);
217
		int intervalValue;
218
		try {
219
			intervalValue = Integer.parseInt(intervalStrValue);
220
		} catch (NumberFormatException nfe) {
221
			throw new ServiceException("SchedulerService.scheduleJob - Could not parse interval value " 
222
					+ "into an integer: " + intervalStrValue);
223
		}
224
		
225
		switch (intervalUnit) {
226
		case 's':
227
		case 'S':
228
			scheduleSecondlyJob(jobName, jobClass, startCal, intervalValue, jobType);
229
			break;
230
		case 'm':
231
		case 'M':
232
			scheduleMinutelyJob(jobName, jobClass, startCal, intervalValue, jobType);
233
			break;
234
		case 'h':
235
		case 'H':
236
			scheduleHourlyJob(jobName, jobClass, startCal, intervalValue, jobType);
237
			break;
238
		case 'd':
239
		case 'D':
240
			scheduleDailyJob(jobName, jobClass, startCal, intervalValue, jobType);
241
			break;
242
		default:
243
			throw new ServiceException("SchedulerService.scheduleJob - Could not interpret interval unit: " 
244
					+ intervalUnit + ". Unit must be s, m, h or d");	
245
		}	
246
		
247
		try {
248
			ScheduledJobAccess jobAccess = new ScheduledJobAccess();
249
			jobAccess.createJob(jobName, jobClass, startCal, intervalValue, jobParams);
250
		} catch (AccessException ae) {
251
			try {
252
				deleteJob(jobName);
253
			} catch (ServiceException se) {
254
				// Not much we can do here but log this
255
				logMetacat.error("An access exception was thrown when writing job: " + jobName 
256
						+ "to the db, and another exception was thrown when trying to remove the " 
257
						+ "job from the scheduler.  The db and scheduler may be out of sync");
258
			}
259
			throw new ServiceException("SchedulerService.scheduleJob - Error accessing db: " 
260
					+ ae.getMessage());
261
		}
262
		
263
		return jobName;
264
	}
265
	
266
	public void unscheduleJob(String jobName) throws ServiceException {
267
		try {
268
			ScheduledJobAccess jobAccess = new ScheduledJobAccess();
269
			ScheduledJobDAO jobDAO = jobAccess.getJobByName(jobName);
270

    
271
			sched.unscheduleJob(jobDAO.getName(), jobDAO.getGroupName());
272

    
273
			jobDAO.setStatus(ScheduledJobInterface.STATE_UNSHCEDULED);
274
			jobAccess.updateJobStatus(jobDAO);
275
		} catch (SchedulerException se) {
276
			throw new ServiceException("SchedulerService.unscheduleJob - Could not create "
277
							+ "scheduled job because of service issue: " + se.getMessage());
278
		} catch (AccessException ae) {
279
			throw new ServiceException("SchedulerService.unscheduleJob - Could not create "
280
							+ "scheduled job because of db access issue: " + ae.getMessage());
281
		}
282
	}
283
	
284
	public void deleteJob(String jobName) throws ServiceException {
285
		String groupName = "";
286
		
287
		try {
288
			ScheduledJobAccess jobAccess = new ScheduledJobAccess();
289
			ScheduledJobDAO jobDAO = jobAccess.getJobByName(jobName);
290
			groupName = jobDAO.getGroupName();
291
			
292
			sched.deleteJob(jobName, groupName);
293
		} catch (SchedulerException se) {
294
			throw new ServiceException("SchedulerService.deleteJob - Could not delete job: " 
295
					+ jobName + " for group: " + groupName + " : " + se.getMessage());
296
		} catch (AccessException ae) {
297
			throw new ServiceException("SchedulerService.deleteJob - Could not delete "
298
					+ "scheduled job because of db access issue: " + ae.getMessage());
299
}
300
	}
301
	
302
	public Vector<String> getJobInfo(String groupName) throws ServiceException {
303
		Vector<String> returnVector = new Vector<String>();
304
		
305
		try {
306
			if (groupName.equalsIgnoreCase("All")) {
307
				String[] groupNames = sched.getJobGroupNames();
308
				for (int i = 0; i < groupNames.length; i++) {
309
					returnVector.addAll(Arrays.asList(sched.getJobNames(groupNames[i])));
310
				}
311
			} else {
312
				returnVector.addAll(Arrays.asList(sched.getJobNames(groupName)));
313
			}
314
		} catch (SchedulerException se) {
315
			throw new ServiceException("SchedulerService.getJobNames - Could not get job names for group: " 
316
					+ groupName + " : " + se.getMessage());
317
		}
318

    
319
		return returnVector;
320
	}
321
	
322
	private void scheduleSecondlyJob(String name, Class<Job> jobClass, Calendar startTime, int interval, String jobGroup) throws ServiceException {
323
		JobDetail jobDetail = new JobDetail(name, jobGroup, jobClass);
324

    
325
		Trigger trigger = TriggerUtils.makeSecondlyTrigger(interval);
326
		trigger.setName(name);
327
		trigger.setStartTime(startTime.getTime());
328

    
329
		try {
330
			sched.scheduleJob(jobDetail, trigger);
331
		} catch (SchedulerException se) {
332
			throw new ServiceException("SchedulerService.scheduleSecondlyJob - Could not create " 
333
					+ "scheduler: " + se.getMessage());
334
		}
335
	}
336
	
337
	private void scheduleMinutelyJob(String name, Class<Job> jobClass, Calendar startTime, int interval, String jobGroup) throws ServiceException {
338
		JobDetail jobDetail = new JobDetail(name, jobGroup, jobClass);
339

    
340
		Trigger trigger = TriggerUtils.makeMinutelyTrigger(interval);
341
		trigger.setName(name);
342
		trigger.setStartTime(startTime.getTime());
343

    
344
		try {
345
			sched.scheduleJob(jobDetail, trigger);
346
		} catch (SchedulerException se) {
347
			throw new ServiceException("SchedulerService.scheduleMinutelyJob - Could not create " 
348
					+ "scheduler: " + se.getMessage());
349
		}
350
	}
351
	
352
	private void scheduleHourlyJob(String name, Class<Job> jobClass, Calendar startTime, int interval, String jobGroup) throws ServiceException {
353
		JobDetail jobDetail = new JobDetail(name, jobGroup, jobClass);
354

    
355
		Trigger trigger = TriggerUtils.makeHourlyTrigger(interval);
356
		trigger.setName(name);
357
		trigger.setStartTime(startTime.getTime());
358

    
359
		try {
360
			sched.scheduleJob(jobDetail, trigger);
361
		} catch (SchedulerException se) {
362
			throw new ServiceException("SchedulerService.scheduleHourlyJob - Could not create " 
363
					+  "scheduler: " + se.getMessage());
364
		}
365
	}
366
	
367
	private void scheduleDailyJob(String name, Class<Job> jobClass, Calendar startTime, int interval, String jobGroup) throws ServiceException {
368

    
369
		JobDetail jobDetail = new JobDetail(name, jobGroup, jobClass);
370

    
371
		Trigger trigger = TriggerUtils.makeHourlyTrigger(interval * 24);
372
		trigger.setName(name);
373
		trigger.setStartTime(startTime.getTime());
374

    
375
		try {
376
			sched.scheduleJob(jobDetail, trigger);
377
		} catch (SchedulerException se) {
378
			throw new ServiceException("SchedulerService.scheduleHourlyJob - Could not create " 
379
					+ "scheduler: " + se.getMessage());
380
		}
381
	}
382
	
383

    
384
	
385
	private Calendar getStartDateFromDelay(String delay) throws ServiceException {
386
		Calendar cal = Calendar.getInstance();
387
	
388
		char delayUnit = delay.trim().charAt(delay.length());
389
		String delayStrValue = delay.trim().substring(0, delay.length() - 1);
390
		int delayValue;
391
		try {
392
			delayValue = Integer.parseInt(delayStrValue);
393
		} catch (NumberFormatException nfe) {
394
			throw new ServiceException("SchedulerService.getStartDateFromDelay - Could not " 
395
					+ "parse delay value into an integer: " + delayStrValue);
396
		}
397
		
398
		switch (delayUnit) {
399
		case 's':
400
		case 'S':
401
			cal.add(Calendar.SECOND, delayValue);
402
			break;
403
		case 'm':
404
		case 'M':
405
			cal.add(Calendar.MINUTE, delayValue);
406
			break;
407
		case 'h':
408
		case 'H':
409
			cal.add(Calendar.HOUR, delayValue);
410
			break;
411
		case 'd':
412
		case 'D':
413
			cal.add(Calendar.DAY_OF_YEAR, delayValue);
414
			break;
415
		default:
416
			throw new ServiceException("SchedulerService.getStartDateFromDelay - Could not " 
417
					+ "interpret delay unit: " + delayUnit + ". Unit must be s, m, h or d");	
418
		}
419
		
420
		return cal;
421
	}
422
	
423

    
424

    
425
	
426
	
427
	
428
}
(6-6/6)