| « Java Swing + Browser + Flash + Mediaplayer + ... | Time Warp mit dem Quartz Scheduler » |
Quartz: 3 Beispiele
Nachdem ich in der Einführung schon den SystemJob-Code genannt habe, folgen nun noch drei weitere:
- Webseiteninhalt per E-Mail senden
- Datei-Download per FTP
- Rekursiver Verzeichnis-Download per FTP
Fortsetzung:
Webseiteninhalt per E-Mail senden
Dieser Job lädt eine Webseite (wahlweise über einen Proxyserver) und schickt den HTML-Inhalt ohne Bilddaten per E-Mail an die angegebene Adresse. Das ist praktisch, wenn man z.B. Datenbankauswertungen oder administrative Abläufe in PHP programmiert hat, diese regelmäßig durch einen HTTP-Request starten und das Ergebnis im Posteingang sehen möchte.
Quartz wird mit allen Libraries geliefert, um Mails zu senden. Das Senden selbst ist sogar ein fertiger Job (org.quartz.jobs.ee.mail/SendMailJob), ich habe mich hier aber für eine eigene, statische Methode für den Mailversand entschieden:
Code:
/* | |
* Email.java | |
*/ | |
| |
package de.jrthies.launchpad.util; | |
| |
import java.util.Properties; | |
import javax.mail.Message; | |
import javax.mail.MessagingException; | |
import javax.mail.Session; | |
import javax.mail.Transport; | |
import javax.mail.internet.InternetAddress; | |
import javax.mail.internet.MimeMessage; | |
| |
| |
/** | |
* Klasse zum Senden von E-Mails via SMTP | |
* @author Jean-René Thies | |
*/ | |
public class Email { | |
| |
/** | |
* Mail senden | |
*/ | |
public static void sendmail(String recipient, String subject, String message, String from) throws MessagingException { | |
Properties props = new Properties(); | |
props.put("mail.smtp.host", "172.1.1.1"); // Anpassen! | |
Session session = Session.getDefaultInstance(props); | |
Message msg = new MimeMessage(session); | |
InternetAddress addressFrom = new InternetAddress(from); | |
msg.setFrom(addressFrom); | |
InternetAddress addressTo = new InternetAddress(recipient); | |
msg.setRecipient(Message.RecipientType.TO, addressTo); | |
msg.setSubject(subject); | |
msg.setContent(message, "text/html"); | |
Transport.send(msg); | |
} | |
} |
In Zeile 27 muss lediglich ein verfügbarer SMTP-Server eingetragen werden, und schon kann's losgehen.
Hier die Job-Klasse für Quartz:
Code:
/* | |
* SendWebpageJob.java | |
*/ | |
| |
package de.jrthies.launchpad.jobs; | |
| |
import de.jrthies.launchpad.util.Email; | |
import java.io.BufferedReader; | |
import java.io.InputStreamReader; | |
import java.net.URL; | |
import java.net.URLConnection; | |
import org.quartz.Job; | |
import org.quartz.JobDataMap; | |
import org.quartz.JobExecutionContext; | |
import org.quartz.JobExecutionException; | |
| |
/** | |
* Webseite per URL laden und als HTML E-Mail senden | |
* @author Jean-René Thies | |
*/ | |
public class SendWebpageJob implements Job { | |
| |
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { | |
JobDataMap dataMap = jobExecutionContext.getJobDetail().getJobDataMap(); | |
String url = dataMap.getString("URL"); | |
boolean extern = dataMap.getBooleanFromString("istExtern"); | |
String sendTo = dataMap.getString("sendTo"); | |
download(url, sendTo, extern); | |
} | |
| |
/** | |
* Download- und Sende-Methode inkl. optionaler Proxy-Property. | |
*/ | |
private void download(String getUrl, String sendTo, boolean externalURL) { | |
try { | |
if (externalURL) { | |
System.setProperty("proxySet", "true"); | |
System.setProperty("proxyHost", "yourproxy"); | |
System.setProperty("proxyPort", "8080"); | |
} | |
URL url = new URL(getUrl); | |
URLConnection conn = url.openConnection(); | |
// Get the response | |
BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream())); | |
StringBuffer pages = new StringBuffer(); | |
String line; | |
while ((line = rd.readLine()) != null) { | |
page.append(line); | |
} | |
rd.close(); | |
System.out.println("Sende " + getUrl + " an " + sendTo); | |
Email.sendmail(sendTo, getUrl, pages.toString(), "LaunchPad"); | |
} catch (Exception ex) { | |
ex.printStackTrace(); | |
} | |
} | |
} |
Wenn ein Proxyserver vorhanden ist, muss er in Zeile 38 eingetragen sein. In Zeile 52 kann auch der Subject-String "LaunchPad" individuell angepasst werden.
An den Einträgen der JobDataMap erkennt man, dass jeder SendWebpageJob eine Konfiguration benötigt. Die kann z.B. so aussehen:
Code:
JobDetail job = new JobDetail(txtJobName.getText(), sched.DEFAULT_GROUP, SendWebpageJob.class); | |
job.setDescription(txtJobBeschreibung.getText()); | |
job.setDurability(checkDurable.isSelected()); | |
//Job-Konfiguration | |
job.getJobDataMap().put("URL", txtURL.getText()); | |
job.getJobDataMap().put("sendTo", txtMailto.getText()); | |
job.getJobDataMap().putAsString("istExtern", checkProxy.isSelected()); |
Hier habe ich einfach alle Parameter aus GUI-Komponenten eines Dialogs ausgelesen. Anschließend folgt eine Trigger-Definition und der Start im Scheduler, wie bereits in der Einführung demonstriert.
Datei-Download per FTP
Für FTP-Transfers brauchen wir zwei weitere Libraries: Apache Commons Net und Jakarta ORO.
Ein Job für den einfachen Download einer Datei per FTP kann dann so aussehen:
Code:
/* | |
* FTPDownloadFileJob.java | |
*/ | |
| |
package de.jrthies.launchpad.jobs; | |
| |
import java.io.File; | |
import java.io.FileOutputStream; | |
import org.apache.commons.net.ftp.FTPClient; | |
import org.apache.commons.net.ftp.FTPFile; | |
import org.quartz.Job; | |
import org.quartz.JobDataMap; | |
import org.quartz.JobExecutionContext; | |
import org.quartz.JobExecutionException; | |
| |
/** | |
* Einzelne Datei vom FTP-Server laden. | |
* @author Jean-René Thies | |
*/ | |
public class FTPDownloadFileJob implements Job { | |
| |
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { | |
JobDataMap dataMap = jobExecutionContext.getJobDetail().getJobDataMap(); | |
String host = dataMap.getString("host"); | |
String loginname = dataMap.getString("loginname"); | |
String loginpassword = dataMap.getString("loginpassword"); | |
String remotedir = dataMap.getString("remotedir"); | |
String remotefile = dataMap.getString("remotefile"); | |
String localdir = dataMap.getString("localdir"); | |
if (!localdir.endsWith("/")) { | |
localdir = localdir + "/"; | |
} | |
File dir = new File(localdir); | |
if (!dir.exists()) { | |
dir.mkdir(); | |
} | |
boolean deleteRemote = dataMap.getBooleanFromString("deleteRemoteFile"); | |
| |
download(host, loginname, loginpassword, remotedir, remotefile, localdir, deleteRemote); | |
} | |
| |
private void download(String host, String loginname, String loginpassword, String remotedir, String remotefile, | |
String localdir, boolean deleteRemote) { | |
try { | |
FTPClient f = new FTPClient(); | |
f.connect(host); | |
f.login(loginname, loginpassword); | |
f.changeWorkingDirectory(remotedir); | |
f.setFileType(f.BINARY_FILE_TYPE); | |
// Ist die Datei verfügbar? | |
FTPFile[] files = f.listFiles(); | |
for (int i = 0; i [lt] files.length; i++) { | |
FTPFile file = files[i]; | |
if (file.getName().equals(remotefile) [amp][amp] file.isFile()) { | |
//Download | |
File localfile = new File(localdir + remotefile); | |
FileOutputStream fos = new FileOutputStream(localfile); | |
f.retrieveFile(remotefile, fos); | |
fos.close(); | |
System.out.println("Download complete"); | |
if (deleteRemote) { | |
f.deleteFile(remotefile); | |
} | |
} | |
} | |
//Verbindung trennen | |
f.logout(); | |
f.disconnect(); | |
} catch (Exception ex) { | |
ex.printStackTrace(); | |
} | |
} | |
} |
Auch hier gibt es wieder eine JobDataMap zur Konfiguration. Sie muss neben dem FTP-Host, dem Login und einer Verzeichnisangabe noch den Dateinamen und ein lokales Download-Verzeichnis enthalten. Bei Bedarf kann man die Proxy-Einstellung noch einbauen.
Immer wieder die gleiche Datei herunterzuladen, ist aber nur in wenigen Fällen sinnvoll. Daher noch eine kleine Verbesserung:
Rekursiver Verzeichnis-Download per FTP
Die nachfolgende Job-Klasse ist etwas cleverer: Sie scannt ein FTP-Verzeichnis mit allen darin befindlichen Unterverzeichnissen. Für die Unterverzeichnisse werden weitere Jobs der gleichen Klasse gestartet. Das Interface "StatefulJob" stellt sicher, dass die Downloads sukzessive erfolgen. Jobs und Trigger sind jeweils so eingestellt, dass sie nachgeholt werden, wenn der Startzeitpunkt durch andere Downloads blockiert war. Und last but not least starten Downloads nur für die Dateien, die lokal noch nicht vorhanden sind.
Code:
/* | |
* FTPDownloadDirectoryJob.java | |
*/ | |
| |
package de.jrthies.launchpad.jobs; | |
| |
import java.io.File; | |
import java.io.FileOutputStream; | |
import java.util.Calendar; | |
import java.util.Date; | |
import org.apache.commons.net.ftp.FTPClient; | |
import org.apache.commons.net.ftp.FTPFile; | |
import org.quartz.JobDataMap; | |
import org.quartz.JobDetail; | |
import org.quartz.JobExecutionContext; | |
import org.quartz.JobExecutionException; | |
import org.quartz.Scheduler; | |
import org.quartz.SimpleTrigger; | |
import org.quartz.StatefulJob; | |
import org.quartz.impl.StdSchedulerFactory; | |
| |
/** | |
* Verzeichnisinhalt vom FTP-Server laden. | |
* Weitere Unterverzeichnisse werden nicht rekursiv geladen! | |
* @author Jean-René Thies | |
*/ | |
public class FTPDownloadDirectoryJob implements StatefulJob { | |
| |
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { | |
JobDataMap dataMap = jobExecutionContext.getJobDetail().getJobDataMap(); | |
String host = dataMap.getString("host"); | |
String loginname = dataMap.getString("loginname"); | |
String loginpassword = dataMap.getString("loginpassword"); | |
String remotedir = dataMap.getString("remotedir"); | |
String localdir = dataMap.getString("localdir"); | |
if (!localdir.endsWith("/")) { | |
localdir = localdir + "/"; | |
} | |
File dir = new File(localdir); | |
if (!dir.exists()) { | |
dir.mkdir(); | |
} | |
boolean deleteRemote = dataMap.getBooleanFromString("deleteRemoteFile"); | |
| |
download(host, loginname, loginpassword, remotedir, localdir, deleteRemote); | |
} | |
| |
private void download(String host, String loginname, String loginpassword, String remotedir, String localdir, boolean deleteRemote) { | |
try { | |
FTPClient f = new FTPClient(); | |
f.connect(host); | |
f.login(loginname, loginpassword); | |
f.changeWorkingDirectory(remotedir); | |
f.setFileType(f.BINARY_FILE_TYPE); | |
FTPFile[] files = f.listFiles(); | |
for (int i = 0; i [lt] files.length; i++) { | |
FTPFile remotefile = files[i]; | |
File localfile = new File(localdir + remotefile.getName()); | |
//Nur Dateien laden, die seit mindestens fünf Minuten auf dem Server | |
//und lokal noch nicht vorhanden sind. | |
Calendar remotefiledate = remotefile.getTimestamp(); | |
Calendar compare = Calendar.getInstance(); | |
compare.setTime(new Date()); | |
compare.add(Calendar.MINUTE, -5); | |
if (remotefile.isFile() [amp][amp] remotefiledate.before(compare) [amp][amp] !localfile.exists()) { | |
//Download | |
FileOutputStream fos = new FileOutputStream(localfile); | |
f.retrieveFile(remotefile.getName(), fos); | |
fos.close(); | |
System.out.println("Download complete"); | |
if (deleteRemote) { | |
f.deleteFile(remotefile.getName()); | |
} | |
} else if (remotefile.isDirectory() [amp][amp] !remotefile.getName().startsWith(".")) { | |
//Rekursiv neue Jobs anlegen | |
if (!remotedir.endsWith("/")) { | |
remotedir = remotedir + "/"; | |
} | |
String remotesubdir = remotedir + remotefile.getName(); | |
String localsubdir = localdir + remotefile.getName(); | |
Scheduler sched = StdSchedulerFactory.getDefaultScheduler(); | |
JobDetail job = | |
new JobDetail(remotesubdir + " laden", sched.DEFAULT_GROUP, | |
FTPDownloadDirectoryJob.class); | |
job.setRequestsRecovery(true); | |
//Job-Konfiguration | |
job.getJobDataMap().put("host", host); | |
job.getJobDataMap().put("loginname", loginname); | |
job.getJobDataMap().put("loginpassword", loginpassword); | |
job.getJobDataMap().put("remotedir", remotesubdir); | |
job.getJobDataMap().put("localdir", localsubdir); | |
job.getJobDataMap().putAsString("deleteRemoteFile", deleteRemote); | |
//Trigger-Konfiguration | |
long startTime = System.currentTimeMillis() + 3000L; | |
SimpleTrigger trigger = | |
new SimpleTrigger(remotesubdir + "Trigger", sched.DEFAULT_GROUP, new Date(startTime), null, 0, 0L); | |
trigger.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW); | |
//Job starten | |
sched.scheduleJob(job, trigger); | |
} | |
} | |
//Verbindung trennen | |
f.logout(); | |
f.disconnect(); | |
} catch (Exception ex) { | |
ex.printStackTrace(); | |
} | |
} | |
} |
Die Calendar-Prüfung in den Zeilen 61-65 dient dazu, nur die Dateien zu laden, die seit mindestens fünf Minuten unverändert auf dem Server liegen. Ansonsten würden auch Downloads von Dateien starten, die gerade von anderer Stelle aus zum Server übertragen werden.