/* * Copyright 2001-2009 Terracotta, Inc. * * 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 org.quartz.jobs; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quartz.Job; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; /** * <p> Built in job for executing native executables in a separate process.</p> * * <pre> * JobDetail job = new JobDetail("dumbJob", null, org.quartz.jobs.NativeJob.class); * job.getJobDataMap().put(org.quartz.jobs.NativeJob.PROP_COMMAND, "echo \"hi\" >> foobar.txt"); * Trigger trigger = TriggerUtils.makeSecondlyTrigger(5); * trigger.setName("dumbTrigger"); * sched.scheduleJob(job, trigger); * </pre> * * If PROP_WAIT_FOR_PROCESS is true, then the Integer exit value of the process * will be saved as the job execution result in the JobExecutionContext. * * @see #PROP_COMMAND * @see #PROP_PARAMETERS * @see #PROP_WAIT_FOR_PROCESS * @see #PROP_CONSUME_STREAMS * * @author Matthew Payne * @author James House * @author Steinar Overbeck Cook */ public class NativeJob implements Job { private final Logger log = LoggerFactory.getLogger(getClass()); /* *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Constants. * *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** * Required parameter that specifies the name of the command (executable) * to be ran. */ public static final String PROP_COMMAND = "command"; /** * Optional parameter that specifies the parameters to be passed to the * executed command. */ public static final String PROP_PARAMETERS = "parameters"; /** * Optional parameter (value should be 'true' or 'false') that specifies * whether the job should wait for the execution of the native process to * complete before it completes. * * <p>Defaults to <code>true</code>.</p> */ public static final String PROP_WAIT_FOR_PROCESS = "waitForProcess"; /** * Optional parameter (value should be 'true' or 'false') that specifies * whether the spawned process's stdout and stderr streams should be * consumed. If the process creates output, it is possible that it might * 'hang' if the streams are not consumed. * * <p>Defaults to <code>false</code>.</p> */ public static final String PROP_CONSUME_STREAMS = "consumeStreams"; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Interface. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ public void execute(JobExecutionContext context) throws JobExecutionException { JobDataMap data = context.getMergedJobDataMap(); String command = data.getString(PROP_COMMAND); String parameters = data.getString(PROP_PARAMETERS); if (parameters == null) { parameters = ""; } boolean wait = true; if(data.containsKey(PROP_WAIT_FOR_PROCESS)) { wait = data.getBooleanValue(PROP_WAIT_FOR_PROCESS); } boolean consumeStreams = false; if(data.containsKey(PROP_CONSUME_STREAMS)) { consumeStreams = data.getBooleanValue(PROP_CONSUME_STREAMS); } Integer exitCode = this.runNativeCommand(command, parameters, wait, consumeStreams); context.setResult(exitCode); } protected Logger getLog() { return log; } private Integer runNativeCommand(String command, String parameters, boolean wait, boolean consumeStreams) throws JobExecutionException { String[] cmd; String[] args = new String[2]; Integer result = null; args[0] = command; args[1] = parameters; try { //with this variable will be done the swithcing String osName = System.getProperty("os.name"); // specific for Windows if (osName.startsWith("Windows")) { cmd = new String[args.length + 2]; if (osName.equals("Windows 95")) { // windows 95 only cmd[0] = "command.com"; } else { cmd[0] = "cmd.exe"; } cmd[1] = "/C"; System.arraycopy(args, 0, cmd, 2, args.length); } else if (osName.equals("Linux")) { cmd = new String[3]; cmd[0] = "/bin/sh"; cmd[1] = "-c"; cmd[2] = args[0] + " " + args[1]; } else { // try this... cmd = args; } Runtime rt = Runtime.getRuntime(); // Executes the command getLog().info("About to run " + cmd[0] + " " + cmd[1] + " " + (cmd.length>2 ? cmd[2] : "") + " ..."); Process proc = rt.exec(cmd); // Consumes the stdout from the process StreamConsumer stdoutConsumer = new StreamConsumer(proc.getInputStream(), "stdout"); // Consumes the stderr from the process if(consumeStreams) { StreamConsumer stderrConsumer = new StreamConsumer(proc.getErrorStream(), "stderr"); stdoutConsumer.start(); stderrConsumer.start(); } if(wait) { result = proc.waitFor(); } // any error message? } catch (Throwable x) { throw new JobExecutionException("Error launching native command: ", x, false); } return result; } /** * Consumes data from the given input stream until EOF and prints the data to stdout * * @author cooste * @author jhouse */ class StreamConsumer extends Thread { InputStream is; String type; /** * */ public StreamConsumer(InputStream inputStream, String type) { this.is = inputStream; this.type = type; } /** * Runs this object as a separate thread, printing the contents of the InputStream * supplied during instantiation, to either stdout or stderr */ @Override public void run() { BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader(is)); String line; while ((line = br.readLine()) != null) { if(type.equalsIgnoreCase("stderr")) { getLog().warn(type + ">" + line); } else { getLog().info(type + ">" + line); } } } catch (IOException ioe) { getLog().error("Error consuming " + type + " stream of spawned process.", ioe); } finally { if(br != null) { try { br.close(); } catch(Exception ignore) {} } } } } }