/* $Id: HBCIJobImpl.java,v 1.5 2011/06/06 10:30:31 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.GV;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.kapott.hbci.GV_Result.HBCIJobResult;
import org.kapott.hbci.GV_Result.HBCIJobResultImpl;
import org.kapott.hbci.callback.HBCICallback;
import org.kapott.hbci.exceptions.HBCI_Exception;
import org.kapott.hbci.exceptions.InvalidArgumentException;
import org.kapott.hbci.exceptions.InvalidUserDataException;
import org.kapott.hbci.exceptions.JobNotSupportedException;
import org.kapott.hbci.manager.HBCIHandler;
import org.kapott.hbci.manager.HBCIUtils;
import org.kapott.hbci.manager.HBCIUtilsInternal;
import org.kapott.hbci.manager.LogFilter;
import org.kapott.hbci.manager.MsgGen;
import org.kapott.hbci.passport.HBCIPassport;
import org.kapott.hbci.passport.HBCIPassportInternal;
import org.kapott.hbci.passport.HBCIPassportList;
import org.kapott.hbci.protocol.SEG;
import org.kapott.hbci.protocol.SyntaxElement;
import org.kapott.hbci.protocol.factory.SEGFactory;
import org.kapott.hbci.status.HBCIMsgStatus;
import org.kapott.hbci.status.HBCIRetVal;
import org.kapott.hbci.structures.Konto;
import org.kapott.hbci.structures.Value;
public abstract class HBCIJobImpl
implements HBCIJob
{
private String name; /* Job-Name mit Versionsnummer */
private String jobName; /* Job-Name ohne Versionsnummer */
public String getJobName() {
return jobName;
}
private String segVersion; /* Segment-Version */
private Properties llParams; /* Eingabeparameter f�r diesen GV (Saldo.KTV.number) */
private HBCIPassportList passports;
protected HBCIJobResultImpl jobResult; /* Objekt mit R�ckgabedaten f�r diesen GV */
private HBCIHandler parentHandler;
private int idx; /* idx gibt an, der wievielte task innerhalb der aktuellen message
dieser GV ist */
private boolean executed;
private int contentCounter; /* Z�hler, wie viele R�ckgabedaten bereits in outStore eingetragen wurden
(entspricht der anzahl der antwort-segmente!)*/
private Hashtable<String, String[][]> constraints; /* Festlegungen, welche Parameter eine Anwendung setzen muss, wie diese im
HBCI-Kernel umgesetzt werden und welche default-Werte vorgesehen sind;
die Hashtable hat als Schl�ssel einen String, der angibt, wie ein Wert aus einer
Anwendung heraus zu setzen ist. Der dazugeh�rige Value ist ein Array. Jedes Element
dieses Arrays ist ein String[2], wobei das erste Element angibt, wie der Pfadname heisst,
unter dem der anwendungs-definierte Wert abzulegen ist, das zweite Element gibt den
default-Wert an, falls f�r diesen Namen *kein* Wert angebeben wurde. Ist der default-
Wert="", so kann das Syntaxelement weggelassen werden. Ist der default-Wert=null,
so *muss* die Anwendung einen Wert spezifizieren */
private Hashtable<String, Integer> logFilterLevels; /* hier wird f�r jeden hl-param-name gespeichert, ob der dazugeh�rige wert
�ber den logfilter-Mechanimus gesch�tzt werden soll */
private HashSet<String> indexedConstraints;
protected HBCIJobImpl(HBCIHandler parentHandler,String jobnameLL,HBCIJobResultImpl jobResult)
{
findSpecNameForGV(jobnameLL,parentHandler);
this.llParams=new Properties();
this.passports=new HBCIPassportList();
this.passports.addPassport((HBCIPassportInternal)parentHandler.getPassport(),HBCIPassport.ROLE_ISS);
this.jobResult=jobResult;
this.jobResult.setParentJob(this);
this.contentCounter=0;
this.constraints=new Hashtable<String, String[][]>();
this.logFilterLevels=new Hashtable<String, Integer>();
this.indexedConstraints=new HashSet<String>();
this.executed=false;
this.parentHandler=parentHandler;
/* offensichtlich soll ein GV mit dem Namen name in die nachricht
aufgenommen werden. da GV durch segmente definiert sind, und einige
dieser segmente ein request-tag benoetigen (siehe klasse
SyntaxElement), wird hier auf jeden fall das request-tag gesetzt.
wenn es *nicht* benoetigt wird, schadet es auch nichts. und es ist
auf keinen fall "zu viel" gesetzt, da dieser code nur ausgefuehrt wird,
wenn das jeweilige segment tatsaechlich erzeugt werden soll */
llParams.setProperty(this.name,"requested");
}
/* gibt den segmentcode f�r diesen job zur�ck */
public String getHBCICode()
{
StringBuffer ret=null;
// Macht aus z.Bsp. "KUmsZeit5" -> "KUmsZeitPar5.SegHead.code"
StringBuffer searchString=new StringBuffer(name);
for (int i=searchString.length()-1;i>=0;i--) {
if (!(searchString.charAt(i)>='0' && searchString.charAt(i)<='9')) {
searchString.insert(i+1,"Par");
searchString.append(".SegHead.code");
break;
}
}
HBCIPassportInternal passport=getMainPassport();
StringBuffer tempkey=new StringBuffer();
// durchsuchen aller param-segmente nach einem job mit dem jobnamen des
// aktuellen jobs
for (Enumeration i=passport.getBPD().propertyNames();i.hasMoreElements();) {
String key=(String)i.nextElement();
if (key.indexOf("Params")==0) {
tempkey.setLength(0);
tempkey.append(key);
tempkey.delete(0,tempkey.indexOf(".")+1);
if (tempkey.toString().equals(searchString.toString())) {
ret=new StringBuffer(passport.getBPD().getProperty(key));
ret.replace(1,2,"K");
ret.deleteCharAt(ret.length()-1);
break;
}
}
}
return ret.toString();
}
/* gibt zu einem gegebenen jobnamen des namen dieses jobs in der syntax-spez.
* zur�ck (also mit angeh�ngter versionsnummer)
*/
private void findSpecNameForGV(String jobnameLL,HBCIHandler handler)
{
int maxVersion=0;
StringBuffer key=new StringBuffer();
// alle param-segmente durchlaufen
Properties bpd = handler.getPassport().getBPD();
for (Enumeration i=bpd.propertyNames();i.hasMoreElements();) {
String path = (String)i.nextElement();
key.setLength(0);
key.append(path);
if (key.indexOf("Params")==0) {
key.delete(0,key.indexOf(".")+1);
// wenn segment mit namen des aktuellen jobs gefunden wurde
if (key.indexOf(jobnameLL+"Par")==0 &&
key.toString().endsWith(".SegHead.code"))
{
// willuhn 2011-06-06 Maximal zulaessige Segment-Version ermitteln
// Hintergrund: Es gibt Szenarien, in denen nicht die hoechste verfuegbare
// Versionsnummer verwendet werden kann, weil die Voraussetzungen impliziert,
// die beim User nicht gegeben sind. Mit diesem Parameter kann die maximale
// Version nach oben begrenzt werden. In AbstractPinTanPassport#setBPD() ist
// ein konkretes Beispiel enthalten (Bank macht HITANS5 und damit HHD 1.4, der
// User hat aber nur ein HHD-1.3-tauglichen TAN-Generator)
int maxAllowedVersion = Integer.parseInt(HBCIUtils.getParam("kernel.gv." + bpd.getProperty(path,"default") + ".segversion.max","0"));
key.delete(0,jobnameLL.length()+("Par").length());
// extrahieren der versionsnummer aus dem spez-namen
String st=key.substring(0,key.indexOf("."));
int version=0;
try {
version=Integer.parseInt(st);
} catch (Exception e) {
HBCIUtils.log("found invalid job version: key="+key+", jobnameLL="+jobnameLL+" (this is a known, but harmless bug)", HBCIUtils.LOG_WARN);
}
// willuhn 2011-06-06 Segment-Versionen ueberspringen, die groesser als die max. zulaessige sind
if (maxAllowedVersion > 0 && version > maxAllowedVersion)
{
HBCIUtils.log("skipping segment version " + version + " for task " + jobnameLL + ", larger than allowed version " + maxAllowedVersion, HBCIUtils.LOG_INFO);
continue;
}
// merken der gr��ten jemals aufgetretenen versionsnummer
if (version!=0) {
HBCIUtils.log("task "+jobnameLL+" is supported with segment version "+st,HBCIUtils.LOG_DEBUG2);
if (version>maxVersion) {
maxVersion=version;
}
}
}
}
}
if (maxVersion==0)
{
String msg=HBCIUtilsInternal.getLocMsg("EXCMSG_GVNOTSUPP",jobnameLL);
if (!HBCIUtilsInternal.ignoreError(handler.getPassport(),"client.errors.ignoreJobNotSupported",msg))
throw new JobNotSupportedException(jobnameLL);
maxVersion = 1;
HBCIUtils.log("Using segment version " + maxVersion + " for job " + jobnameLL + ", although not found in BPD. This may fail", HBCIUtils.LOG_WARN);
}
// namen+versionsnummer speichern
this.jobName = jobnameLL;
this.segVersion = Integer.toString(maxVersion);
this.name = jobnameLL + this.segVersion;
}
/**
* Legt die Versionsnummer des Segments manuell fest.
* Ist u.a. noetig, um HKTAN-Segmente in genau der Version zu senden, in der
* auch die HITANS empfangen wurden. Andernfalls koennte es passieren, dass
* wir ein HKTAN mit einem TAN-Verfahren senden, welches in dieser HKTAN-Version
* gar nicht von der Bank unterstuetzt wird. Das ist ein Dirty-Hack, ich weiss ;)
* Falls das noch IRGENDWO anders verwendet wird, muss man hoellisch aufpassen,
* dass alle Stellen, wo "this.name" bzw. "this.segVersion" direkt oder indirekt
* verwendet wurde, ebenfalls beruecksichtigt werden.
* @param version die neue Versionsnummer.
*/
public synchronized void setSegVersion(String version)
{
if (version == null || version.length() == 0)
{
HBCIUtils.log("tried to change segment version for task " + this.jobName + " explicit, but no version given",HBCIUtils.LOG_WARN);
return;
}
// Wenn sich die Versionsnummer nicht geaendert hat, muessen wir die
// Huehner ja nicht verrueckt machen ;)
if (version.equals(this.segVersion))
return;
HBCIUtils.log("changing segment version for task " + this.jobName + " explicit from " + this.segVersion + " to " + version,HBCIUtils.LOG_INFO);
// Der alte Name
String oldName = this.name;
// Neuer Name und neue Versionsnummer
this.segVersion = version;
this.name = this.jobName + version;
// Bereits gesetzte llParams fixen
String[] names = this.llParams.keySet().toArray(new String[this.llParams.size()]);
for (String s:names)
{
if (!s.startsWith(oldName))
continue; // nicht betroffen
// Alten Schluessel entfernen und neuen einfuegen
String value = this.llParams.getProperty(s);
String newName = s.replaceFirst(oldName,this.name);
this.llParams.remove(s);
this.llParams.setProperty(newName,value);
}
// Destination-Namen in den LowLevel-Parameter auf den neuen Namen umbiegen
Enumeration<String> e = constraints.keys();
while (e.hasMoreElements())
{
String frontendName = e.nextElement();
String[][] values = constraints.get(frontendName);
for (int i=0;i<values.length;++i)
{
String[] value = values[i];
// value[0] ist das Target
if (!value[0].startsWith(oldName))
continue;
// Hier ersetzen wir z.Bsp. "TAN2Step5.process" gegen "TAN2Step3.process"
value[0] = value[0].replaceFirst(oldName,this.name);
}
}
}
public int getMaxNumberPerMsg()
{
int ret=1;
StringBuffer searchString=new StringBuffer(name);
for (int i=searchString.length()-1;i>=0;i--) {
if (!(searchString.charAt(i)>='0' && searchString.charAt(i)<='9')) {
searchString.insert(i+1,"Par");
searchString.append(".maxnum");
break;
}
}
HBCIPassportInternal passport=getMainPassport();
StringBuffer tempkey=new StringBuffer();
for (Enumeration i=passport.getBPD().propertyNames();i.hasMoreElements();) {
String key=(String)i.nextElement();
if (key.indexOf("Params")==0) {
tempkey.setLength(0);
tempkey.append(key);
tempkey.delete(0,tempkey.indexOf(".")+1);
if (tempkey.toString().equals(searchString.toString())) {
ret=Integer.parseInt(passport.getBPD().getProperty(key));
break;
}
}
}
return ret;
}
public int getMinSigs()
{
int ret=0;
StringBuffer searchString=new StringBuffer(name);
for (int i=searchString.length()-1;i>=0;i--) {
if (!(searchString.charAt(i)>='0' && searchString.charAt(i)<='9')) {
searchString.insert(i+1,"Par");
searchString.append(".minsigs");
break;
}
}
HBCIPassportInternal passport=getMainPassport();
StringBuffer tempkey=new StringBuffer();
for (Enumeration i=passport.getBPD().propertyNames();i.hasMoreElements();) {
String key=(String)i.nextElement();
if (key.indexOf("Params")==0) {
tempkey.setLength(0);
tempkey.append(key);
tempkey.delete(0,tempkey.indexOf(".")+1);
if (tempkey.toString().equals(searchString.toString())) {
ret=Integer.parseInt(passport.getBPD().getProperty(key));
break;
}
}
}
return ret;
}
public int getSecurityClass()
{
int ret=1;
StringBuffer searchString=new StringBuffer(name);
for (int i=searchString.length()-1;i>=0;i--) {
if (!(searchString.charAt(i)>='0' && searchString.charAt(i)<='9')) {
searchString.insert(i+1,"Par");
searchString.append(".secclass");
break;
}
}
HBCIPassportInternal passport=getMainPassport();
StringBuffer tempkey=new StringBuffer();
for (Enumeration i=passport.getBPD().propertyNames();i.hasMoreElements();) {
String key=(String)i.nextElement();
if (key.indexOf("Params")==0) {
tempkey.setLength(0);
tempkey.append(key);
tempkey.delete(0,tempkey.indexOf(".")+1);
if (tempkey.toString().equals(searchString.toString())) {
ret=Integer.parseInt(passport.getBPD().getProperty(key));
break;
}
}
}
return ret;
}
protected void addConstraint(String frontendName,String destinationName,String defValue,int logFilterLevel)
{
addConstraint(frontendName, destinationName, defValue, logFilterLevel, false);
}
protected void addConstraint(String frontendName,String destinationName,String defValue,int logFilterLevel,boolean indexed)
{
// value ist array:(lowlevelparamname, defaultvalue)
String[] value=new String[2];
value[0]=getName()+"."+destinationName;
value[1]=defValue;
// alle schon gespeicherten "ziel-lowlevelparameternamen" f�r den gew�nschten
// frontend-namen suchen
String[][] values=(constraints.get(frontendName));
if (values==null) {
// wenn es noch keine gibt, ein neues frontend-ding anlegen //FIXME: was ist ein "frontend-ding"?
values=new String[1][];
values[0]=value;
} else {
ArrayList<String[]> a=new ArrayList<String[]>(Arrays.asList(values));
a.add(value);
values=(a.toArray(values));
}
constraints.put(frontendName,values);
if (indexed) {
indexedConstraints.add(frontendName);
}
if (logFilterLevel>0) {
logFilterLevels.put(frontendName,new Integer(logFilterLevel));
}
}
public void verifyConstraints()
{
HBCIPassportInternal passport=getMainPassport();
// durch alle gespeicherten constraints durchlaufen
for (Iterator<String> i=constraints.keySet().iterator();i.hasNext();) {
// den frontendnamen f�r das constraint ermitteln
String frontendName=(i.next());
// dazu alle ziel-lowlevelparameter mit default-wert extrahieren
String[][] values=(constraints.get(frontendName));
// durch alle ziel-lowlevel-parameternamen durchlaufen, die gesetzt werden m�ssen
for (int j=0;j<values.length;j++) {
//Array mit Pfadname und default Wert
String[] value=values[j];
// lowlevel-name (Pfadname) des parameters (z.B. wird Frontendname src.bic zum Pfad My.bic
String destination=value[0];
// default-wert des parameters, wenn keiner angegeben wurde
String defValue=value[1];
String givenContent=getLowlevelParam(destination);
if (givenContent==null && indexedConstraints.contains(frontendName)) {
givenContent = getLowlevelParam(insertIndex(destination, 0));
}
String content=null;
content=defValue;
if (givenContent!=null && givenContent.length()!=0)
content=givenContent;
if (content==null) {
String msg=HBCIUtilsInternal.getLocMsg("EXC_MISSING_HL_PROPERTY",frontendName);
if (!HBCIUtilsInternal.ignoreError(passport,"client.errors.ignoreWrongJobDataErrors",msg))
throw new InvalidUserDataException(msg);
content="";
}
// evtl. default-wert als aktuellen wert setzen (naemlich dann,
// wenn kein content angegeben wurde (givenContent==null), aber
// ein default-Content definiert wurde (content.length()!=0)
if (content.length()!=0 && givenContent==null)
setLowlevelParam(destination,content);
}
}
// verify if segment can be created
SEG seg=null;
try {
seg=createJobSegment();
seg.validate();
} catch (Exception ex) {
throw new HBCI_Exception("*** the job segment for this task can not be created",ex);
} finally {
if (seg!=null) {
SEGFactory.getInstance().unuseObject(seg);
}
}
}
public SEG createJobSegment()
{
return createJobSegment(0);
}
public SEG createJobSegment(int segnum)
{
SEG seg=null;
try {
MsgGen gen=getParentHandler().getMsgGen();
seg=SEGFactory.getInstance().createSEG(getName(),getName(),null,0,gen.getSyntax());
for (Enumeration e=getLowlevelParams().propertyNames();e.hasMoreElements();) {
String key=(String)e.nextElement();
String value=getLowlevelParams().getProperty(key);
seg.propagateValue(key,value,
SyntaxElement.TRY_TO_CREATE,
SyntaxElement.DONT_ALLOW_OVERWRITE);
}
seg.propagateValue(getName()+".SegHead.seq",Integer.toString(segnum),
SyntaxElement.DONT_TRY_TO_CREATE,
SyntaxElement.ALLOW_OVERWRITE);
} catch (Exception ex) {
throw new HBCI_Exception("*** the job segment for this task can not be created",ex);
}
return seg;
}
public List<String> getJobParameterNames()
{
MsgGen gen=getParentHandler().getMsgGen();
return gen.getGVParameterNames(name);
}
public List<String> getJobResultNames()
{
MsgGen gen=getParentHandler().getMsgGen();
return gen.getGVResultNames(name);
}
public Properties getJobRestrictions()
{
return passports.getMainPassport().getJobRestrictions(name);
}
/** Setzen eines komplexen Job-Parameters (Kontodaten). Einige Jobs ben�tigten Kontodaten
als Parameter. Diese m�ssten auf "normalem" Wege durch drei Aufrufe von
{@link #setParam(String,String)} erzeugt werden (je einer f�r
die L�nderkennung, die Bankleitzahl und die Kontonummer). Durch Verwendung dieser
Methode wird dieser Weg abgek�rzt. Es wird ein Kontoobjekt �bergeben, f�r welches
die entsprechenden drei <code>setParam(String,String)</code>-Aufrufe automatisch
erzeugt werden.
@param paramname die Basis der Parameter f�r die Kontodaten (f�r "<code>my.country</code>",
"<code>my.blz</code>", "<code>my.number</code>" w�re das also "<code>my</code>")
@param acc ein Konto-Objekt, aus welchem die zu setzenden Parameterdaten entnommen werden */
public void setParam(String paramname,Konto acc)
{
setParam(paramname, null, acc);
}
/**
* @see org.kapott.hbci.GV.HBCIJob#setParam(java.lang.String, java.lang.Integer, org.kapott.hbci.structures.Konto)
*/
public void setParam(String paramname,Integer index,Konto acc)
{
if (acceptsParam(paramname+".country") && acc.country!=null && acc.country.length()!=0)
setParam(paramname+".country",index,acc.country);
if (acceptsParam(paramname+".blz") && acc.blz!=null && acc.blz.length()!=0)
setParam(paramname+".blz",index,acc.blz);
if (acceptsParam(paramname+".number") && acc.number!=null && acc.number.length()!=0)
setParam(paramname+".number",index,acc.number);
if (acceptsParam(paramname+".subnumber") && acc.subnumber!=null && acc.subnumber.length()!=0)
setParam(paramname+".subnumber",index,acc.subnumber);
if (acceptsParam(paramname+".name") && acc.name!=null && acc.name.length()!=0)
setParam(paramname+".name",index,acc.name);
if (acceptsParam(paramname+".curr") && acc.curr!=null && acc.curr.length()!=0)
setParam(paramname+".curr",index,acc.curr);
if (acceptsParam(paramname+".bic") && acc.bic!=null && acc.bic.length()!=0)
setParam(paramname+".bic",index,acc.bic);
if (acceptsParam(paramname+".iban") && acc.iban!=null && acc.iban.length()!=0)
setParam(paramname+".iban",index,acc.iban);
}
/** Setzen eines komplexen Job-Parameters (Geldbetrag). Einige Jobs ben�tigten Geldbetr�ge
als Parameter. Diese m�ssten auf "normalem" Wege durch zwei Aufrufe von
{@link #setParam(String,String)} erzeugt werden (je einer f�r
den Wert und die W�hrung). Durch Verwendung dieser
Methode wird dieser Weg abgek�rzt. Es wird ein Value-Objekt �bergeben, f�r welches
die entsprechenden zwei <code>setParam(String,String)</code>-Aufrufe automatisch
erzeugt werden.
@param paramname die Basis der Parameter f�r die Geldbetragsdaten (f�r "<code>btg.value</code>" und
"<code>btg.curr</code>" w�re das also "<code>btg</code>")
@param v ein Value-Objekt, aus welchem die zu setzenden Parameterdaten entnommen werden */
public void setParam(String paramname, Value v)
{
setParam(paramname, null, v);
}
public void setParam(String paramname, Integer index, Value v)
{
if (acceptsParam(paramname+".value"))
setParam(paramname+".value",index,HBCIUtils.bigDecimal2String(v.getBigDecimalValue()));
String curr=v.getCurr();
if (acceptsParam(paramname+".curr") && curr!=null && curr.length()!=0)
setParam(paramname+".curr",index,curr);
}
/** Setzen eines Job-Parameters, bei dem ein Datums als Wert erwartet wird. Diese Methode
dient als Wrapper f�r {@link #setParam(String,String)}, um das Datum in einen korrekt
formatierten String umzuwandeln. Das "richtige" Datumsformat ist dabei abh�ngig vom
aktuellen Locale.
@param paramName Name des zu setzenden Job-Parameters
@param date Datum, welches als Wert f�r den Job-Parameter benutzt werden soll */
public void setParam(String paramName, Date date)
{
setParam(paramName, null, date);
}
public void setParam(String paramName, Integer index, Date date)
{
setParam(paramName, index, HBCIUtils.date2StringISO(date));
}
/** Setzen eines Job-Parameters, bei dem ein Integer-Wert Da als Wert erwartet wird. Diese Methode
dient nur als Wrapper f�r {@link #setParam(String,String)}.
@param paramName Name des zu setzenden Job-Parameters
@param i Integer-Wert, der als Wert gesetzt werden soll */
public void setParam(String paramName,int i)
{
setParam(paramName,Integer.toString(i));
}
protected boolean acceptsParam(String hlParamName)
{
return constraints.get(hlParamName)!=null;
}
/** <p>Setzen eines Job-Parameters. F�r alle Highlevel-Jobs ist in der Package-Beschreibung zum
Package {@link org.kapott.hbci.GV} eine Auflistung aller Jobs und deren Parameter zu finden.
F�r alle Lowlevel-Jobs kann eine Liste aller Parameter entweder mit dem Tool
{@link org.kapott.hbci.tools.ShowLowlevelGVs} oder zur Laufzeit durch Aufruf
der Methode {@link org.kapott.hbci.manager.HBCIHandler#getLowlevelJobParameterNames(String)}
ermittelt werden.</p>
<p>Bei Verwendung dieser oder einer der anderen <code>setParam()</code>-Methoden werden zus�tzlich
einige der Job-Restriktionen (siehe {@link #getJobRestrictions()}) analysiert. Beim Verletzen einer
der �berpr�ften Einschr�nkungen wird eine Exception mit einer entsprechenden Meldung erzeugt.
Diese �berpr�fung findet allerdings nur bei Highlevel-Jobs statt.</p>
@param paramName der Name des zu setzenden Parameters.
@param value Wert, auf den der Parameter gesetzt werden soll */
@Override
public void setParam(String paramName,String value)
{
setParam(paramName,null,value);
}
/** <p>Setzen eines Job-Parameters. F�r alle Highlevel-Jobs ist in der Package-Beschreibung zum
Package {@link org.kapott.hbci.GV} eine Auflistung aller Jobs und deren Parameter zu finden.
F�r alle Lowlevel-Jobs kann eine Liste aller Parameter entweder mit dem Tool
{@link org.kapott.hbci.tools.ShowLowlevelGVs} oder zur Laufzeit durch Aufruf
der Methode {@link org.kapott.hbci.manager.HBCIHandler#getLowlevelJobParameterNames(String)}
ermittelt werden.</p>
<p>Bei Verwendung dieser oder einer der anderen <code>setParam()</code>-Methoden werden zus�tzlich
einige der Job-Restriktionen (siehe {@link #getJobRestrictions()}) analysiert. Beim Verletzen einer
der �berpr�ften Einschr�nkungen wird eine Exception mit einer entsprechenden Meldung erzeugt.
Diese �berpr�fung findet allerdings nur bei Highlevel-Jobs statt.</p>
@param paramName der Name des zu setzenden Parameters.
@param index Der index oder <code>null</code>, wenn kein Index gew�nscht ist
@param value Wert, auf den der Parameter gesetzt werden soll */
@Override
public void setParam(String paramName,Integer index,String value)
{
// wenn der Parameter einen LogFilter-Level gesetzt hat, dann den
// betreffenden Wert zum Logfilter hinzuf�gen
Integer logFilterLevel=logFilterLevels.get(paramName);
if (logFilterLevel!=null && logFilterLevel.intValue()!=0) {
LogFilter.getInstance().addSecretData(value,"X",logFilterLevel.intValue());
}
String[][] destinations=constraints.get(paramName);
HBCIPassportInternal passport=getMainPassport();
if (destinations==null) {
String msg=HBCIUtilsInternal.getLocMsg("EXCMSG_PARAM_NOTNEEDED",new String[] {paramName,getName()});
if (!HBCIUtilsInternal.ignoreError(passport,"client.errors.ignoreWrongJobDataErrors",msg))
throw new InvalidUserDataException(msg);
destinations=new String[0][];
}
if (value==null || value.length()==0) {
String msg=HBCIUtilsInternal.getLocMsg("EXCMSG_PARAM_EMPTY",new String[] {paramName,getName()});
if (!HBCIUtilsInternal.ignoreError(passport,"client.errors.ignoreWrongJobDataErrors",msg))
throw new InvalidUserDataException(msg);
value="";
}
if (index!=null && !indexedConstraints.contains(paramName)) {
String msg=HBCIUtilsInternal.getLocMsg("EXCMSG_PARAM_NOTINDEXED",new String[] {paramName,getName()});
if (!HBCIUtilsInternal.ignoreError(passport,"client.errors.ignoreWrongJobDataErrors",msg))
throw new InvalidUserDataException(msg);
}
for (int i=0;i<destinations.length;i++) {
String[] valuePair=destinations[i];
String lowlevelname=valuePair[0];
if (index != null && indexedConstraints.contains(paramName)) {
lowlevelname = insertIndex(lowlevelname, index);
}
setLowlevelParam(lowlevelname,value);
}
}
public void setContinueOffset(int loop)
{
String offset=getContinueOffset(loop);
setLowlevelParam(getName()+".offset",(offset!=null)?offset:"");
}
protected void setLowlevelParam(String key,String value)
{
HBCIUtils.log("setting lowlevel parameter "+key+" = "+value,HBCIUtils.LOG_DEBUG);
llParams.setProperty(key,value);
}
public Properties getLowlevelParams()
{
return llParams;
}
public String getLowlevelParam(String key)
{
return getLowlevelParams().getProperty(key);
}
public void setIdx(int idx)
{
this.idx=idx;
}
public String getName()
{
return name;
}
public String getSegVersion()
{
return this.segVersion;
}
/* stellt fest, ob f�r diesen Task ein neues Auftragssegment generiert werden muss.
Das ist in zwei F�llen der Fall: der Task wurde noch nie ausgef�hrt; oder der Task
wurde bereits ausgef�hrt, hat aber eine "offset"-Meldung zur�ckgegeben */
public boolean needsContinue(int loop)
{
boolean needs=false;
if (executed) {
HBCIRetVal retval=null;
int num=jobResult.getRetNumber();
for (int i=0;i<num;i++) {
retval=jobResult.getRetVal(i);
if (retval.code.equals("3040") && retval.params.length!=0 && (--loop)==0) {
needs=true;
break;
}
}
}
else needs=true;
return needs;
}
/* gibt (sofern vorhanden) den offset-Wert des letzten HBCI-R�ckgabecodes
zur�ck */
private String getContinueOffset(int loop)
{
String ret=null;
int num=jobResult.getRetNumber();
for (int i=0;i<num;i++) {
HBCIRetVal retval=jobResult.getRetVal(i);
if (retval.code.equals("3040") && retval.params.length!=0 && (--loop)==0) {
ret=retval.params[0];
break;
}
}
return ret;
}
/* f�llt das Objekt mit den R�ckgabedaten. Dazu wird zuerst eine Liste aller
Segmente erstellt, die R�ckgabedaten f�r diesen Task enthalten. Anschlie�end
werden die HBCI-R�ckgabewerte (RetSegs) im outStore gespeichert. Danach werden
die GV-spezifischen Daten im outStore abgelegt */
public void fillJobResult(HBCIMsgStatus status,int offset)
{
try {
executed=true;
Properties result=status.getData();
// nachsehen, welche antwortsegmente ueberhaupt
// zu diesem task gehoeren
// res-num --> segmentheader (wird f�r sortierung der
// antwort-segmente ben�tigt)
Hashtable<Integer,String> keyHeaders=new Hashtable<Integer, String>();
for (Enumeration i=result.keys();i.hasMoreElements();) {
String key=(String)(i.nextElement());
if (key.startsWith("GVRes")&&
key.endsWith(".SegHead.ref")) {
String segref=result.getProperty(key);
if ((Integer.parseInt(segref))-offset==idx) {
// nummer des antwortsegments ermitteln
int resnum=0;
if (key.startsWith("GVRes_")) {
resnum=Integer.parseInt(key.substring(key.indexOf('_')+1,key.indexOf('.')));
}
keyHeaders.put(
new Integer(resnum),
key.substring(0,key.length()-(".SegHead.ref").length()));
}
}
}
saveBasicValues(result,idx+offset);
saveReturnValues(status,idx+offset);
// segment-header-namen der antwortsegmente in der reihenfolge des
// eintreffens sortieren
Object[] resnums=keyHeaders.keySet().toArray(new Object[0]);
Arrays.sort(resnums);
// alle antwortsegmente durchlaufen
for (int i=0;i<resnums.length;i++) {
// dabei reihenfolge des eintreffens beachten
String header=keyHeaders.get(resnums[i]);
extractPlaintextResults(status,header,contentCounter);
extractResults(status,header,contentCounter++);
// der contentCounter wird fuer jedes antwortsegment um 1 erhoeht
}
} catch (Exception e) {
String msg=HBCIUtilsInternal.getLocMsg("EXCMSG_CANTSTORERES",getName());
if (!HBCIUtilsInternal.ignoreError(getMainPassport(),
"client.errors.ignoreJobResultStoreErrors",
msg+": "+HBCIUtils.exception2String(e))) {
throw new HBCI_Exception(msg,e);
}
}
}
/* wenn wenigstens ein HBCI-R�ckgabewert f�r den aktuellen GV gefunden wurde,
so werden im outStore zus�tzlich die entsprechenden Dialog-Parameter
gespeichert (Property @c basic.*) */
private void saveBasicValues(Properties result,int ref)
{
// wenn noch keine basic-daten gespeichert sind
if (jobResult.getDialogId()==null) {
// Pfad des originalen MsgHead-Segmentes holen und um "orig_" ergaenzen,
// um den Key fuer die entsprechenden Daten in das result-Property zu erhalten
String msgheadName="orig_"+result.getProperty("1");
jobResult.storeResult("basic.dialogid",result.getProperty(msgheadName+".dialogid"));
jobResult.storeResult("basic.msgnum",result.getProperty(msgheadName+".msgnum"));
jobResult.storeResult("basic.segnum",Integer.toString(ref));
HBCIUtils.log("basic values for " + getName() + " set to "
+ jobResult.getDialogId() + "/"
+ jobResult.getMsgNum()
+ "/" + jobResult.getSegNum(),
HBCIUtils.LOG_DEBUG);
}
}
/*
* speichert die HBCI-R�ckgabewerte f�r diesen GV im outStore ab. Dazu
* werden alle RetSegs durchgesehen; diejenigen, die den aktuellen GV
* betreffen, werden im @c data Property unter dem namen @c ret_i.*
* gespeichert. @i entspricht dabei dem @c retValCounter.
*/
protected void saveReturnValues(HBCIMsgStatus status,int sref)
{
HBCIRetVal[] retVals=status.segStatus.getRetVals();
String segref=Integer.toString(sref);
for (int i=0;i<retVals.length;i++) {
HBCIRetVal rv=retVals[i];
if (rv.segref!=null && rv.segref.equals(segref)) {
jobResult.jobStatus.addRetVal(rv);
}
}
/* bei Jobs, die mehrere Nachrichten ben�tigt haben, bewirkt das, dass nur
* der globStatus der *letzten* ausgef�hrten Nachricht gespeichert wird.
* Das ist aber auch ok, weil nach einem Fehler keine weiteren Nachrichten
* ausgef�hrt werden, so dass im Fehlerfall der fehlerhafte globStatus zur
* Verf�gung steht. Im OK-Fall werden h�chstens die OK-Meldungen der vorherigen
* Nachrichten �berschrieben. */
jobResult.globStatus=status.globStatus;
}
/* diese Methode wird i.d.R. durch abgeleitete GV-Klassen �berschrieben, um die
R�ckgabedaten in einem passenden Format abzuspeichern. Diese default-Implementation
tut nichts */
protected void extractResults(HBCIMsgStatus msgstatus,String header,int idx)
{
}
private void extractPlaintextResults(HBCIMsgStatus status,String header,int idx)
{
Properties result=status.getData();
for (Enumeration e=result.keys();e.hasMoreElements();) {
String key=(String)(e.nextElement());
if (key.startsWith(header+".")) {
jobResult.storeResult(HBCIUtilsInternal.withCounter("content",idx)+
"."+
key.substring(header.length()+1),result.getProperty(key));
}
}
}
public HBCIJobResult getJobResult()
{
return jobResult;
}
public HBCIPassportInternal getMainPassport()
{
return passports.getMainPassport();
}
private void _checkAccountCRC(String frontendname,
String blz,String number)
{
// pruefsummenberechnung nur wenn blz/kontonummer angegeben sind
if (blz==null || number==null) {
return;
}
if (blz.length()==0 || number.length()==0) {
return;
}
// daten merken, die im urspruenglich verwendet wurden (um spaeter
// zu wissen, ob sie korrigiert wurden)
String orig_blz=blz;
String orig_number=number;
while (true) {
// daten validieren
boolean crcok=HBCIUtils.checkAccountCRC(blz,number);
// aktuelle daten merken
String old_blz=blz;
String old_number=number;
if (!crcok) {
// wenn beim validieren ein fehler auftrat, nach neuen daten fragen
StringBuffer sb=new StringBuffer(blz).append("|").append(number);
HBCIUtilsInternal.getCallback().callback(getMainPassport(),
HBCICallback.HAVE_CRC_ERROR,
HBCIUtilsInternal.getLocMsg("CALLB_HAVE_CRC_ERROR"),
HBCICallback.TYPE_TEXT,
sb);
int idx=sb.indexOf("|");
blz=sb.substring(0,idx);
number=sb.substring(idx+1);
}
if (blz.equals(old_blz) && number.equals(old_number)) {
// blz und kontonummer auch nach rueckfrage unveraendert,
// also tatsaechlich mit diesen daten weiterarbeiten
break;
}
}
if (!blz.equals(orig_blz)) {
setParam(frontendname+".KIK.blz",blz);
}
if (!number.equals(orig_number)) {
setParam(frontendname+".number",number);
}
}
private void _checkIBANCRC(String frontendname,String iban)
{
// pruefsummenberechnung nur wenn iban vorhanden ist
if (iban==null || iban.length()==0) {
return;
}
// daten merken, die im urspruenglich verwendet wurden (um spaeter
// zu wissen, ob sie korrigiert wurden)
String orig_iban=iban;
while (true) {
boolean crcok=HBCIUtils.checkIBANCRC(iban);
String old_iban=iban;
if (!crcok) {
StringBuffer sb=new StringBuffer(iban);
HBCIUtilsInternal.getCallback().callback(getMainPassport(),
HBCICallback.HAVE_IBAN_ERROR,
HBCIUtilsInternal.getLocMsg("CALLB_HAVE_IBAN_ERROR"),
HBCICallback.TYPE_TEXT,
sb);
iban=sb.toString();
}
if (iban.equals(old_iban)) {
// iban unveraendert
break;
}
}
if (!iban.equals(orig_iban)) {
setParam(frontendname+".iban",iban);
}
}
protected void checkAccountCRC(String frontendname)
{
String[][] data=constraints.get(frontendname+".blz");
if (data!=null && data.length!=0) {
// wenn es tatsaechlich einen frontendparamter der form acc.blz gibt,
// brauchen wir zunaechst den "basis-namen" ("acc")
String paramname=data[0][0];
String lowlevelHeader=paramname.substring(0,paramname.lastIndexOf(".KIK.blz"));
// basierend auf dem basis-namen blz/number holen
String blz=llParams.getProperty(lowlevelHeader+".KIK.blz");
String number=llParams.getProperty(lowlevelHeader+".number");
// blz/number ueberpruefen
_checkAccountCRC(frontendname, blz,number);
}
// analoges fuer die IBAN
String[][] data2=constraints.get(frontendname+".iban");
if (data2!=null && data2.length!=0) {
String paramname=data2[0][0];
String lowlevelHeader=paramname.substring(0,paramname.lastIndexOf(".iban"));
String iban=llParams.getProperty(lowlevelHeader+".iban");
_checkIBANCRC(frontendname, iban);
}
}
public void addSignaturePassport(HBCIPassport passport,String role)
{
HBCIUtils.log("adding additional passport to job "+getName(),
HBCIUtils.LOG_DEBUG);
passports.addPassport((HBCIPassportInternal)passport,role);
}
public HBCIPassportList getSignaturePassports()
{
return passports;
}
// die default-implementierung holt einfach aus den job-parametern
// den genannten wert. eine bestimmte GV-klasse kann das �berschreiben,
// um "besondere Werte" (z.B. sumValues) irgendwie anders zu errechnen
public String getChallengeParam(String path)
{
String result;
if (path.equals("SegHead.code")) {
/* this special value is required for HHD1.3, where the segcode
* can be part of the challenge parameters, but the segcode is
* not a "lowlevel param", so have to handle this manually */
result=getHBCICode();
} else {
// normal lowlevel param
String valuePath=this.getName()+"."+path;
result=this.getLowlevelParam(valuePath);
}
return result;
}
/**
* Liefert das Auftraggeber-Konto, wie es ab HKTAN5 erforderlich ist.
* @return das Auftraggeber-Konto oder NULL, wenn keines angegeben ist.
*/
public Konto getOrderAccount()
{
// Checken, ob wir das Konto unter "My.[number/iban]" haben
String prefix = this.getName() + ".My.";
String number = this.getLowlevelParam(prefix + "number");
String iban = this.getLowlevelParam(prefix + "iban");
if ((number == null || number.length() == 0) && (iban == null || iban.length() == 0))
{
// OK, vielleicht unter "KTV.[number/iban]"?
prefix = this.getName() + ".KTV.";
number = this.getLowlevelParam(prefix + "number");
iban = this.getLowlevelParam(prefix + "iban");
if ((number == null || number.length() == 0) && (iban == null || iban.length() == 0))
return null; // definitiv kein Konto vorhanden
}
Konto k = new Konto();
k.number = number;
k.iban = iban;
k.bic = this.getLowlevelParam(prefix + "bic");
k.subnumber = this.getLowlevelParam(prefix + "subnumber");
k.blz = this.getLowlevelParam(prefix + "KIK.blz");
k.country = this.getLowlevelParam(prefix + "KIK.country");
return k;
}
public HBCIHandler getParentHandler()
{
return this.parentHandler;
}
public void addToQueue(String customerId)
{
getParentHandler().addJobToDialog(customerId,this);
}
public void addToQueue()
{
addToQueue(null);
}
protected boolean twoDigitValueInList(String value, String list)
{
boolean found=false;
int len=list.length();
if ((len&1)!=0) {
throw new InvalidArgumentException("list must have 2*n digits");
}
if (value.length()!=2) {
throw new InvalidArgumentException("value must have 2 digits");
}
for (int i=0; i<len; i+=2) {
String x=list.substring(i,i+2);
if (value.equals(x)) {
found=true;
break;
}
}
return found;
}
private static final Pattern INDEX_PATTERN = Pattern.compile("(\\w+\\.\\w+\\.\\w+)(\\.\\w+)?");
private String insertIndex(String key, Integer index)
{
if (index != null) {
Matcher m = INDEX_PATTERN.matcher(key);
if (m.matches()) {
return m.group(1) + '[' + index + ']' + (m.group(2) != null ? m.group(2) : "");
}
}
return key;
}
}