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

gdv.xport.util.SatzRegistry Maven / Gradle / Ivy

Go to download

gdv-xport-lib ist die Java-Bibliothek fuer den Umgang mit dem GDV-Format. Sie erleichtert den Export und Export dieses Datenformats.

There is a newer version: 7.2.2
Show newest version
/*
 * Copyright (c) 2021 by Oli B.
 *
 * 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 orimplied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * (c)reated 13.01.2021 by Oli B. ([email protected])
 */

package gdv.xport.util;

import gdv.xport.Datenpaket;
import gdv.xport.config.Config;
import gdv.xport.satz.Datensatz;
import gdv.xport.satz.Nachsatz;
import gdv.xport.satz.Satz;
import gdv.xport.satz.Vorsatz;
import gdv.xport.satz.feld.common.TeildatensatzNummer;
import gdv.xport.satz.feld.common.WagnisartLeben;
import gdv.xport.satz.model.SatzX;
import gdv.xport.satz.xml.SatzXml;
import gdv.xport.satz.xml.XmlService;
import org.apache.commons.lang3.Range;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.validation.ValidationException;
import javax.xml.stream.XMLStreamException;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Diese Klasse wurde von SatzFactory kopiert und so angepasst, dass auch
 * verschiedene Instanzen fuer die jeweilige XML-Beschreibung der GDV-
 * Datensaetze erzeugt werden koennen. Dies ist mit der SatzFactory-Klasse
 * nicht moeglich, da sie ein rein statische Klasse ist.
 * 

* Da die Klasse auch zum Registrieren eigener Satzarten gedacht ist, wurde * sie in SatzRegistry umbenannt. Funktional entspricht sie aber der * {@link SatzFactory}-Klasse (s.o.). *

* * @author oliver ([email protected]) * @since 5.0 */ public class SatzRegistry implements VersionHandler { private static final Logger LOG = LogManager.getLogger(SatzRegistry.class); /** Default-Validator, der nur Satzart 800 - 899 akzeptiert. */ public static final Validator VALIDATOR = new Validator(); /** Dieser Validator akzeptiert alle Satzarten zwischen 0 und 9999. */ public static final Validator NO_VALIDATOR = new Validator(Range.between(0, 9999)); private static final Map INSTANCES = new HashMap<>(); private final Map registeredSaetze = new ConcurrentHashMap<>(); private final XmlService xmlService; { registerDefault(); } private void registerDefault() { // hier enthaelt die XML-Beschreibung f. KH-Deckungssumme weniger Infos //registerEnum(gdv.xport.satz.feld.sparte51.Feld221.class, SatzTyp.of("0221.051")); } private SatzRegistry(XmlService xmlService) { this.xmlService = xmlService; } /** * Hierueber kann man sich die Default-Factory mit der aktuell gueltigen * XML-Beschreibung der GDV-Datensaetze holen. * * @return Factory auf Basis von VUVM2018.xml */ public static SatzRegistry getInstance() { return getInstance(Config.getXmlResource()); } /** * Hierueber kann man sich die Default-Factory mit der gewuenschten * XML-Beschreibung der GDV-Datensaetze holen. Es wird dabei fuer die * gleiche Resource auch die gleiche Instanz zurueckgegeben. * * @param resource z.B. "VUVM2015.xml" * @return Factory auf Basis der uebergebenen Resource */ public static SatzRegistry getInstance(final String resource) { SatzRegistry factory = INSTANCES.get(resource); try { if (factory == null) { factory = new SatzRegistry(XmlService.getInstance(resource)); INSTANCES.put(resource, factory); LOG.info("{} wurde angelegt.", factory); } return factory; } catch (XMLStreamException | IOException ex) { throw new IllegalArgumentException("invalid resource: " + resource, ex); } } /** * Mit dieser Klasse konnen die Registrierungen wieder komplett * rueckgaengig gemacht werden. Diese Methode wurde vor allem zur * Unterstuetzung der Unit-Tests eingefuehrt. */ public void reset() { registeredSaetze.clear(); registerDefault(); LOG.debug("{} wurde zurueckgesetzt.", this); } /** * Mit dieser Methode koennen eigene Klassen fuer (z.B. noch nicht * unterstuetzte Datensaetze) registriert werden. Die Kasse muss * einen Default-Konstruktor bereitstellen. Ansonsten wird hier eine * {@link IllegalArgumentException} geworfen. * * @param clazz the clazz * @param satzart the satzart */ public void register(final Class clazz, final int satzart) { register(clazz, satzart, VALIDATOR); } /** * Mit dieser Methode kann ein Validator mit uebergeben werden. So kann * z.B. {@link SatzRegistry#NO_VALIDATOR} mit uebergeben werden, um die * Default-Validierung abzuschalten. Als Default werden nur Datensaetze * mit der Satzart 800 - 899 zugelassen. * * @param clazz the clazz * @param satzart the satzart * @param validator fuer die Validierung der Satzart * @see #register(Satz, SatzTyp) */ public void register(final Class clazz, final int satzart, Validator validator) { try { Constructor ctor = clazz.getConstructor(); LOG.debug("Default constructor {} found.", ctor); } catch (NoSuchMethodException ex) { throw new IllegalArgumentException("no default constructor found in " + clazz, ex); } SatzTyp satzTyp = SatzTyp.of(satzart); register(newInstance(satzTyp, clazz), satzTyp, validator); } /** * Mit dieser Registrierung reicht es, wenn nur ein Aufzaehlungstyp mit der * Datensatz-Beschreibung uebergeben wird. * * @param enumClass ie Aufzaehlungsklasse, z.B. Feld100.class * @param satzNr die SatzNummer (z.B. new SatzNummer(100)) * @deprecated durch {@link #register(Satz, SatzTyp)} abgeloest */ @Deprecated public void registerEnum(final Class enumClass, final SatzTyp satzNr) { register(new SatzX(satzNr, enumClass), satzNr, NO_VALIDATOR); } /** * Mit dieser Methode kann ein beliebiger Satz registriert werden. Sie * loest die alter registerEnum-Methode ab, in der ein Satz mithilfe einer * Enum-Beschreibung registriert werden konnte. *

* Mit der verbesserten Unterstuetzung der GDV-XML-Beschreibung in v5.0 * kann jetzt auch diese XML-Beschreibung fuer die Registrierung eigener * Datensaetze verwendet und hierueber registriert werden. So kann z.B. * mit *

*
     * SatzRegistry.getInstance().register(SatzXml.of("Satz0221.051.xml"), SatzTyp.of("0221.051"));
     * 
*

* eine eigene Beschreibung fuer Satzart 0221.051 registriert werden. *

* * @param satz Satz-Vorlage (z.B. SatzXml.of("Satz0221.051.xml")) * @param satzNr Satzart (z.B. SatzTyp.of("0221.051")) */ public void register(final Satz satz, final SatzTyp satzNr) { register(satz, satzNr, VALIDATOR); } /** * Mit dieser register-Methode kann ein eigener Validator mit uebergeben * werden. Dies ist hilfreich, wenn man den vom GDV vorgegebenen Bereich * fuer eigene Datensaetze (800-899) verlassen will. Dann kann man z.B. * den NO_VALIDATOR uebergeben, der alle Satzarten zwischen 0 und 9999 * akzeptiert. * * @param satz Satz-Vorlage (z.B. SatzXml.of("Satz0221.051.xml")) * @param satzNr Satzart (z.B. SatzTyp.of("0221.051")) * @param validator Validator fuer den SatzTyp */ public void register(final Satz satz, final SatzTyp satzNr, Validator validator) { validator.validate(satzNr); registeredSaetze.put(satzNr, satz); } /** * Hiermit kann man eine Registrierung rueckgaengig machen (was z.B. fuer's * Testen hilfreich sein kann). Diese unregister-Methode ersetzt ab 4.2 die * anderen uregister-Methoden. * * @param typ SatzTyp bzw. Satzart */ public void unregister(SatzTyp typ) { registeredSaetze.remove(typ); } /** * Mit dieser Methode koennen eigene Klassen fuer (z.B. noch nicht * unterstuetzte Datensaetze) registriert werden. * * @param clazz the clazz * @param satzNr the satz nr */ public void register(final Class clazz, final SatzTyp satzNr) { registeredSaetze.put(satzNr, generateDatensatz(satzNr, clazz)); } /** * Holt einen Satz. * * @param satztyp der Satztyp * @return angeforderter Satz */ public Satz getSatz(final SatzTyp satztyp) { Satz satz = registeredSaetze.get(satztyp); if (satz == null) { return getSatzFromXmlService(satztyp); } try { return (Satz) satz.clone(); } catch (CloneNotSupportedException ex) { throw new IllegalArgumentException(satztyp + " laesst sich nicht clonen", ex); } } private static Satz newInstance(SatzTyp satztyp, Class clazz) { try { Satz satz = clazz.getDeclaredConstructor().newInstance(); if (satz.getSatzart() != satztyp.getSatzart()) { Constructor ctor = clazz.getConstructor(int.class); satz = ctor.newInstance(satztyp.getSatzart()); } return satz; } catch (Exception e) { LOG.info("default constructor does not work (" + e + "), trying another ctor..."); Constructor ctor = null; try { ctor = clazz.getConstructor(int.class); return ctor.newInstance(satztyp.getSatzart()); } catch (InvocationTargetException ite) { throw new ShitHappenedException(ite.getTargetException() + " in " + ctor, ite); } catch (NoSuchMethodException nsme) { throw new UnsupportedOperationException("registered " + clazz + " has not the required ctor", nsme); } catch (InstantiationException ie) { throw new ShitHappenedException("registered " + clazz + " can't be instantiated", ie); } catch (IllegalAccessException iae) { throw new IllegalStateException("registered " + clazz + " can't be accessed", iae); } } } private Satz getSatzFromXmlService(SatzTyp satztyp) { try { SatzXml satz = xmlService.getSatzart(satztyp); if (satztyp.hasSparte()) { satz.setSparte(satztyp.getSparte()); } return satz; } catch (NotRegisteredException ex) { if (satztyp.hasParent()) { LOG.error("{} is not available via XmlService, trying {} now.", satztyp, satztyp.getParent()); LOG.trace("Details:", ex); SatzXml satz = xmlService.getSatzart(satztyp.getParent()); satz.setSparte(satztyp.getSparte()); return satz; } else { throw ex; } } } private Datensatz generateDatensatz(final SatzTyp satzNr) { LOG.trace("Will use fallback for Satz {}:", satzNr); return useFallback(satzNr); } /** * Versucht anhand des uebergebenen Strings herauszufinden, um was fuer eine * Satzart es sich handelt und liefert dann einen entsprechenden (gefuellten) * Satz zurueck. *

* Im ersten Schritt wird versucht, einen moeglichst passenden SatzTyp zu ermitteln. Zu diesem * SatzTyp wird dann versucht, eine registrierte Satzart zu finden. Wenn das fehlschlaegt, wird im * 2. Schritt als Ersatz eine Satzart generiert aus "satzart" und "sparte". *

*

* ACHTUNG: Um den ganz korrekten Satzaufbau zu liefern, muesste dazu die Version der Satzatz * bekannt sein! Diese Info steht immer im Vorsatz des zugehörigen Datenpaketes. Lt. Auskunft vom * GDV werden z.T. noch Saetze aus Release 01.11.2009 verarbeitet. Da hier aber die aktuellste * Version verwendet wird, kann der zurueckgegebene Satz mehr Felder enthalten, als die * tatsaechliche Version. Diese Unschaerfe wird hier in Kauf genommen, da i.d.R. immer nur Felder * hinzugefuegt werden. Dies muss beim Zugriff ueber die Feld-Nr. beachtet werden. *

* * @param content the content * @return einen gefuellten Satz */ // public Satz getSatz(final String content) { // int satzart = Integer.parseInt(content.substring(0, 4)); // Satz satz; // try { // satz = getSatz(SatzTyp.of(satzart)); // } catch (NotRegisteredException e) { // LOG.debug("can't get Satz " + satzart + " (" + e + "), parsing Sparte..."); // int sparte = Integer.parseInt(content.substring(10, 13)); // satz = getDatensatz(SatzTyp.of(satzart, sparte)); // } // try { // satz.importFrom(content); // return satz; // } catch (IOException ioe) { // throw new IllegalArgumentException("can't parse " + content, ioe); // } // } public Satz getSatz(final String content) { /* * @Oli: diese Variante sollte deutlich besser passen als die bisherige. Jedoch habe ich nicht * bis zum Exzess getestet. Bitte pruef das doch noch mal. */ SatzTyp satzTyp; try { satzTyp = errateSatzTyp(content); } catch (IOException ioe) { throw new IllegalArgumentException("can't recognize SatzTyp " + content, ioe); } Satz satz; try { satz = getSatz(satzTyp); } catch (NotRegisteredException e) { LOG.debug("can't get Satz " + satzTyp.toString() + " (" + e + "), try fallback..."); satz = getDatensatz(satzTyp); } try { satz.importFrom(content); return satz; } catch (IOException ioe) { throw new IllegalArgumentException("can't parse " + content, ioe); } } private SatzTyp errateSatzTyp(final String content) throws IOException { /* * @Oli: das entspricht im Wesentlichen der Methode "Datenpaket.importDatensatz(...)". */ SatzTyp satzTyp; int satzart = Integer.parseInt(content.substring(0, 4)); if (satzart == 1 || satzart == 9999) return SatzTyp.of(satzart); int sparte = errateSparte(content); satzTyp = SatzTyp.of(satzart, sparte); if (satzart >= 210 && satzart < 300) { if (sparte == 10 && ((satzart == 220) || (satzart == 221))) { WagnisartLeben wagnisart = errateWagnisart(content); if (wagnisart != WagnisartLeben.NULL) { // wagnisart 0 hat immer ein Leerzeichen als // teildatenSatzmummer. Nur groesser 0 // besitzt per Definition Werte. TeildatensatzNummer teildatensatzNummer = errateGdvSatzartNummer(content); satzTyp = SatzTyp.of(satzart, sparte, wagnisart.getCode(), teildatensatzNummer.getCode()); } else { satzTyp = SatzTyp.of(satzart, sparte, wagnisart.getCode()); } } else if (sparte == 20 && satzart == 220) { // Fuer 0220.020.x ist die Krankenfolgenummer zur Identifikation der Satzart noetig int krankenFolgeNr = errateKrankenFolgeNr(content); // Krankenfolgenummer nicht auslesbar -> Unbekannter Datensatz if (krankenFolgeNr == -1) { satzTyp = SatzTyp.of(satzart, sparte); } else { satzTyp = SatzTyp.of(satzart, sparte, krankenFolgeNr); } } else if (sparte == 580 && satzart == 220) { // Fuer 0220.580.x ist die BausparArt zur Identifikation der Satzart // noetig int bausparArt = errateBausparenArt(content); // BausparenArt nicht auslesbar -> Unbekannter Datensatz if (bausparArt == -1) { satzTyp = SatzTyp.of(satzart, sparte); } else { satzTyp = SatzTyp.of(satzart, sparte, bausparArt); } } else { satzTyp = SatzTyp.of(satzart, sparte); } } return satzTyp; } private static int errateSparte(final String content) throws IOException { try { return Integer.parseInt(content.substring(10, 13)); } catch (StringIndexOutOfBoundsException | NumberFormatException ex) { throw new IOException("cannot read sparte from first 14 bytes (\"" + content + "\")", ex); } } private static WagnisartLeben errateWagnisart(final String content) throws IOException { if (content.length() < 60) throw new IOException("can't read Byte 60 from " + content); String wagnisart = content.substring(59, 60); if (wagnisart.trim().length() == 0) { return WagnisartLeben.NULL; } else { try { return WagnisartLeben.isIn(Integer.parseInt(wagnisart)); } catch (NumberFormatException e) { LOG.warn("Not allowed value for wagnisart found. Type Number is required but was \"" + wagnisart + "\"."); return WagnisartLeben.NULL; } } } private static TeildatensatzNummer errateGdvSatzartNummer(final String content) throws IOException { if (content.length() < 256) throw new IOException("can't read Byte 256 from " + content); String teildatenSatz = content.substring(content.length() - 1, content.length()); if (teildatenSatz.trim().length() == 0) { return TeildatensatzNummer.NULL; } else { try { return TeildatensatzNummer.isIn(Integer.parseInt(teildatenSatz)); } catch (NumberFormatException e) { LOG.warn( "Value \"" + teildatenSatz + "\" for TeildatensatzNummer found, but Number expected."); return TeildatensatzNummer.NULL; } } } private static int errateKrankenFolgeNr(final String content) throws IOException { if (content.length() < 48) throw new IOException("can't read Byte 48 from " + content); try { return Integer.parseInt(content.substring(47, 48)); } catch (NumberFormatException ex) { return -1; } } private static int errateBausparenArt(final String content) throws IOException { if (content.length() < 44) throw new IOException("can't read Byte 44 from " + content); try { return Integer.parseInt(content.substring(43, 44)); } catch (NumberFormatException ex) { return -1; } } /** * Liefert den passenden Vorsatz. * * @return Vorsatz * @since 5.0 */ public Vorsatz getVorsatz() { return new Vorsatz(this); } /** * Liefert den passenden Nachsatz. * * @return Nachsatz * @since 5.0 */ public Nachsatz getNachsatz() { return new Nachsatz(this); } /** * Liefert den gewuenschten Datensatz. Mit der uebergebenen Satznummer wird * der Datensatz spezifizert, der folgendes enthaelt: *
    *
  • Satzart (z.B. 210)
  • *
  • Sparte (z.B. 70 fuer Rechtsschutz)
  • *
  • Wagnisart (z.B. 1 fuer Kapitallebensversicherung)
  • *
  • Teildatensatz-Nummer (6 = Bezugsrechte, 7 = Auszahlungen, 8 = * zukünftige Summenänderungen, 9 = Wertungssummen)
  • *
*

* Falls der gewuenschte Datensatz nicht registriert ist, wird der Datensatz * anhand der von {@link XmlService} bestimmt. *

*

* Im Gegensatz zu {@link #getSatz(SatzTyp)} wird hier auf jeden Fall * ein Datensatz zurueckgeliefert, auch wenn der SatzTyp weder registriert * noch ueber den {@link XmlService} verfuegbar ist. Dies ist vor allem * fuer den Import relevant, damit er nicht bei unbekannten Datensaetzen * abbricht. *

* * @param satzNr z.B. SatzTyp.of("0210.070.1.6") * @return den passenden Datensatz */ public Datensatz getDatensatz(final SatzTyp satzNr) { Satz satz = registeredSaetze.get(satzNr); if (satz instanceof Datensatz) { return (Datensatz) satz; } return generateDatensatz(satzNr); } private Datensatz generateDatensatz(SatzTyp satzNr, Class clazz) { try { Constructor ctor = clazz.getConstructor(int.class, int.class); return ctor.newInstance(satzNr.getSatzart(), satzNr.getSparte()); } catch (NoSuchMethodException exWithTwoParams) { LOG.info("constructor " + clazz + "(int, int) not found (" + exWithTwoParams + ")"); return getDatensatz(satzNr.getSparte(), clazz); } catch (InstantiationException exWithTwoParams) { LOG.info(clazz + "(int, int) can't be instantiated (" + exWithTwoParams + ")"); return getDatensatz(satzNr.getSparte(), clazz); } catch (IllegalAccessException exWithTwoParams) { LOG.info(clazz + "(int, int) can't be accessed (" + exWithTwoParams + ")"); return getDatensatz(satzNr.getSparte(), clazz); } catch (InvocationTargetException exWithTwoParams) { LOG.info("error in calling " + clazz + "(int, int): " + exWithTwoParams); return getDatensatz(satzNr.getSparte(), clazz); } } private static Datensatz getDatensatz(final int sparte, final Class clazz) { try { Constructor ctor = clazz.getConstructor(int.class); return ctor.newInstance(sparte); } catch (NoSuchMethodException nsme) { LOG.info(clazz + " found but no " + clazz.getSimpleName() + "(" + sparte + ") constructor (" + nsme + ")"); return getDatensatz(clazz); } catch (Exception exWithOneParam) { LOG.warn("constructor problem with " + clazz, exWithOneParam); return getDatensatz(clazz); } } private static Datensatz getDatensatz(final Class clazz) { try { return clazz.getDeclaredConstructor().newInstance(); } catch (ReflectiveOperationException e) { throw new IllegalArgumentException("can't instantiate " + clazz, e); } } /** * Als Fallback wird nur der Datensatz fuer die entsprechende Satzart * zurueckgeben. Falls dieser nicht existiert, wird ein (allgemeiner) * Datensatz mit Satzart und Sparte als Parameter erzeugt. * * @param satzNr die SatzNummer * @return der erzeugte Datensatz */ private Datensatz useFallback(final SatzTyp satzNr) { try { Datensatz fallback = (Datensatz) getSatz(satzNr); if (satzNr.hasSparte()) { fallback.setSparte(satzNr.getSparte()); } return fallback; } catch (NotRegisteredException re) { // Dieser Teil wird fuer den Import benoetigt, um auch unsupported Datensaetze zu unterstuetzen LOG.warn("Reduced functionality for (unknown or unsupported) Satzart {}:", satzNr, re); Datensatz satz = new Datensatz(satzNr.getSatzart(), satzNr.getSparte()); satz.addFiller(); return satz; } } /** * Liefert ein Datenpaket mit allen unterstuetzten Satzarten. * * Satzarten, die mit {@link #register(Class, int)} registriert wurden, * werden nicht aufgefuehrt! *

* Grund: Ein Objekt vom Typ <code>Satz</code> kann * nicht auf <code>Datensatz</code> gecastet werden. *

* * @return Datenpaket mit allen unterstuetzten Satzarten */ public Datenpaket getAllSupportedSaetze() { Map supportedSaetze = new HashMap<>(); for (Map.Entry entry : xmlService.getSatzarten().entrySet()) { supportedSaetze.put(entry.getKey(), entry.getValue()); } for (Map.Entry entry : registeredSaetze.entrySet()) { Satz value = entry.getValue(); if (value instanceof Datensatz) { supportedSaetze.put(entry.getKey(), (Datensatz) value); } } supportedSaetze.remove(SatzTyp.of("0001")); supportedSaetze.remove(SatzTyp.of("9999")); Datenpaket all = new Datenpaket(); for (Datensatz satz : supportedSaetze.values()) { all.add(satz); } return all; } /** * Liefert das Release der jeweiligen XML-Beschreibung, aus der die GDV-Datensaetze erzeugt * wurden. * * @return das Release der erzeugten XmlSaetze */ public String getGdvRelease() { return xmlService.getGdvRelease(); } @Override public String toString() { return this.getClass().getSimpleName() + " mit " + this.xmlService; } /** * Liefert die Version der angefragten Satzart * * @param satzTyp Satzart * @return z.B. "2.4" */ @Override public String getVersionOf(SatzTyp satzTyp) { return getDatensatz(satzTyp).getSatzversion().getInhalt(); } static class Validator { private final Range allowed; public Validator() { this(Range.between(800, 899)); } public Validator(Range allowed) { this.allowed = allowed; } public SatzTyp validate(SatzTyp x) { if (allowed.contains(x.getSatzart())) { return x; } else { throw new ValidationException(x.getSatzart() + " liegt ausserhalb von " + allowed); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy