Wie bemerke ich, dass die ausserordentlich wichtige Datei xyz nicht vorhanden ist? Wie bemerke ich, dass eine andere Datei ABC heute nicht neu erzeugt wurde? Wie bemerke ich, dass sich ein Wert in einer SQL-Datenbank ändert? Diese und ähnlich Fragen stellen sich immer wieder.
SCOM könnte hier Antworten geben und die Aufgabe fiele daher in den Beritt der Kollegen von der Systemtruppe. Aber mein Chef möchte kurze Wege: Hausinterne Programme erzeugen und verändern Dateien. Also kümmert sich das Programmier-Team um deren Überwachung.
Skizze
Meine Idee dazu ist die: Ich schaffe eine Java-Klasse, die prüfen kann, ob eine Datei existiert. Für n Prüfungen jeweils unterschiedlicher Dateien instanziiere ich diese Klasse n mal mit jeweils unterschiedlichen Parametern. Ich schaffe eine weitere Java-Klasse, die prüfen kann, ob das Erzeugungsdatum einer Datei passt. Für n Prüfungen … s.o. Und ich schreibe eine weitere Java-Klasse für die Prüfung in Zusammenhang mit einer SQL-Datenbank.
Allgemein formuliert: Jede Prüfung wird in einer getrennten Java-Klasse abgebildet.
Wie erzeuge ich die Prüfungs-Objekte?
Wenn ich die Objekte jeweils mit dem new-Operator erzeuge, dann handel ich mir einen Nachteil ein: Jedes Mal, wenn eine neue Prüfung dazu kommt, muss ich den Code verändern, der die Objekte erzeugt. Das ist ein No-Brainer und zieht Verwaltungsaufwand nach sich: Dokumentation, Versionsverwaltung, Deployment. Mir ist es lieber, den Code unberührt zu lassen und nur eine Konfiguration zu ändern.
Gibt es eine andere Möglichkeit, die Objekte zu erzeugen? Ja.
Java macht es möglich, ein Objekt in XML-Form abzubilden. Und der umgekehrte Weg, man bemerke die Symmetrie, ist auch gangbar: Wenn ein entsprechender XML-Text vorhanden ist, dann kann daraus ein Objekt erzeugt werden. Wenn ich den XML-Text verändere, dann verändere ich das potentielle Objekt. So kann ich n unterschiedliche XML-Texte erzeugen, die sich jeweils beispielsweise nur um einen Pfadnamen der zu prüfenden Datei unterscheiden. Das ist prima, weil es eine Konfiguration ist.
Ein Weg, den ich nicht gehe: Es gibt eine Liste mit Dateinamen, für die geprüft werden soll, ob die entsprechenden Dateien vorhanden sind, und ein Programm A liest diese Liste und führt die Prüfungen durch. Es gibt eine weitere Liste mit Dateinamen für die Datumsprüfung und ein Programm B, das diese Liste liest und entsprechende Antworten gibt. Und so weiter für alle geforderten Prüf-Typen.
Dieser Ansatz ist nicht komfortabel, weil ich mit mehr als einer Konfigurationsdatei hantieren muss. Mein Ansatz erlaubt mir eine einzelne Datei, die die Prüfungen steuert.
Code
… ohne Drumherum, damit die Idee deutlich wird.
Command ist sehr allgemein.
1 2 3 4 5 6 7 8 9 |
package de.aysx.monfrere.common; /** * Die allgemeine Form eines Kommandos: Etwas, das man ausführen kann. */ public interface Command { void execute (); } |
FileExistsP war der Ausgangspunkt der Überlegungen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
package de.aysx.monfrere.commands; import java.io.File; import de.aysx.monfrere.common.FileCommand; /** * Prüft, ob die Datei existiert. Der komplette Pfad zu der Datei muss dazu * entweder im Konstruktor angegeben werden oder mittels Setter gesetzt werden. * */ public class FileExistsP extends FileCommand { private static final long serialVersionUID = -6837148941711939270L; public FileExistsP () { // void; } public FileExistsP ( String filename ) { super (); setFilename ( filename ); } @Override public void execute () { File f = new File ( getFilename ()); if ( !f.exists ()){ System.out.println ("File does not exist " + getFilename ()); } } } |
FileCreatedTodayP ist so ähnlich wie FileExistsP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
package de.aysx.monfrere.commands; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import de.aysx.monfrere.common.FileCommand; /** * Prüft, ob die Datei heute erzeugt wurde. Der komplette Pfad zu der Datei * muss dazu entweder im Konstruktor angegeben werden oder * mittels Setter gesetzt werden. * */ public class FileCreatedTodayP extends FileCommand { private static final long serialVersionUID = 4196551307847361695L; public FileCreatedTodayP () { // void; } public FileCreatedTodayP ( String filename ) { super (); setFilename ( filename ); } @Override public void execute () { try { BasicFileAttributes attrs = Files.readAttributes ( Paths.get(getFilename ()), BasicFileAttributes.class); FileTime ft = attrs.creationTime (); Date ftd = new Date ( ft.toMillis ()); String fts = DateFormat.getDateInstance().format(ftd); fts = SimpleDateFormat.getDateInstance ( SimpleDateFormat.SHORT ).format ( ftd ); Date today = new Date (); String todays = SimpleDateFormat.getDateInstance ( SimpleDateFormat.SHORT ).format ( today ); if ( !fts.equals ( todays )){ System.out.println ( "File was not created today " + getFilename ()); } } catch ( IOException e ) { e.printStackTrace(); } } } |
FileCommand gibt es, weil bestimmter Code sich wiederholt.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package de.aysx.monfrere.common; import java.io.Serializable; /** * Die allgemeine Form eines Kommandos, das eine Datei prüft. Wesentlich ist * das Vorhandensein eines Dateinamens. */ public abstract class FileCommand implements Command, Serializable { private static final long serialVersionUID = -6215276259506141326L; private String filename; public String getFilename () { return filename; } public void setFilename ( String filename ) { this.filename = filename; } } |
CommandList ist die Klammer, die die Commands zusammenhält. Ich brauche so aus der XML-Datei nur ein Objekt zu lesen, weil die darin enthaltenen Objekte ebenfalls erzeugt werden.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
package de.aysx.monfrere.commands; import java.io.Serializable; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import de.aysx.monfrere.common.Command; /** * Eine Liste von Commandos. Die "Liste ausführen" bedeutet, jedes der in ihr * enthaltenen Commands auszuführen.<br> * Die Liste wird entweder im Konstruktor übergeben oder gesetzt. * */ public class CommandList implements Command, Serializable { private static final long serialVersionUID = -6027628010846425269L; public CommandList () { // void } public CommandList ( List <Command> l ){ setCommands ( l ); } @Override public void execute () { Iterator < Command > i = getCommands ().iterator (); while ( i.hasNext ()){ i.next ().execute (); } } private List < Command > commands; public List < Command > getCommands () { if ( null == this.commands ){ this.commands = new ArrayList<Command> (); } return this.commands; } public void setCommands ( List < Command > l) { this.commands = l; } } |
Main startet die Prüfungen, die in einer Datei konfiguriert sind.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
package de.aysx.monfrere.controller; import java.beans.XMLDecoder; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import de.aysx.monfrere.common.Command; /** * Nimmt als Argument eine XML-Datei. * Versucht, ein Java-Objekt daraus zu machen und es als Command * auszuführen. */ public class Main { public static void main ( String[] args ) { String xmlinputFile = args[0]; XMLDecoder d; try { d = new XMLDecoder( new BufferedInputStream( new FileInputStream ( xmlinputFile ))); Object result; result = d.readObject (); if ( result instanceof Command ){ ((Command)result).execute (); } else { System.out.println ("No Command in " + xmlinputFile ); } d.close(); } catch ( FileNotFoundException e ) { e.printStackTrace(); } } } |
Hier ist ein Beispiel für eine solche XML-Datei.
Decoder zeigt mir, wie die XML-Darstellung eines Objekts ist.
Nur für den Fall, dass ich das nicht im Kopf ausrechnen kann ;)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
package de.aysx.monfrere.helper; import java.beans.XMLEncoder; import java.io.BufferedOutputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.List; import de.aysx.monfrere.commands.CommandList; import de.aysx.monfrere.commands.FileCreatedTodayP; import de.aysx.monfrere.commands.FileExistsP; import de.aysx.monfrere.common.Command; /** * Diese Hilfsklasse zeigt mir, wie ein Objekt in XML aussieht. Hier werden * Command-Objekte erschaffen und ihre XML-Darstellung jeweils in einer * Datei abgelegt. Die so erzeugten Dateien dienen als Vorlage für die * Konfiguration. */ public class Decoder { public static void main ( String[] args ) { XMLEncoder e; try { FileOutputStream fos = new FileOutputStream ( "FileExistsP.xml" ); e = new XMLEncoder ( new BufferedOutputStream ( fos )); FileExistsP fxp = new FileExistsP ( "pfad/zur/datei" ); e.writeObject ( fxp ); e.close (); fos = new FileOutputStream ( "CommandList.xml" ); e = new XMLEncoder ( new BufferedOutputStream ( fos )); List < Command > liste = new ArrayList<Command> (); liste.add ( fxp ); CommandList cl = new CommandList ( liste ); e.writeObject ( cl ); e.close (); fos = new FileOutputStream ( "FileCreatedTodayP.xml" ); e = new XMLEncoder ( new BufferedOutputStream ( fos )); FileCreatedTodayP fctp = new FileCreatedTodayP ( "pfad/zur/datei" ); e.writeObject ( fctp ); e.close (); } catch ( FileNotFoundException e1 ) { e1.printStackTrace (); } } } |
Eine Jar-Datei inklusive Quelltext gibt es hier.
Die Prüfung in Zusammenhang mit der SQL-Datenbank habe ich nicht ausformuliert. Fleissarbeit. Werde ich vermutlich implementieren, wenn die Handarbeit mich bei der täglichen Arbeit ausreichend nervt.
Das Thema “Zeitplanung der Ausführung” könnte interessant werden. Vielleicht will ich nicht jede Prüfung bei jedem Programmdurchlauf ausführen. Die oben genannten Prüfungen führe ich jeden Morgen aus und nur einmal am Tag. Den Zustand eines SQL-Datensatzes möchte ich vielleicht im 5-Minuten-Takt prüfen. Es wird nie langweilig.
An die Stelle des System.out.println
wird etwas anders treten: Email schreiben, Eintrag in Logdatei schreiben oder ähnliches. Konfigurierbar, natürlich! Und natürlich reicht ein Ausdruck auf der Console nicht als Fehlerbehandlung.
Hier erledigt der Code in XML-Form Überwachungsaufgaben. Aber das ist nicht wesentlich. Jede Programmierung kann so abgebildet werden. Und es wird auch gemacht, Beispiel ant, das mich auf die Idee gebracht hat.
Warum das “P” im Namen? Weil ein P ein bisschen aussieht wie ?.