Dr. Vermes Mátyás1
2002. november 9.
Jelen dokumentum leírja az Electra tranzakciók feldolgozására kialakított környezetet. A leírás alapján reményeim szerint a programozók implementálni tudják az egyes tranzakciófajtákat.
Az Electrától az rpcelectra.exe XMLRPC szerver veszi át a tranzakciós kötegeket. A szerver a kötegeket ellenőrzés után letárolja az electra/elkoteg.dat filébe. Megjegyzendő, hogy az XMLRPC szerver sosem könyvel, hanem feladata csupán a kötegek adminisztrálása: átvétel, tárolás, jelentés a státuszokról.
A KELECTRA Kontó program figyeli az előbbi electra/elkoteg.dat-ban megjelenő kötegeket, és feldolgozza a bennük levő tételeket. A feldolgozás eredményét KELECTRA beteszi az electra/elnaplo.dat filébe, ahonnan az lekérdezhető.2
Az XMLRPC szerver is és a KELECTRA is a kötegeket objektumkönyvtárakon keresztül kezeli. Ez azt jelenti, hogy minden köteg, illetve tranzakciófajtához van egy objektumkönyvtár, amelyik az adott tranzakció adminisztrálását és lekönyvelését végzi. Jelenleg a következő Electra tranzakciós könyvtárak léteznek:
Az objektumkönyvtárak Linuxon shared libként (so) vannak megvalósítva. Ez lehetővé teszi, hogy az implementáció javítgatásakor ne kelljen mindent újrafordítani, újralinkelni, hanem elegendő a javított objektumkönyvtár binárisát (az so filét) átadni a banknak. Az objektum implementációk majdnem teljesen készen vannak, egyetlen (a legfontosabb) metódus hiányzik csak belőlük, a tételek tényleges feldolgozását végző process metódus:
A továbbiak háttérinformációt szolgáltatnak a hiányzó process metódusok megírásához.
Minden előbb felsorolt tranzakcióhoz tartozik egy XML kötegformátum. A kötegformátumokat nem én találtam ki, hanem készen kaptam a Cardináltól. Példaként nézzük az inkasszók kötegformátumát:
<!ELEMENT INKORD - - ( HD& ACCNO& DETAILS ) >
<!ELEMENT HD - - ( OSN ) >
<!ELEMENT OSN - O ( #PCDATA) -- A csomag egyedi azonositoja -->
<!ELEMENT ACCNO - - ( ACC& CSN ) >
<!ELEMENT ACC - O ( #PCDATA ) -- Terhelendo szamlaszam -->
<!ELEMENT CSN - O ( #PCDATA ) -- A szamla devizaneme -->
<!ELEMENT DETAILS - - ( INK* ) >
<!ELEMENT INK - - ( SQN?& DAC?& DNM?& DBN?& BSZ?&
KZ1?& KZ2?& KZ3?& AP?& AMT? ) >
<!ELEMENT SQN - O ( #PCDATA ) -- Tetelsorszam a csomagon belul -->
<!ELEMENT DAC - O ( #PCDATA ) -- Fizetesre kotelezett szamlaszama -->
<!ELEMENT DNM - O ( #PCDATA ) -- Fizetesre kotelezett neve -->
<!ELEMENT DBN - O ( #PCDATA ) -- Fizetesre kotelezett bankja -->
<!ELEMENT BSZ - O ( #PCDATA ) -- Bizonylatszam -->
<!ELEMENT KZ1 - O ( #PCDATA ) -- Kozlemeny -->
<!ELEMENT KZ2 - O ( #PCDATA ) -- Kozlemeny -->
<!ELEMENT KZ3 - O ( #PCDATA ) -- Kozlemeny -->
<!ELEMENT AMT - O ( #PCDATA ) -- Beszedendo osszeg ( 999999999999,00 )-->
<!ELEMENT IKA - O ( #PCDATA ) -- Benyujtas inkoda:
'a'=fizeto bejelentese alapjan,
'e'=jogszabaly alapjan,
'z'=hataridos -->
<!ELEMENT IKK - O ( #PCDATA ) -- Jogszabalyszam -->
<!ELEMENT IKH - O ( #PCDATA ) -- Hataridos inkasszo hatarideje -->
A formátumokat ún. DTD-kben (Document Type Definition) adták. A DTD-k részleteivel nem kell foglalkoznunk, a lényeg a következő:
A többi tranzakciófajta ugyanilyen kötegben jön, csak a kötegtípus nevében, illetve a tételek mezőiben van eltérés. A tételekben levő <sqn> tag (kötegen belüli azonosító) egységesen mindenhol definiálva van.
Minden kötgetípushoz készítettem egy objektumot, ami az adott köteg adatait tudja tárolni. A kötegobjektumok közös őse az electrakoteg objektum, melynek definíciója:
*****************************************************************************
// electra köteg
//
// Az osztály absztrakt (nincs definiálva electrakotegNew());
// a származtatott osztálynak definiálnia kell az alábbiakat:
//
// xmlname : a köteget azonosító tag neve
// xmlitemname : a details-ban levő tagok neve
// newitem : csinál egy új (megfelelő osztályú) itemet
****************************************************************************
function electrakotegClass()
static clid:=electrakotegRegister()
return clid
****************************************************************************
static function electrakotegRegister()
local clid:=classRegister("electrakoteg",{objectClass()})
classMethod(clid,"initialize",{|this,osn|electrakotegIni(this,osn)})
classAttrib(clid,"osn") // csomag azonosító
classAttrib(clid,"acc") // számlaszám
classAttrib(clid,"csn") // számla devizaneme
classAttrib(clid,"details") // tételek listája
classMethod(clid,"additem",{|this,item|_electrakoteg_additem(this,item)})
classMethod(clid,"obj2xml",{|this|_electrakoteg_obj2xml(this)}) //konverzió xml-re
classMethod(clid,"xml2obj",{|this,xml,opt|_electrakoteg_xml2obj(this,xml,opt)}) //betöltés xml-ből
classMethod(clid,"list",{|this,xml|_electrakoteg_list(this,xml)}) //listázás
return clid
****************************************************************************
function electrakotegIni(this,osn)
objectIni(this)
this:osn:=""
this:acc:=""
this:csn:=""
this:details:={}
if( osn!=NIL )
this:osn:=osn
end
return this
*****************************************************************************
Látjuk, hogy a kötegobjektum
A konkrét tranzakciós kötegeket a fenti electrakoteg osztályból származtatjuk. Példaként nézzük az inkasszók esetét. Az electrainkord osztály kiegészíti electrakoteg-et néhány fajtaspecifikus metódussal.
****************************************************************************
function electrainkordClass()
static clid:=electrainkordRegister()
return clid
****************************************************************************
static function electrainkordRegister()
local clid:=classRegister("electrainkord",{electrakotegClass()})
classMethod(clid,"initialize",{|this,osn|electrainkordIni(this,osn)})
//a különféle típusú kötegek abban különböznek
//hogy az alábbi metódusokat másképp definiálják
classMethod(clid,"xmlname",{||"inkord"})
classMethod(clid,"xmlitemname",{||"ink"})
classMethod(clid,"newitem",{||electrainkorditemNew()})
return clid
****************************************************************************
function electrainkordNew(osn)
local clid:=electrainkordClass()
return objectNew(clid):initialize(osn)
****************************************************************************
function electrainkordIni(this,osn)
electrakotegIni(this,osn)
return this
*****************************************************************************
Végül definiálni kell az inkasszó tételeket tartalmazó objektumot, az electrainkroditem-et (ilyenek vannak a detailsben). Ennek pontosan olyan attribútumai vannak, mint a DTD-nek. Minden attribútum üresre van inicializálva, ami biztosítja, hogy a CCC-ben meghatározott típusa legyen.
****************************************************************************
function electrainkorditemClass()
static clid:=electrainkorditemRegister()
return clid
****************************************************************************
static function electrainkorditemRegister()
local clid:=classRegister("electrainkorditem",{objectClass()})
classMethod(clid,"initialize",{|this|electrainkorditemIni(this)})
classAttrib(clid,"sqn") //csomagon belüli tételsorszám
classAttrib(clid,"dac") //fizetésre kötelezett számla
classAttrib(clid,"dnm") //fizetésre kötelezett neve
classAttrib(clid,"dbn") //fizetésre kötelezett bankja
classAttrib(clid,"bsz") //bizonylatszám
classAttrib(clid,"kz1") //közlemény
classAttrib(clid,"kz2") //közlemény
classAttrib(clid,"kz3") //közlemény
classAttrib(clid,"amt") //átutalt összeg
classAttrib(clid,"ika") //benyújtás indoka: a, e, z
classAttrib(clid,"ikk") //jogszabályszám
classAttrib(clid,"ikh") //határidős inkasszó határideje
classMethod(clid,"obj2xml",{|this|_electrainkorditem_obj2xml(this)}) //konverzió xml-re
classMethod(clid,"process",{|this,ktg|_electrainkorditem_process(this,ktg)})
return clid
****************************************************************************
function electrainkorditemNew()
local clid:=electrainkorditemClass()
return objectNew(clid):initialize()
****************************************************************************
function electrainkorditemIni(this)
objectIni(this)
this:sqn:=0
this:dac:=""
this:dnm:=""
this:dbn:=""
this:bsz:=""
this:kz1:=""
this:kz2:=""
this:kz3:=""
this:amt:=0
this:ika:=""
this:ikk:=""
this:ikh:=ctod("")
return this
*****************************************************************************
A tételobjektumok process metódusa fogja elvégezni a tétel tényleges Kontóbeli feldolgozását.
Az Electra az rpcelectra.exe XMLRPC szerver metódushívásaival adogatja a kötegeket. A szerver főprogramja a következő:
function main(port)
local server
set printer to log-rpcelectra additive
set printer on
set console off
alertblock({|t,a|xmlrpc_alert(t,a)})
kontoini()
server:=xmlrpcserverNew(port)
server:keepalive:=.t.
//server:debug:=.t.
//server:recover:=.f.
server:addmethod("electra.getversion" ,{|sid|_electra_getversion(sid)})
server:addmethod("electra.konyvnap" ,{|sid|_electra_konyvnap(sid)})
server:addmethod("electra.sysstate" ,{|sid|_electra_sysstate(sid)})
server:addmethod("electra.huford" ,{|sid,data,chk|_electra_huford(sid,data,chk)})
server:addmethod("electra.hveord" ,{|sid,data,chk|_electra_hveord(sid,data,chk)})
server:addmethod("electra.inkord" ,{|sid,data,chk|_electra_inkord(sid,data,chk)})
server:addmethod("electra.vibord" ,{|sid,data,chk|_electra_vibord(sid,data,chk)})
server:addmethod("electra.devord" ,{|sid,data,chk|_electra_devord(sid,data,chk)})
server:addmethod("electra.dveord" ,{|sid,data,chk|_electra_dveord(sid,data,chk)})
server:addmethod("electra.bleord" ,{|sid,data,chk|_electra_bleord(sid,data,chk)})
server:addmethod("electra.bfeord" ,{|sid,data,chk|_electra_bfeord(sid,data,chk)})
server:addmethod("electra.naplo" ,{|sid,ktg|_electra_naplo(sid,ktg)})
server:closeblock:={|s,r|xmlrpc_verifyconnection(s,r)}
server:loopfreq:=5000
server:loopblock:={||fflush()}
xmlrpc_register(server,"electra",VERSION)
server:loop
return NIL
Nézzük példaként az inkord (inkasszó) köteg fogadását:
static function _electra_inkord(sid,data,chk)
local o,s:=session_validate_sid(sid)
validate_checksum(data,chk)
o:=electrainkordNew()
o:xml2obj( base64_decode(data) )
store(s,o)
return NIL
A sid és a checksum ellenőrzése után létrehozunk egy üres electrainkord objektumot, amit feltöltünk a (base64 dekódolt) XML leírásából, majd letároljuk az objektumot:
static function store(session,ktg)
local e
session:access(ktg:xmlname,ktg:acc)
if( .t.==ELKOTEG:insert(ktg:osn) )
ELKOTEG_TIPUS := ktg:xmlname
ELKOTEG_KOTEGID := ktg:osn
ELKOTEG_SZAMLASZAM := ktg:acc
ELKOTEG_TETELSZAM := len(ktg:details)
ELKOTEG_DBMDATA := ktg:obj2xml
ELKOTEG_ERKEZETT := dtos(date())+time()
ELKOTEG:unlock
else
e:=errorNew()
e:operation:="store "+ktg:xmlname
e:description:="kötegazonosító nem egyedi"
e:args:={ktg:xmlname, ktg:osn, ktg:acc}
eval(errorblock(),e)
end
return NIL
Ezzel az XMLRPC szerver be is töltötte a fő faladatát.
A tényleges feldolgozás a KELECTRA program feladata. Az alábbi process függvény az ELKOTEG adatbázis aktuális rekordjában tárolt köteg feldolgozását végzi.
static function process()
local allapot:=ELKOTEG_ALLAPOT
local tipus, ktg, retry:=.f.
local result, e, n
if( allapot=="OK" )
return NIL
end
Ha a köteg állapota OK, akkor már fel van dolgozva, és szükségtelen
újra feldolgozni. A feldolgozás alatti időre a köteg állapotát !-ra állítjuk,
ha ilyen végállapotú köteget találunk, akkor annak a feldolgozása megszakadt.
ELKOTEG_ALLAPOT:="!"
ELKOTEG_VEGREHAJT:=dtos(date())+time()
ELKOTEG:commit
begin sequence
tipus:=lower(alltrim(ELKOTEG_TIPUS))
if( tipus=="huford" )
ktg:=electrahufordNew()
elseif( tipus=="hveord" )
ktg:=electrahveordNew()
elseif( tipus=="inkord" )
ktg:=electrainkordNew()
elseif( tipus=="vibord" )
ktg:=electravibordNew()
elseif( tipus=="devord" )
ktg:=electradevordNew()
elseif( tipus=="dveord" )
ktg:=electradveordNew()
elseif( tipus=="bleord" )
ktg:=electrableordNew()
elseif( tipus=="bfeord" )
ktg:=electrabfeordNew()
else
ELKOTEG_ALLAPOT:="XTIP"
break()
end
Létrehozunk egy megfelelő típusú köteg objektumot,
és feltöltjük azt az XML leírásból.
ktg:xml2obj( ELKOTEG_DBMDATA )
for n:=1 to len( ktg:details )
if( !empty(allapot) .and. ELNAPLO:seek({ktg:osn,ktg:details[n]:sqn}) )
if( !ELNAPLO_ISMETELHET )
loop
end
ELNAPLO:rlock
else
trancount++
ELNAPLO:append
ELNAPLO_KOTEGID:=ktg:osn
ELNAPLO_SORSZAM:=ktg:details[n]:sqn
ELNAPLO_ISMETELHET:=.t.
ELNAPLO:commit
end
A köteg minden tételére meghívjuk a process metódust.
A feldolgozás tranzakcióban történik.
begin sequence
tranBegin()
result:=ktg:details[n]:process(ktg)
tranCommit()
recover using result
tranRollback()
end sequence
if( result:classname=="error" )
e:=result
result:=electraresultNew()
result:state:="XERR"
result:canretry:=.t.
result:message:=_any2str(e)
end
A feldolgozás eredményét, vagy az esetleges hibát rögzítjük
a tételszintű naplóban.
ELNAPLO_UGYSZAM := result:itemid
ELNAPLO_ALLAPOT := result:state
ELNAPLO_ISMETELHET := result:canretry
ELNAPLO_VEGREHAJT := dtos(date())+time()
if( !empty(result:message) )
ELNAPLO_DBMUZENET:=result:message
elseif( !empty(ELNAPLO_DBMUZENET) )
ELNAPLO_DBMUZENET:=""
end
ELNAPLO:unlock
retry:=retry.or.result:canretry
next
ELKOTEG_ALLAPOT:=if(retry,"AG","OK") //AG, ha ismételhető
Beállítjuk a köteg státuszát.
end sequence
ELKOTEG:unlock
return NIL
Az infrastruktúra részét képezi az electra.config filé, ami XML formátumban tárolja a tranzakciók kiegészítő adatait (tranzakciókódok, jelölőadatok, stb.), amiket az Electrában nem rögzítenek. Példaként bemutatom az inkasszónál használt konfigurációs objektumot.
function inkordconfigClass()
static clid
if( clid==NIL )
clid:=classRegister("inkordconfig",{xmlconfigClass()})
classMethod(clid,"initialize",{|this|inkordconfigIni(this)})
classAttrib(clid,"jeloloadat")
classAttrib(clid,"tranzkod")
end
return clid
function inkordconfigNew()
local clid:=inkordconfigClass()
return objectNew(clid):initialize()
static function inkordconfigIni(this)
xmlconfigIni(this)
//default értékek
this:tranzkod:="610"
this:jeloloadat:="4815"
return this
A konfigurációs objektumokat az xmlconfig-ból kell származtatni,
ezzel öröklik azt a képességet, hogy automatikusan feltöltődjenek
az electra.config filéből. A objektumban tetszőleges attribútumokat
definiálhatunk, amiket a megfelelő default értékekkel kell
inicializálni.
Álljon itt példaként a devord tranzakció üres process metódusa.
*****************************************************************************
function _electradevorditem_process(this,ktg)
return process(this,ktg,devordconfig())
*****************************************************************************
static function process(this,ktg,config)
//tranzakcióban fut,
//sikertelen könyvelés esetén break-kel kell kilépni,
//break-ben egy electraresult-ot adunk vissza,
//ami az error osztály leszármazottja
local result:=electraresultNew()
result:operation := "_electradevorditem_process"
result:canretry := .f.
result:state := "XIMP"
result:message := "nincs implementálva"
result:description := "nincs implementálva"
break(result)
return result
*****************************************************************************
A devordconfig() hívás adja konfigurációs objektumot (jelenleg az is üres). A metódus mindig ,,nincs implementálva'' hibaüzenetet ad. Teljesen kidolgozott példa található többek között a proc_inkord.prg modulban.
1ComFirm BT.
2 Folyamatos vita tárgya, hogy mi legyen a hosszabb kifutású tranzakciók állapotának jelentésével. A KELECTRA csak a közvetlenül általa végzett művelet eredményét ismerheti, pl. lekönyvelt egy átutalást, és az sorbaállítódott, vagy sikeresen elindított egy inkasszót. A tranzakciók későbbi sorsáról azonban nincs információ, pl. a sorbanálló tételt menesztik, az inkasszót visszautasítják. Ennek a problémának a megoldására az rpcelectra-kelectra hatáskörében nincs mód.
3 A Cardinal eredetileg nagybetűs tagokat definiált, később azonban áttértünk kisbetűk használatára. A XML-ben a kis/nagybetűk különbözőnek számítanak.