/* $Id: HBCIBatch.java,v 1.1 2011/05/04 22:37:45 willuhn Exp $
This file is part of hbci4java
Copyright (C) 2001-2008 Stefan Palme
hbci4java is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
hbci4java is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.kapott.hbci.tools;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.StringTokenizer;
import org.kapott.hbci.GV.HBCIJob;
import org.kapott.hbci.callback.HBCICallbackConsole;
import org.kapott.hbci.exceptions.HBCI_Exception;
import org.kapott.hbci.manager.HBCIHandler;
import org.kapott.hbci.manager.HBCIUtils;
import org.kapott.hbci.manager.HBCIUtilsInternal;
import org.kapott.hbci.passport.AbstractHBCIPassport;
import org.kapott.hbci.passport.HBCIPassport;
/** Tool zum Ausf�hren von HBCI-Jobs, die in einer Batch-Datei definiert werden
* k�nnen.
* <pre>
* args[0] - configfile f�r HBCIUtils.init() (Property-File mit Kernel-Parametern
* [siehe API-Doc zu org.kapott.hbci.manager.HBCIUtils])
* zus�tzliche parameter:
* client.passport.default=
* default.hbciversion=
*
* args[1] - Dateiname der Antwortdatei f�r Callbacks
* country=
* blz=
* host=
* port=
* filter=
* userid=
* customerid=
* sizentry=
* passphrase=
* softpin=
* pin=
* tans=
*
* args[2] - Dateiname der Batch-Datei (jobnamen und parameter siehe
* API-Doc zu Paket org.kapott.hbci.GV)
* # kommentar
*
* hljobname:jobid:(props|toString)[:customerid]
* hljobparam=paramvalue
* hljobparam=<filename
* ...
*
* _lljobname:jobid[:customerid]
* _lljobparam=paramvalue
* _lljobparam=<filename
* ...
*
* --[:customerid]
*
* args[3] - Dateiname der Ausgabedatei (mehr dazu siehe unten)
* jobid:XXXX
* job status:
* YYYYYYYYYYY
* ZZZZZZZZZZZ
* ...
* job result:
* resultparam=value
* resultparam=value
*
* ...
* [args[4]] - Dateiname der Log-Datei
* </pre>
*
* <p>Alle Jobs, bei deren Ausf�hrung ein Fehler auftritt, werden nicht in die
* "normale" Ausgabedatei aufgenommen. Statt dessen wird eine zweite Aus-
* gabedatei erzeugt, die den gleichen Namen wie die "normale" Ausgabedatei
* plus ein Suffix ".err" hat. In dieser Fehlerdatei wird f�r jeden fehler-
* haften Job folgende Struktur geschrieben (String in "<>" wird durch die
* jeweiligen werte ersetzt):</p>
* <pre>
* jobid:JOBID
* global status:
* allg. fehlermeldung zur hbci-nachricht, in der der job ausgef�hrt werden sollte
* job status:
* fehlermeldung zu dem nachrichten-segment, in welchem der job definiert war
*
* ...
* </pre>
* <p>das ist zwar nicht besonders sch�n, reicht aber vielleicht erst mal (?)
* Alternativ dazu k�nnte ich anbieten, dass eine vollst�ndige Fehlernachricht
* �ber den *kompletten* Batch-Vorgang in eine Fehlerdatei geschrieben wird,
* sobald *irgendein* Job nicht sauber ausgef�hrt wurde (das h�tte den Vorteil,
* dass auch Fehler, die nicht direkt mit einem bestimmten Job in Verbindung
* stehen [z.B. Fehler bei der Dialog-Initialisierung] ordentlich geloggt
* werden).</p> */
public class HBCIBatch
{
// speziell callback-klasse, um die ausgaben zu reduzieren und um die
// nutzer-interaktion zu unterbinden, indem alle abgefragten daten auto-
// tisch �bergeben werden (aus args[1])
private static class MyCallback
extends HBCICallbackConsole
{
private Properties answers; // alle spezifizierten antwortdaten
public MyCallback(String[] args)
throws FileNotFoundException,IOException
{
// einlesen der answers-datei
answers=new Properties();
FileInputStream answerFile=new FileInputStream(args[1]);
answers.load(answerFile);
answerFile.close();
// wenn ein logfile angegeben wurde, dann dieses als ausgabemedium
// f�r stdout und stderr verwenden
if (args.length>=5) {
PrintStream outStream=new PrintStream(new FileOutputStream(args[4]));
System.setOut(outStream);
System.setErr(outStream);
this.setOutStream(outStream);
}
}
// modifizierte callback-methode, die daten-anfragen "automatisch"
// beantwortet
public synchronized void callback(HBCIPassport passport,int reason,String msg,int datatype,StringBuffer retData)
{
switch (reason) {
case NEED_CHIPCARD:
System.out.println(HBCIUtilsInternal.getLocMsg("CALLB_NEED_CHIPCARD"));
break;
case NEED_HARDPIN:
System.out.println(HBCIUtilsInternal.getLocMsg("CALLB_NEED_HARDPIN"));
break;
case NEED_SOFTPIN:
retData.replace(0,retData.length(),answers.getProperty("softpin"));
break;
case NEED_PASSPHRASE_LOAD:
case NEED_PASSPHRASE_SAVE:
retData.replace(0,retData.length(),answers.getProperty("passphrase"));
break;
case NEED_PT_SECMECH:
retData.replace(0,retData.length(),answers.getProperty("secmech"));
break;
case NEED_PT_PIN:
retData.replace(0,retData.length(),answers.getProperty("pin"));
break;
case NEED_PT_TAN:
// TODO tan-liste aktivieren
retData.replace(0,retData.length(),answers.getProperty("tan"));
break;
case NEED_COUNTRY:
retData.replace(0,retData.length(),answers.getProperty("country"));
break;
case NEED_BLZ:
retData.replace(0,retData.length(),answers.getProperty("blz"));
break;
case NEED_HOST:
retData.replace(0,retData.length(),answers.getProperty("host"));
break;
case NEED_PORT:
retData.replace(0,retData.length(),answers.getProperty("port"));
break;
case NEED_FILTER:
retData.replace(0,retData.length(),answers.getProperty("filter"));
break;
case NEED_USERID:
retData.replace(0,retData.length(),answers.getProperty("userid"));
break;
case NEED_CUSTOMERID:
retData.replace(0,retData.length(),answers.getProperty("customerid"));
break;
case NEED_SIZENTRY_SELECT:
retData.replace(0,retData.length(),answers.getProperty("sizentry"));
break;
case NEED_NEW_INST_KEYS_ACK:
retData.replace(0,retData.length(),"");
break;
case HAVE_NEW_MY_KEYS:
System.out.println("please restart batch process");
break;
case HAVE_INST_MSG:
HBCIUtils.log(msg,HBCIUtils.LOG_INFO);
break;
case NEED_CONNECTION:
case CLOSE_CONNECTION:
break;
}
}
// ausgabe der status-meldungen komplett unterbinden
public synchronized void status(HBCIPassport passport,int statusTag,
Object[] objs)
{
}
}
private final static int STATE_NEED_JOBNAME=1; // state-flags f�r
private final static int STATE_NEED_JOBPARAMS=2; // batch-file-parser
public static void main(String[] args)
throws Exception
{
// initialisieren von hbci4java
Properties props=new Properties();
InputStream istream=new FileInputStream(args[0]);
props.load(istream);
istream.close();
HBCIUtils.init(props, new MyCallback(args));
// erzeugen des passport-objektes
HBCIPassport passport=AbstractHBCIPassport.getInstance();
try {
// initialisieren des hbci-handlers f�r das passport
String version=passport.getHBCIVersion();
HBCIHandler handler=new HBCIHandler(version.length()!=0?version:HBCIUtils.getParam("default.hbciversion"),passport);
try {
// batch-datei �ffnen
BufferedReader reader=new BufferedReader(new FileReader(args[2]));
String line;
try {
int state=STATE_NEED_JOBNAME;
boolean lljob=false; // low- oder high-level-job?
HBCIJob job=null; // job-objekt
String jobid=null; // job-bezeichner
String customerId=null; // customer-id f�r job
Hashtable<String, Object> jobs=new Hashtable<String, Object>(); // liste aller jobs
// batch-datei zeilenweise einlesen und auswerten
while ((line=reader.readLine())!=null) {
line=line.trim();
// kommentare ignorieren
if (line.startsWith("#")) {
continue;
}
if (state==STATE_NEED_JOBNAME && line.length()!=0) {
// es wird der beginn einer job-definition erwartet
StringTokenizer tok=new StringTokenizer(line,":");
// jobnamen extrahieren
String jobname=tok.nextToken().trim();
if (jobname.equals("--")) {
// wenn jobname="--", dann neue hbci-message erzeugen
customerId=(tok.hasMoreTokens()?tok.nextToken().trim():null);
handler.newMsg(customerId);
} else {
// ansonsten handelt es sich um einen "richtigen" job
String resultMode;
if (jobname.startsWith("_")) {
// wenn jobname mit "_" beginnt, handelt es
// sich um einen low-level-jobnamen
job=handler.newLowlevelJob(jobname.substring(1));
lljob=true;
// zu einem low-level-job m�ssen zus�tzlich noch
// eine ID (zum sp�teren wiederfinden des jobs)
// und optional eine kunden-id festgelegt werden
jobid=tok.nextToken().trim();
resultMode="toString";
customerId=(tok.hasMoreTokens()?tok.nextToken().trim():null);
} else {
// wenn jobname nicht mit "_" beginnt, ist es
// ein high-level-job
job=handler.newJob(jobname);
lljob=false;
// zu einem high-level-job m�ssen zus�tzlich noch
// eine ID (zum sp�teren wiederfinden des jobs),
// ein modus f�r die ausgabe der ergebisdaten
// und optional eine kunden-id festgelegt werden
jobid=tok.nextToken().trim();
resultMode=tok.nextToken().trim();
customerId=(tok.hasMoreTokens()?tok.nextToken().trim():null);
}
// job in menge der jobs speichern
jobs.put(jobid,job);
// ... und ausgabemodus f�r diesen job merken
jobs.put(jobid+"_resultMode",resultMode);
state=STATE_NEED_JOBPARAMS;
}
} else if (state==STATE_NEED_JOBPARAMS) {
// bis zur n�chsten leerzeile oder dem dateienende
// werden jetzt alle zeilen als job-parameter
// interpretiert
if (line.length()!=0) {
StringTokenizer tok=new StringTokenizer(line,"=");
// parameternamen und -wert holen
String paramName=tok.nextToken().trim();
if (!tok.hasMoreTokens()) {
continue;
}
String paramValue=tok.nextToken().trim();
// f�r low-level-jobs m�ssen die parameter mit
// einem "_" beginnen, bei high-level-jobs
// d�rfen sie *nicht* mit einem "_" beginnen
if (paramName.startsWith("_")!=lljob) {
if (lljob) {
throw new HBCI_Exception("*** "+jobid+" is a lowlevel job, so parameter names have to start with '_'");
}
throw new HBCI_Exception("*** "+jobid+" is a highlevel job, so parameter names must not start with '_'");
}
// wenn es sich um einen low-level-job, den
// f�hrenden "_" beim parameter-namen entfernen
if (lljob) {
paramName=paramName.substring(1);
}
// wenn der parameter-wert mit einem "<" beginnt,
// so soll der wert des parameter aus der datei
// gelesen werden, die nach dem "<" spezifiziert
// ist
if (paramValue.startsWith("<")) {
// �ffnen der datei
String filename=paramValue.substring(1);
FileInputStream fin=new FileInputStream(filename);
// puffer f�r einlesen der datei
byte[] buffer=new byte[2048];
int len;
StringBuffer content=new StringBuffer();
// datei in stringbuffer einlesen
while ((len=fin.read(buffer))>0) {
content.append(new String(buffer,0,len,"ISO-8859-1"));
}
// datei schlie�en
fin.close();
// parameterwert ist inhalt des stringbuffers
paramValue=content.toString();
}
// parameter f�r aktuellen job setzen
job.setParam(paramName,paramValue);
} else {
// leerzeile gefunden - damit ist die parameter-
// spez. f�r den aktuellen job beendet
// aktuellen job zur job-queue hinzuf�gen
job.addToQueue(customerId);
state=STATE_NEED_JOBNAME;
}
}
}
// wenn noch ein job "in bearbeitung" ist, der noch nicht
// zur job-queue hinzugef�gt wurde, dann das jetzt nachholen
if (state==STATE_NEED_JOBPARAMS) {
job.addToQueue(customerId);
}
// alle batch-jobs ausf�hren
handler.execute();
// ergebnis-writer f�r ok-jobs und f�r fehlerhafte jobs
// erzeugen
PrintWriter writer=new PrintWriter(new FileWriter(args[3]));
PrintWriter errWriter=new PrintWriter(new FileWriter(args[3]+".err"));
try {
// alle bekannten job-bezeichner (IDs) durchlaufen
for (Enumeration<String> jobIds=jobs.keys();jobIds.hasMoreElements();) {
jobid=jobIds.nextElement();
if (jobid.endsWith("_resultMode")) {
continue;
}
// den dazugeh�rigen job holen
job=(HBCIJob)jobs.get(jobid);
if (job.getJobResult().isOK()) {
// wenn der job erfolgreich gelaufen ist
// ausgabe von jobid
writer.println("jobid:"+jobid);
// ausgabe der hbci-status-meldungen zu diesem job
writer.println("job status:");
writer.println(job.getJobResult().getJobStatus());
// ausgabe der job-ergebnisse
writer.println("job result:");
String resultMode=(String)jobs.get(jobid+"_resultMode");
if (resultMode.equals("props")) {
// ausgabemodus="props": alle ergebnisdaten
// als lowlevel-properties ausgeben
Properties result=job.getJobResult().getResultData();
if (result!=null) {
// array mit result-properties holen und
// sortieren
String[] keys=(String[])new ArrayList(result.keySet()).toArray(new String[0]);
Arrays.sort(keys);
// ausgabe aller result-properties
for (int i=0;i<keys.length;i++) {
String name=keys[i];
String value=result.getProperty(name);
writer.println(name+"="+value);
}
}
} else {
// ausgabemodus="toString": job-spezifische
// toString()-methode f�r formatierung der
// ergebnisdaten aufrufen
writer.println(job.getJobResult());
}
// leerzeile einf�gen
writer.println();
} else {
// wenn ein job fehler erzeugt hatte, die fehlermeldungen
// an die err-datei anh�ngen
errWriter.println("jobid:"+jobid);
errWriter.println("global status:");
errWriter.println(job.getJobResult().getGlobStatus().getErrorString());
errWriter.println("job status:");
errWriter.println(job.getJobResult().getJobStatus().getErrorString());
errWriter.println();
}
}
} finally {
writer.close();
errWriter.close();
}
} finally {
reader.close();
}
} finally {
handler.close();
passport=null;
}
} finally {
if (passport!=null) {
passport.close();
}
}
}
}