All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.kapott.hbci.protocol.SyntaxElement Maven / Gradle / Ivy

Go to download

HBCI4j - Home Banking Computer Interface for Java - Clone from https://github.com/hbci4j/hbci4java

There is a newer version: 3.5.46
Show newest version
/*  $Id: SyntaxElement.java,v 1.1 2011/05/04 22:38:03 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.protocol;

import lombok.extern.slf4j.Slf4j;
import org.kapott.hbci.exceptions.HBCI_Exception;
import org.kapott.hbci.exceptions.NoSuchPathException;
import org.kapott.hbci.manager.HBCIUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.util.ArrayList;
import java.util.Map;
import java.util.Iterator;
import java.util.List;

/* ein syntaxelement ist ein strukturelement einer hbci-nachricht (die nachricht
    selbst, eine segmentfolge, ein einzelnes segment, eine deg oder
    ein einzelnes de) */
@Slf4j
public abstract class SyntaxElement {

    public final static boolean TRY_TO_CREATE = true;
    public final static boolean DONT_TRY_TO_CREATE = false;
    public final static boolean ALLOW_OVERWRITE = true;
    public final static boolean DONT_ALLOW_OVERWRITE = false;
    private List childContainers = new ArrayList<>();
    /**
     * < @internal @brief alle in diesem element enthaltenen unterelemente
     */
    private String name;
    /**
     * < @internal @brief bezeichner fuer dieses element
     */
    private String type;
    private String path;
    /**
     * < @internal @brief nur beim parsen: zeichen, das vor diesem element stehen muesste
     */
    private boolean valid;
    /**
     * < @internal @brief indicates if this element is really valid, i.e. will appear in
     * an outgoing hbci message resp. in returned results from an incoming message
     */

    private MultipleSyntaxElements parent;
    // Wird von einigen Rewriter-Modules beim Parsen verwendet, um im Antwort-String
    // an der richtigen Stelle Daten auszuschneiden oder einzufügen.
    // TODO: Problem dabei ist nur: sobald auch nur *ein* Rewriter die Antwort-
    // Message verändert, stimmen von allen anderen SyntaxElementen die
    // Werte für posInMsg nicht mehr (es sei denn, es wird nach dem
    // Verändern ein neues MSG-Objekt erzeugt).
    private int posInMsg;
    private Document document;
    private Node def;
    /**
     * wird fuer datenelemente benoetigt, die sonst unbeabsichtigt generiert werden koennten.
     * das problem ist, dass es datenelemente (bisher nur bei segmenten bekannt) gibt,
     * die aus einigen "required" unterelementen bestehen und aus einigen optionalen
     * unterelementen. wenn *alle* "required" elemente bereits durch predefined values
     * bzw. durch automatisch generierte werte vorgegeben sind, dann wird das entsprechende
     * element erzeugt, da es auch ohne angabe der optionalen unterelemente gueltig ist.
     * 

* es ist aber u.U. gar nicht beabsichtigt, dass dieses element erzeugt wird (beispiel * segment "KIOffer", wo nur die DEG SegHead required ist, alle anderen elemente sind * optional). es kann also vorkommen, dass ein element *unbeabsichtigt* nur aus den * vorgabedaten erzeugt wird. *

* bei den elementen, bei denen das passieren kann, wird in der xml-spezifikation * deshalb zusaetzlich das attribut "needsRequestTag" angegeben. der wert dieses * attributes wird hier in der variablen @p needsRequestTag gespeichert. *

* beim ueberpruefen, ob das aktuelle element gueltig ist (mittels @c validate() ), * wird neben der gueltigkeit aller unterelemente zusaetzlich ueberprueft, ob dieses * element ein request-tag benoetigt, und wenn ja, ob es vorhanden ist. wenn die *

* needsRequestTag -bedingung nicht erfuellt ist, ist auch das element ungueltig, * und es wird nicht erzeugt. *

* das vorhandensein eines request-tags wird in der variablen @haveRequestTag * gespeichert. dieses flag kann fuer ein bestimmtes element gesetzt werden, indem * ihm der wert "requested" zugewiesen wird. normalerweise kann nur DE-elementen * ein wert zugewiesen werden, diese benoetigen aber kein request-tag. wird also einem * gruppierenden element der wert "requested" zugewiesen, dann wird das durch die * methode @c propagateValue() als explizites setzen des @p haveRequestTag * interpretiert. *

* alle klassen und methoden, die also daten fuer die erzeugung von nachrichten * generieren, muessen u.U. fuer bestimmte syntaxelemente diesen "requested"-wert * setzen. *

* needsRequestTag kann komplett weg, oder? -- nein. Für GV-Segmente * gilt das schon. Die Überprüfung des requested-Werted findet aber * in der *allgemeinen* SyntaxElement-Klasse statt, wo auch andere * Segmente (z.b. MsgHead) erzeugt werden. Wenn als "allgemeiner" * Check der Check "if SEG.isRequested" eingeführt werden würde, dann * würde der nur bei tatsächlich gewünschten GV-Segmenten true ergeben. * Bei MsgHead-Segmenten z.B. würde er false ergeben (weil diese * Segmente niemals auf "requested" gesetzt werden). Deshalb darf diese * "requested"-Überprüfung nur bei den Syntaxelementen stattfinden, * bei denen das explizit gewünscht ist (needsRequestTag). */ private boolean needsRequestTag; private boolean haveRequestTag; /** * es wird ein syntaxelement mit der id 'name' initialisiert; der pfad bis zu * diesem element wird in 'path' uebergeben; 'idx' ist die nummer dieses * elementes innerhalb der syntaxelementliste fuer dieses element (falls ein * bestimmtes syntaxelement mehr als einmal auftreten kann) */ protected SyntaxElement(String type, String name, String path, int idx, Document document) { initData(type, name, path, idx, document); } // TODO: aus konsistenz-gründen auch in MultipleSyntaxElements create und // createAndAdd trennen /** * beim parsen: initialisiert ein neues syntaxelement mit der id 'name'; in * 'path' wird der pfad bis zu dieser stelle uebergeben 'predelim' gibt das * delimiter-zeichen an, das beim parsen vor diesem document- element stehen * muesste 'idx' ist die nummer des syntaxelementes innerhalb der * uebergeordneten liste (die liste repraesentiert das evtl. mehrmalige * auftreten eines syntaxelementes, siehe class syntaxelementlist) 'res' ist * der zu parsende String 'predefs' soll eine menge von pfad-wert-paaren * enthalten, die fuer einige syntaxelemente den wert angeben, den diese * elemente zwingend haben muessen (z.b. ein bestimmter segmentcode o.ae.) */ protected SyntaxElement(String type, String name, String path, char predelim, int idx, StringBuilder res, int fullResLen, Document document, Map predefs, Map valids) { initData(type, name, path, predelim, idx, res, fullResLen, document, predefs, valids); } /** * gibt einen string mit den typnamen (msg,seg,deg,de,...) des * elementes zurueck */ protected abstract String getElementTypeName(); /** * liefert das delimiter-zeichen zurueck, dass innerhalb dieses * syntaxelementes benutzt wird, um die einzelnen child-elemente voneinander * zu trennen */ protected abstract char getInDelim(); /** * erzeugt einen neuen Child-Container, welcher durch den * xml-knoten 'ref' identifiziert wird; wird beim erzeugen von elementen * benutzt */ protected abstract MultipleSyntaxElements createNewChildContainer(Node ref, Document document); /** * beim parsen: haengt an die 'childElements' ein neues Element an. der * xml-knoten 'ref' gibt an, um welches element es sich dabei handelt; aus * 'res' (der zu parsende String) wird der wert fuer das element ermittelt * (falls es sich um ein de handelt); in 'predefined' ist der wert des * elementes zu finden, der laut syntaxdefinition ('document') an dieser stelle * auftauchen mueste (optional; z.b. fuer segmentcodes); 'predelim*' geben * die delimiter an, die direkt vor dem zu erzeugenden syntaxelement * auftauchen muessten */ protected abstract MultipleSyntaxElements parseNewChildContainer(Node ref, char predelim0, char predelim1, StringBuilder res, int fullResLen, Document document, Map predefs, Map valids); private void initData(String type, String name, String ppath, int idx, Document document) { if (getElementTypeName().equals("SEG")) log.trace("creating segment " + ppath + " -> " + name + "(" + idx + ")"); this.type = type; this.name = name; this.document = document; /* der pfad wird gebildet aus bisherigem pfad plus name des elementes plus indexnummer, falls diese groesser 0 ist */ StringBuilder temppath = new StringBuilder(128); if (ppath != null && ppath.length() != 0) temppath.append(ppath).append("."); temppath.append(HBCIUtils.withCounter(name, idx)); this.path = temppath.toString(); setValid(false); if (document != null) { this.def = getSyntaxDef(type, document); // erzeugen der child-elemente String requestTag = ((Element) def).getAttribute("needsRequestTag"); if (requestTag != null && requestTag.equals("1")) needsRequestTag = true; int syntaxIdx = 0; for (Node ref = def.getFirstChild(); ref != null; ref = ref.getNextSibling()) { if (ref.getNodeType() == Node.ELEMENT_NODE) { MultipleSyntaxElements child = createAndAppendNewChildContainer(ref, document); if (child != null) { child.setParent(this); child.setSyntaxIdx(syntaxIdx); if (getElementTypeName().equals("MSG")) log.trace("child container " + child.getPath() + " has syntaxIdx=" + child.getSyntaxIdx()); } syntaxIdx++; } } /* durchlaufen aller "value"-knoten und setzen der werte der entsprechenden de */ // TODO: effizienter: das nicht hier machen, sondern später, // Wenn wir das *hier* machen, dann werden ja DOCH wieder // alle "minnum=0"-Segmente // erzeugt, weil für jedes Segment code und version gesetzt // werden müssten. Am besten das immer in dem Moment machen, // wo ein entsprechendes SyntaxDE erzeugt wird. // --> nein, das geht hier. Grund: die optimierte Message-Engine // wird nur für Segmentfolgen angewendet. Und in Segmentfolgen- // Definitionen sind keine values oder valids angegeben, so dass // dieser Code hier gar keine Relevanz für Segmentfolgen hat NodeList valueNodes = ((Element) def).getElementsByTagName("value"); int len = valueNodes.getLength(); String dottedPath = this.path + "."; for (int i = 0; i < len; i++) { Node valueNode = valueNodes.item(i); String valuePath = ((Element) valueNode).getAttribute("path"); String value = (valueNode.getFirstChild()).getNodeValue(); String destpath = dottedPath + valuePath; if (!propagateValue(destpath, value, TRY_TO_CREATE, DONT_ALLOW_OVERWRITE)) throw new NoSuchPathException(destpath); } /* durchlaufen aller "valids"-knoten und speichern der valid-values */ // TODO: das hier ebenfalls später machen, siehe "values" NodeList validNodes = ((Element) def).getElementsByTagName("valids"); len = validNodes.getLength(); dottedPath = getPath() + "."; for (int i = 0; i < len; i++) { Node validNode = validNodes.item(i); String valuePath = ((Element) (validNode)).getAttribute("path"); String absPath = dottedPath + valuePath; NodeList validvalueNodes = ((Element) (validNode)).getElementsByTagName("validvalue"); int len2 = validvalueNodes.getLength(); for (int j = 0; j < len2; j++) { Node validvalue = validvalueNodes.item(j); String value = (validvalue.getFirstChild()).getNodeValue(); storeValidValueInDE(absPath, value); } } } } protected void init(String type, String name, String path, int idx, Document document) { initData(type, name, path, idx, document); } protected MultipleSyntaxElements createAndAppendNewChildContainer(Node ref, Document document) { MultipleSyntaxElements ret = createNewChildContainer(ref, document); if (ret != null) addChildContainer(ret); return ret; } protected boolean storeValidValueInDE(String destPath, String value) { boolean ret = false; for (Iterator i = childContainers.listIterator(); i.hasNext(); ) { MultipleSyntaxElements l = i.next(); if (l.storeValidValueInDE(destPath, value)) { ret = true; break; } } return ret; } // ------------------------------------------------------------------------------------------- /** * loop through all child-elements; the segments found there * will be sequentially enumerated starting with num startValue; * if startValue is zero, the segments will not be enumerated, * but all given the number 0 * * @param startValue value to be used for the first segment found * @return next sequence number usable for enumeration */ public int enumerateSegs(int startValue, boolean allowOverwrite) { int idx = startValue; for (MultipleSyntaxElements s : getChildContainers()) { if (s != null) idx = s.enumerateSegs(idx, allowOverwrite); } return idx; } private void initData(String type, String name, String ppath, char predelim, int idx, StringBuilder res, int fullResLen, Document document, Map predefs, Map valids) { this.type = type; this.name = name; this.parent = null; this.childContainers = new ArrayList<>(); this.needsRequestTag = false; this.haveRequestTag = false; this.document = document; this.def = null; /* position des aktuellen datenelementes berechnet sich aus der * gesamtlänge des ursprünglichen msg-strings minus der länge des * reststrings, der jetzt zu parsen ist, und der mit dem aktuellen * datenelement beginnt */ this.posInMsg=fullResLen-res.length(); StringBuilder temppath = new StringBuilder(128); if (ppath != null && ppath.length() != 0) temppath.append(ppath).append("."); temppath.append(HBCIUtils.withCounter(name, idx)); this.path = temppath.toString(); setValid(false); if (document != null) { this.def = getSyntaxDef(type, document); /* fuellen der 'predefs'-tabelle mit den in der syntaxbeschreibung vorgegebenen werten */ NodeList valueNodes = ((Element) def).getElementsByTagName("value"); String dottedPath = getPath() + "."; int len = valueNodes.getLength(); for (int i = 0; i < len; i++) { Node valueNode = valueNodes.item(i); String valuePath = ((Element) valueNode).getAttribute("path"); String value = (valueNode.getFirstChild()).getNodeValue(); predefs.put(dottedPath + valuePath, value); } if (valids != null) { /* durchlaufen aller "valids"-knoten und speichern der valid-values */ NodeList validNodes = ((Element) def).getElementsByTagName("valids"); len = validNodes.getLength(); for (int i = 0; i < len; i++) { Node validNode = validNodes.item(i); String valuePath = ((Element) (validNode)).getAttribute("path"); String absPath = dottedPath + valuePath; NodeList validvalueNodes = ((Element) (validNode)).getElementsByTagName("validvalue"); int len2 = validvalueNodes.getLength(); for (int j = 0; j < len2; j++) { Node validvalue = validvalueNodes.item(j); String value = (validvalue.getFirstChild()).getNodeValue(); valids.put(HBCIUtils.withCounter(absPath + ".value", j), value); } } } // anlegen der child-elemente int counter = 0; for (Node ref = def.getFirstChild(); ref != null; ref = ref.getNextSibling()) { if (ref.getNodeType() == Node.ELEMENT_NODE) { MultipleSyntaxElements child = parseAndAppendNewChildContainer(ref, ((counter++) == 0) ? predelim : getInDelim(), getInDelim(), res, fullResLen, document, predefs, valids); if (child != null) { child.setParent(this); // TODO: this is a very very dirty hack to fix the problem with the params-template; // bei der SF "Params", die mit referenziert wird, // soll nach jedem erfolgreich in die SF aufgenommenen Param-Segment eine neue // SF begonnen werden, damit das Problem mit dem am Ende der SF stehenden Template- // Param-Segment nicht mehr auftritt // dazu wird beim hinzufuegen von segmenten zur sf ueberprueft, ob diese evtl. bereits // segmente enthaelt (hasValidChilds()). falls das der fall ist, so wird // kein neues segment hinzugefuegt // analoges gilt für die SF "GVRes" - hier muss dafür gesorgt werden, dass jede // antwort in ein eigenes GVRes kommt, damit die zuordnung reihenfolge-erkennung // der empfangenen GVRes-segmente funktioniert (in HBCIJobImpl.fillJobResult()) if ((this instanceof SF) && (getName().equals("Params") || getName().equals("GVRes")) && ((MultipleSEGs) child).hasValidChilds()) { break; } } } } } // if there was no error until here, this syntaxelement is valid setValid(true); } protected void init(String type, String name, String path, char predelim, int idx, StringBuilder res, int fullResLen, Document document, Map predefs, Map valids) { initData(type, name, path, predelim, idx, res, fullResLen, document, predefs, valids); } protected MultipleSyntaxElements parseAndAppendNewChildContainer(Node ref, char predelim0, char predelim1, StringBuilder res, int fullResLen, Document document, Map predefs, Map valids) { MultipleSyntaxElements ret = parseNewChildContainer(ref, predelim0, predelim1, res, fullResLen, document, predefs, valids); if (ret != null) addChildContainer(ret); return ret; } /** * fuellt die hashtable 'values' mit den werten der de-syntaxelemente; dazu * wird in allen anderen typen von syntaxelementen die liste der * child-elemente durchlaufen und deren 'fillValues' methode aufgerufen */ public void extractValues(Map values) { for (MultipleSyntaxElements l : childContainers) { l.extractValues(values); } } // ------------------------------------------------------------------------------------------- private void addChildContainer(MultipleSyntaxElements x) { childContainers.add(x); } /** * @return the ArrayList containing all child-elements (the elements * of the ArrayList are instances of the SyntaxElementArray class */ public List getChildContainers() { return childContainers; } /** * setzt den wert eines de; in allen syntaxelementen ausser DE wird dazu die * liste der child-elemente durchlaufen; jedem dieser child-elemente wird der * wert zum setzen uebergeben; genau _eines_ dieser elemente wird sich dafuer * zustaendig fuehlen (das DE mit 'path'='destPath') und den wert uebernehmen */ // TODO: code splitten public boolean propagateValue(String destPath, String value, boolean tryToCreate, boolean allowOverwrite) { boolean ret = false; if (destPath.equals(getPath())) { if (value != null && value.equals("requested")) this.haveRequestTag = true; else throw new HBCI_Exception(HBCIUtils.getLocMsg("EXCMSG_INVVALUE", new Object[]{destPath, value})); ret = true; } else { // damit überspringen wir gleich elemente, bei denen es mit // sicherheit nicht funktionieren kann if (destPath.startsWith(getPath())) { for (MultipleSyntaxElements l : childContainers) { if (l.propagateValue(destPath, value, tryToCreate, allowOverwrite)) { ret = true; break; } } if (!ret && tryToCreate) { // der Wert konnte nicht gesetzt werden -> möglicherweise // existiert ja nur der entsprechende child-container noch // nicht log.trace(getPath() + ": could not set value for " + destPath); // Namen des fehlenden Elementes ermitteln String subPath = destPath.substring(getPath().length() + 1); log.trace(" subpath is " + subPath); int dotPos = subPath.indexOf('.'); if (dotPos == -1) { dotPos = subPath.length(); } String subType = subPath.substring(0, dotPos); log.trace(" subname is " + subType); int counterPos = subType.indexOf('_'); if (counterPos != -1) { subType = subType.substring(0, counterPos); } log.trace(" subType is " + subType); // hier überprüfen, ob es wirklich noch keinen child-container // mit diesem Namen gibt. Wenn z.B. der pfad msg.gv.ueb.kik.blz // gesucht wird und msg.gv schon existiert, wird diese methode // hier in msg.gv ausgeführt. wenn sie fehlschlägt (z.b. weil // tatsächlich kein .ueb.kik.blz angelegt werden kann), wird false // ("can not propagate") zurückgegeben. im übergeordneten modul // (msg) soll dann nicht versucht werden, das nächste sub-element // (gv) anzulegen - dieser test merkt, dass es "gv" schon gibt boolean found = false; for (MultipleSyntaxElements c : childContainers) { if (c.getName().equals(subType)) { found = true; break; } } if (!found) { // jetzt durch alle child-elemente des definierenden XML-Knotens // loopen und den ref-Knoten suchen, der das fehlende Element // beschreibt int newChildIdx = 0; Node ref; found = false; for (ref = def.getFirstChild(); ref != null; ref = ref.getNextSibling()) { if (ref.getNodeType() == Node.ELEMENT_NODE) { String type = ((Element) ref).getAttribute("type"); String name = ((Element) ref).getAttribute("name"); if (name.length() == 0) { name = type; } if (name.equals(subType)) { found = true; break; } newChildIdx++; } } if (found) { // entsprechenden child-container erzeugen MultipleSyntaxElements child = createNewChildContainer(ref, document); child.setParent(this); child.setSyntaxIdx(newChildIdx); if (getElementTypeName().equals("MSG")) log.trace("child container " + child.getPath() + " has syntaxIdx=" + child.getSyntaxIdx()); // aktuelle child-container-liste durchlaufen und den neu // erzeugten child-container dort richtig einsortieren int newPosi = 0; for (MultipleSyntaxElements c : childContainers) { if (c.getSyntaxIdx() > newChildIdx) { // der gerade betrachtete child-container hat einen idx // größer als den des einzufügenden elementes, also wird // sich diese position gemerkt und das element hier eingefügt break; } newPosi++; } log.trace(" inserting child container with syntaxIdx " + newChildIdx + " at position " + newPosi); childContainers.add(newPosi, child); // now try to propagate the value to the newly created child ret = child.propagateValue(destPath, value, tryToCreate, allowOverwrite); } } else { log.trace(" subtype " + subType + " already existing - will not try to create"); } } } } return ret; } /** * @return den wert eines bestimmten DE; * funktioniert analog zu 'propagateValue' */ public String getValueOfDE(String path) { String ret = null; for (MultipleSyntaxElements l : childContainers) { ret = l.getValueOfDE(path); if (ret != null) { break; } } return ret; } public String getValueOfDE(String path, int zero) { String ret = null; for (MultipleSyntaxElements l : childContainers) { ret = l.getValueOfDE(path, 0); if (ret != null) { break; } } return ret; } /** * @param path path to the element to be returned * @return the element identified by path */ public SyntaxElement getElement(String path) { SyntaxElement ret = null; if (getPath().equals(path)) { ret = this; } else { for (MultipleSyntaxElements l : childContainers) { ret = l.getElement(path); if (ret != null) { break; } } } return ret; } /** * @return the path to this element */ public final String getPath() { return path; } protected void setPath(String path) { this.path = path; } /** * @return the name of this element (i.e. the last component of path) */ public String getName() { return name; } protected void setName(String name) { this.name = name; } public String getType() { return type; } protected void setType(String type) { this.type = type; } /** * @param type the name of the syntaxelement to be returned * @param document the structure containing the current syntaxdefinition * @return a XML-node with the definition of the requested syntaxelement */ public final Node getSyntaxDef(String type, Document document) { Node ret = document.getElementById(type); if (ret == null) throw new org.kapott.hbci.exceptions.NoSuchElementException(getElementTypeName(), type); return ret; } public boolean isValid() { return valid; } protected final void setValid(boolean valid) { this.valid = valid; } public int checkSegSeq(int value) { for (MultipleSyntaxElements a : childContainers) { value = a.checkSegSeq(value); } return value; } public String toString(int zero) { return toString(); } /** * ueberpreuft, ob das syntaxelement alle restriktionen einhaelt; ist das * nicht der fall, so wird eine Exception ausgeloest. die meisten * syntaxelemente koennen sich nicht selbst ueberpruefen, sondern rufen statt * dessen die validate-funktion der child-elemente auf */ public void validate() { if (!needsRequestTag || haveRequestTag) { for (MultipleSyntaxElements l : childContainers) { l.validate(); } /* wenn keine exception geworfen wurde, dann ist das aktuelle element offensichtlich valid */ setValid(true); } } public void getElementPaths(Map p, int[] segref, int[] degref, int[] deref) { } public MultipleSyntaxElements getParent() { return parent; } public void setParent(MultipleSyntaxElements parent) { this.parent = parent; } public int getPosInMsg() { return posInMsg; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy