org.kapott.hbci.dialog.HBCIJobsDialog Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hbci4j-adorsys Show documentation
Show all versions of hbci4j-adorsys Show documentation
HBCI4j - Home Banking Computer Interface for Java - Clone from https://github.com/hbci4j/hbci4java
/*
* Copyright 2018-2019 adorsys GmbH & Co KG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kapott.hbci.dialog;
import lombok.extern.slf4j.Slf4j;
import org.kapott.hbci.GV.AbstractHBCIJob;
import org.kapott.hbci.GV.GVTAN2Step;
import org.kapott.hbci.exceptions.HBCI_Exception;
import org.kapott.hbci.manager.*;
import org.kapott.hbci.passport.PinTanPassport;
import org.kapott.hbci.protocol.Message;
import org.kapott.hbci.status.HBCIExecStatus;
import org.kapott.hbci.status.HBCIMsgStatus;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/* @brief A class for managing exactly one HBCI-Dialog
A HBCI-Dialog consists of a number of HBCI-messages. These
messages will be sent (and the responses received) one
after the other, without timegaps between them (to avoid
network timeout problems).
The messages generated by a HBCI-Dialog are at first DialogInit-Message,
after that a message that contains one ore more "Geschaeftsvorfaelle"
(i.e. the stuff that you really want to do via HBCI), and at last
a DialogEnd-Message.
In this class we have two API-levels, a mid-level API (for manually
creating and processing dialogs) and a high-level API (for automatic
creation of typical HBCI-dialogs). For each method the API-level is
given in its description
*/
@Slf4j
public final class HBCIJobsDialog extends AbstractHbciDialog {
private HBCIMessageQueue queue = new HBCIMessageQueue();
private HashMap listOfGVs = new HashMap<>();
public HBCIJobsDialog(PinTanPassport passport) {
this(passport, null, -1);
}
public HBCIJobsDialog(PinTanPassport passport, String dialogId, long msgnum) {
super(passport);
this.dialogId = dialogId;
this.msgnum = msgnum;
}
public HBCIMessage addTask(AbstractHBCIJob job) {
return this.addTask(job, true);
}
public HBCIMessage addTask(AbstractHBCIJob job, boolean verify) {
try {
log.info(HBCIUtils.getLocMsg("EXCMSG_ADDJOB", job.getName()));
if (verify) {
job.verifyConstraints();
}
// check bpd.numgva here
String hbciCode = job.getHBCICode();
if (hbciCode == null) {
throw new HBCI_Exception(job.getName() + " not supported");
}
int gva_counter = listOfGVs.size();
Integer counter_st = listOfGVs.get(hbciCode);
int gv_counter = counter_st != null ? counter_st : 0;
int total_counter = getTotalNumberOfGVSegsInCurrentMessage();
gv_counter++;
total_counter++;
if (counter_st == null) {
gva_counter++;
}
// BPD: max. Anzahl GV-Arten
int maxGVA = passport.getMaxGVperMsg();
// BPD: max. Anzahl von Job-Segmenten eines bestimmten Typs
int maxGVSegJob = job.getMaxNumberPerMsg();
// Passport: evtl. weitere Einschränkungen bzgl. der Max.-Anzahl
// von Auftragssegmenten pro Nachricht
int maxGVSegTotal = passport.getMaxGVSegsPerMsg();
if ((maxGVA > 0 && gva_counter > maxGVA) ||
(maxGVSegJob > 0 && gv_counter > maxGVSegJob) ||
(maxGVSegTotal > 0 && total_counter > maxGVSegTotal)) {
if (maxGVSegTotal > 0 && total_counter > maxGVSegTotal) {
log.debug(
"have to generate new message because current type of passport only allows " + maxGVSegTotal + " GV segs per message");
} else {
log.debug(
"have to generate new message because of BPD restrictions for number of tasks per message; " +
"adding job to this new message");
}
newMsg();
gv_counter = 1;
}
listOfGVs.put(hbciCode, gv_counter);
HBCIMessage last = queue.getLast();
last.append(job);
return last;
} catch (Exception e) {
String msg = HBCIUtils.getLocMsg("EXCMSG_CANTADDJOB", job.getName());
log.error("task " + job.getName() + " will not be executed in current dialog");
throw new HBCI_Exception(msg, e);
}
}
private int getTotalNumberOfGVSegsInCurrentMessage() {
int total = 0;
for (Map.Entry hbciCode : listOfGVs.entrySet()) {
total += hbciCode.getValue();
}
log.debug("there are currently " + total + " GV segs in this message");
return total;
}
private void newMsg() {
log.debug("starting new message");
this.queue.append(new HBCIMessage());
listOfGVs.clear();
}
private List doJobs() {
log.info(HBCIUtils.getLocMsg("LOG_PROCESSING_JOBS"));
ArrayList messageStatusList = new ArrayList<>();
while (true) {
HBCIMessage msg = this.queue.poll();
if (msg == null) {
break;
}
patchMessageForSca(msg);
boolean veu = msg.getTasks().stream().anyMatch(AbstractHBCIJob::isVeu);
HBCIMsgStatus msgstatus = new HBCIMsgStatus();
boolean addMsgStatus = true;
try {
int taskNum = 0;
Message message = MessageFactory.createMessage(veu ? "CustomMsgVeu" : "CustomMsg", passport.getSyntaxDocument());
// durch alle jobs loopen, die eigentlich in der aktuellen
// nachricht abgearbeitet werden müssten
for (AbstractHBCIJob task : msg.getTasks()) {
if (task.skipped())
continue;
// Uebernimmt den aktuellen loop-Wert in die Lowlevel-Parameter
task.applyOffset();
task.setIdx(taskNum);
// Daten für den Task festlegen
String header = HBCIUtils.withCounter("GV", taskNum);
task.getLowlevelParams().forEach((key, value) ->
message.rawSet(header + "." + key, value));
taskNum++;
}
// Das passiert immer dann, wenn wir in der Message nur ein HKTAN#2 aus Prozess-Variante 2 hatten.
// Dieses aufgrund einer 3076-SCA-Ausnahme aber nicht benoetigt wird.
if (taskNum == 0) {
addMsgStatus = false;
break;
}
message.rawSet("MsgHead.dialogid", dialogId);
message.rawSet("MsgHead.msgnum", Long.toString(msgnum));
message.rawSet("MsgTail.msgnum", Long.toString(msgnum));
// nachrichtenaustausch durchführen
msgstatus = kernel.rawDoIt(message, null, HBCIKernel.SIGNIT, HBCIKernel.CRYPTIT);
nextMsgNum();
final int segnum = msgstatus.findTaskSegment();
if (segnum != 0) {
// für jeden Task die entsprechenden Rückgabedaten-Klassen füllen
for (AbstractHBCIJob task : msg.getTasks()) {
if (task.skipped())
continue;
try {
task.fillJobResult(msgstatus, segnum);
} catch (Exception e) {
msgstatus.addException(e);
}
}
}
if (msgstatus.hasExceptions()) {
log.error("aborting current loop because of errors");
break;
}
////////////////////////////////////////////////////////////////////
// Jobs erneut ausfuehren, falls noetig.
HBCIMessage newMsg = null;
for (AbstractHBCIJob task : msg.getTasks()) {
if (task.skipped())
continue;
AbstractHBCIJob redo = task.redo();
if (redo != null) {
// Nachricht bei Bedarf erstellen und an die Queue haengen
if (newMsg == null) {
newMsg = new HBCIMessage();
queue.append(newMsg);
}
// Task hinzufuegen
log.debug("repeat task " + redo.getName());
newMsg.append(redo);
}
}
//
////////////////////////////////////////////////////////////////////
} catch (Exception e) {
msgstatus.addException(e);
} finally {
if (addMsgStatus) {
messageStatusList.add(msgstatus);
}
}
}
return messageStatusList;
}
private void patchMessageForSca(HBCIMessage msg) {
//patch offset jobs, append missing hktan task
if (msg.findTask("HKTAN") == null) {
msg.getTasks().stream()
.filter(passport::tan2StepRequired)
.findAny()
.map(this::createHktan)
.ifPresent(msg::append);
}
}
private GVTAN2Step createHktan(AbstractHBCIJob tan2StepRequiredJob) {
final GVTAN2Step hktan = new GVTAN2Step(getPassport(), tan2StepRequiredJob);
hktan.setProcess(KnownTANProcess.PROCESS2_STEP1);
hktan.setSegVersion(getPassport().getCurrentSecMechInfo().getSegversion());
if (getPassport().tanMediaNeeded()) {
hktan.setParam("tanmedia", getPassport().getCurrentSecMechInfo().getMedium());
}
return hktan;
}
/**
* Ausführen aller bisher erzeugten Aufträge. Diese Methode veranlasst den HBCI-Kernel,
* die Aufträge, die durch die Aufrufe auszuführen.
*
* @param close
* @return ein Status-Objekt, anhand dessen der Erfolg oder das Fehlschlagen
* der Dialoge festgestellt werden kann.
*/
@Override
public HBCIExecStatus execute(boolean close) {
HBCIExecStatus ret;
log.debug("executing dialog");
try {
ret = new HBCIExecStatus(doJobs());
} catch (Exception e) {
ret = new HBCIExecStatus(null);
ret.addException(e);
}
if (close) {
dialogEnd();
}
return ret;
}
@Override
public long getMsgnum() {
return msgnum;
}
@Override
public boolean isAnonymous() {
return false;
}
private void nextMsgNum() {
msgnum++;
}
}