java\06_Schnittstellen_Datenformate\ (Start mit java Aufgabe_SD_NN_k.java)
| Bestandteil | Bezeichnung |
|---|---|
| <?xml version="1.0" encoding="UTF-8"?> | XML-Deklaration (Prolog) mit Version und Zeichenkodierung |
Element lieferung (Zeilen 2–6) | Wurzelelement (Root-Element), umschließt alle anderen Elemente |
| nr="88120" | Attribut des Elements lieferung (Name nr, Wert 88120) |
| <ablage hinweis="Garage"/> | leeres Element (ohne Inhalt, Kurzschreibweise ohne End-Tag) mit einem Attribut |
| Eigenschaft | Erläuterung | Statusmeldung |
|---|---|---|
| wohlgeformt | Das Dokument hält die Syntaxregeln von XML ein: genau ein Wurzelelement, zu jedem Start-Tag ein passendes End-Tag (oder leeres Element), korrekte Verschachtelung, Attributwerte in Anführungszeichen, Groß-/Kleinschreibung beachtet. Das prüft jeder XML-Parser. | ja – alle Regeln sind eingehalten, auch das leere Element ist korrekt geschlossen. |
| gültig | Das Dokument ist wohlgeformt und entspricht zusätzlich einer Strukturbeschreibung (DTD oder XML-Schema): nur erlaubte Elemente und Attribute, richtige Reihenfolge und Anzahl, Pflichtangaben vorhanden. | nein / nicht prüfbar – ohne DTD bzw. XSD gibt es keine Grammatik, gegen die geprüft werden kann. |
Erklärung: Die Falle steckt in b): Viele schreiben „gültig, weil kein Fehler drin ist“. Gültigkeit ist aber immer relativ zu einer Grammatik. Ohne DTD/XSD ist ein Dokument höchstens wohlgeformt.
| Deklaration | Bedeutung |
|---|---|
| <!ELEMENT erzeuger (name, telefon?, email+, hof)> | Ein Erzeuger enthält in genau dieser Reihenfolge: genau einen Namen, optional eine Telefonnummer (0 oder 1), mindestens eine E-Mail-Adresse (1 bis beliebig viele) und genau einen Hof. |
| <!ELEMENT hof (adresse | koordinaten)> | Ein Hof enthält entweder eine Adresse oder Koordinaten (Auswahl, genau eines von beiden). |
| <!ATTLIST erzeuger id ID #REQUIRED status (aktiv|pausiert) "aktiv"> | Das Element erzeuger hat das Pflichtattribut id, dessen Wert im Dokument eindeutig sein muss (Typ ID), und das optionale Attribut status mit den erlaubten Werten aktiv oder pausiert; fehlt es, gilt der Standardwert aktiv. |
| <xs:element name="zertifikat" type="xs:string" minOccurs="0" maxOccurs="unbounded"/> | XML-Schema: Element zertifikat mit Textinhalt (Datentyp string), das fehlen oder beliebig oft vorkommen darf (0..n) – entspricht zertifikat* in einer DTD. |
1 <!ELEMENT lieferschein (kunde, position+, fahrer?)> 2 <!ATTLIST lieferschein nr CDATA #REQUIRED 3 datum CDATA #REQUIRED> 4 <!ELEMENT kunde (name, adresse)> 5 <!ATTLIST kunde kundenNr CDATA #REQUIRED> 6 <!ELEMENT name (#PCDATA)> 7 <!ELEMENT adresse (#PCDATA)> 8 <!ELEMENT position (kiste, anzahl, bemerkung*)> 9 <!ELEMENT kiste (#PCDATA)> 10 <!ELEMENT anzahl (#PCDATA)> 11 <!ELEMENT bemerkung (#PCDATA)> 12 <!ELEMENT fahrer (#PCDATA)>
Test · Aufgabe_SD_01_2.java (JDK-Parser mit Validierung, DTD per EntityResolver eingebunden)
--- Lieferschein aus Anlage 6
gültig (wohlgeformt und DTD-konform)
--- Negativtest 1: keine Position (position+ verlangt mindestens eine)
ungültig, Zeile 9: Content des Elementtyps "lieferschein" muss "(kunde,position+,fahrer?)" entsprechen.
--- Negativtest 2: Pflichtattribut nr fehlt
ungültig, Zeile 3: Attribut "nr" ist erforderlich und muss für Elementtyp "lieferschein" angegeben werden.
--- Negativtest 3: anzahl vor kiste (Reihenfolge der Sequenz)
ungültig, Zeile 11: Content des Elementtyps "position" muss "(kiste,anzahl,bemerkung*)" entsprechen.
--- Negativtest 4: zwei Fahrer (fahrer? erlaubt höchstens einen)
ungültig, Zeile 20: Content des Elementtyps "lieferschein" muss "(kunde,position+,fahrer?)" entsprechen.
--- Positivtest: ohne Fahrer (optional) und ohne Bemerkung
gültig (wohlgeformt und DTD-konform)
lieferschein mit position+ und fahrer? 1,5 P; ATTLIST lieferschein 1 P; kunde mit ATTLIST 1 P; position mit bemerkung* 1,5 P; restliche Elemente als (#PCDATA) 1 P. Die Reihenfolge der Deklarationen ist beliebig; je Attribut eine eigene ATTLIST ist ebenso richtig. nr als Typ ID wird akzeptiert (der Wert beginnt mit einem Buchstaben).Erklärung: Jede Regel des Aufgabentexts wird zu genau einer Angabe: „genau ein“ = ohne Zeichen, „mindestens ein“ = +, „optional“ = ?, „beliebig viele, auch keine“ = *. Typischer Fehler: bemerkung+ (verlangt mindestens eine Bemerkung) oder Attribute als Kindelemente deklarieren.
Die DTD legt verbindlich fest, wie ein Wochenangebot aufgebaut sein muss: welche Elemente und Attribute erlaubt sind, in welcher Reihenfolge und Anzahl sie vorkommen und welche Angaben Pflicht sind. Sie ist damit die gemeinsame Schnittstellenvereinbarung zwischen den 28 Erzeugerbetrieben und dem KistenPortal. Mit ihr kann jede hochgeladene Datei vor dem Import automatisch validiert werden; fehlerhafte Dateien werden mit einer Meldung abgewiesen, statt falsche Daten zu importieren.
| Zeile | Beschreibung | Eigenschaft |
|---|---|---|
| 3 | Pflichtattribut jahr fehlt (#REQUIRED). | Gültigkeit |
| 15 | End-Tag </Bezeichnung> passt nicht zum Start-Tag <bezeichnung> (Groß-/Kleinschreibung). | Wohlgeformtheit |
| 22–23 | preis steht vor menge; die DTD verlangt die Reihenfolge (bezeichnung, menge, preis, hinweis*). | Gültigkeit |
| 25 | Der Wert A-101 des ID-Attributs artNr ist bereits in Zeile 9 vergeben; ID-Werte müssen eindeutig sein. | Gültigkeit |
| 25 | einheit="Kilo" ist kein erlaubter Wert; zulässig sind nur kg, stueck, bund. | Gültigkeit |
| 27 | Das End-Tag von menge fehlt der Schrägstrich (<menge> statt </menge>); das Element wird nicht geschlossen. | Wohlgeformtheit |
| 29 | (vorgegeben) Element lieferant ist in der DTD nicht deklariert. | Gültigkeit |
| 31 | Attributwert A-105 steht nicht in Anführungszeichen. | Wohlgeformtheit |
Kontrolle mit dem JDK-Parser · Aufgabe_SD_01_3.java (Auszug der Ausgabe)
--- 1a) Originaldatei Anlage 5
GÜLTIGKEIT Zeile 3: Attribut "jahr" ist erforderlich und muss für Elementtyp "wochenangebot" angegeben werden.
WOHLGEFORMTHEIT Zeile 15: Elementtyp "bezeichnung" muss mit dem entsprechenden Endtag "</bezeichnung>" beendet werden. -> Parser bricht ab
--- 1b) nach Korrektur Zeile 15
... (Gültigkeitsfehler Zeilen 3, 24, 25, 25, 29)
WOHLGEFORMTHEIT Zeile 30: Elementtyp "menge" muss mit dem entsprechenden Endtag "</menge>" beendet werden. -> Parser bricht ab
--- 1c) nach Korrektur Zeile 27
...
WOHLGEFORMTHEIT Zeile 31: Öffnendes Anführungszeichen wird für Attribut "artNr" erwartet, das mit Elementtyp "artikel" verknüpft ist. -> Parser bricht ab
--- 2) nach Korrektur Zeile 31: wohlgeformt, Prüfung gegen die DTD
GÜLTIGKEIT Zeile 3: Attribut "jahr" ist erforderlich und muss für Elementtyp "wochenangebot" angegeben werden.
GÜLTIGKEIT Zeile 24: Content des Elementtyps "artikel" muss "(bezeichnung,menge,preis,hinweis*)" entsprechen.
GÜLTIGKEIT Zeile 25: Attributwert "A-101" mit dem Typ ID muss eindeutig im Dokument sein.
GÜLTIGKEIT Zeile 25: Attribut "einheit" mit Wert "Kilo" muss einen Wert aus der Liste "kg stueck bund " haben.
GÜLTIGKEIT Zeile 29: Elementtyp "lieferant" muss deklariert werden.
GÜLTIGKEIT Zeile 30: Content des Elementtyps "artikel" muss "(bezeichnung,menge,preis,hinweis*)" entsprechen.
--- 3) korrigierte Fassung
gültig (wohlgeformt und DTD-konform)
Der Parser meldet Verstöße gegen das Inhaltsmodell erst beim End-Tag (Zeile 24 bzw. 30) und den fehlenden Schrägstrich aus Zeile 27 erst in Zeile 30, wo das falsche End-Tag </artikel> auftaucht. In der Prüfung gibst du die Zeile an, in der der Fehler entsteht. Die vollständig korrigierte Datei steht in Aufgabe_SD_01_3.java (Konstante KORRIGIERT); in Zeile 25 wurde dort die neue Artikelnummer A-104 und die Einheit kg gewählt.
Warum die Prüfung bestanden wird: In der DTD sind menge und preis als (#PCDATA) deklariert, also als beliebiger Text. Eine DTD kennt keine Datentypen und keine Wertebereiche; sie prüft nur, ob die Elemente vorhanden sind und in der richtigen Reihenfolge stehen. „ca. 80“ und „-1.90“ sind Text und damit zulässig.
Maßnahmen (zwei davon):
menge als positive ganze Zahl und preis als Dezimalzahl größer 0 (Auszug unten, getestet: die KW-47-Datei wird abgewiesen).<xs:element name="artikel">
<xs:complexType>
<xs:sequence>
<xs:element name="bezeichnung" type="xs:string"/>
<xs:element name="menge" type="xs:positiveInteger"/>
<xs:element name="preis">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:minExclusive value="0"/>
<xs:fractionDigits value="2"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="hinweis" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="artNr" type="xs:ID" use="required"/>
<xs:attribute name="einheit" default="kg">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="kg"/>
<xs:enumeration value="stueck"/>
<xs:enumeration value="bund"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
Ausgabe des Tests mit diesem Schema
wochenangebot_korrigiert.xml: XSD-gueltig wochenangebot_kw47.xml: Zeile 10 cvc-datatype-valid.1.2.1: 'ca. 80' ist kein gültiger Wert für 'integer'.
Ausgabe von Aufgabe_SD_01_3.java (eigene Plausibilitätsprüfung nach erfolgreicher DTD-Validierung)
--- 4) Datei KW 47 gegen die DTD
gültig (wohlgeformt und DTD-konform)
... aber fachliche Prüfung:
PLAUSIBILITÄT A-110: Menge ist keine ganze Zahl ("ca. 80")
PLAUSIBILITÄT A-110: Preis muss positiv sein (-1.90)
minOccurs/maxOccurs (z. B. 2 bis 5).Erklärung: b) ist die Aufgabe aus W22 in neuem Gewand: erst alle Tags auf Wohlgeformtheit prüfen (Zeilen 15, 27, 31), dann Element für Element gegen die DTD (Zeilen 3, 22/23, 25, 29). c) und d) zeigen die Grenze der DTD, die in W25 bei der eRechnung abgefragt wurde.
Ein Objekt steht in geschweiften Klammern { } und ist eine ungeordnete Menge von Schlüssel-Wert-Paaren; auf einen Wert greift man über seinen Namen (Schlüssel, immer ein String) zu, z. B. eine Kiste mit "kisteId" und "preis". Ein Array steht in eckigen Klammern [ ] und ist eine geordnete Liste von Werten ohne Schlüssel; auf einen Wert greift man über seine Position (Index) zu, z. B. eine Liste mehrerer Kisten.
| Zeile | Fehler | Korrektur |
|---|---|---|
| 2 | Schlüssel in einfachen Anführungszeichen | "kisteId": "K-MIX-S", |
| 3 | Dezimalkomma – das Komma trennt in JSON die Paare, 20 wäre ein Schlüssel ohne Anführungszeichen | "preis": 14.20, |
| 4 | False großgeschrieben – nur true/false sind erlaubt | "bio": false, |
| 5 | Komma nach dem letzten Element des Arrays | ["Äpfel", "Möhren", "Kartoffeln"] |
Korrigiertes Dokument (mit Python json geprüft)
{
"kisteId": "K-MIX-S",
"preis": 14.20,
"bio": false,
"inhalt": ["Äpfel", "Möhren", "Kartoffeln"]
}
{
"kisten": [
{
"kisteId": "K-OBST-M",
"bezeichnung": "Obstkiste mittel",
"groesse": "M",
"preis": 18.50,
"bio": true,
"gewichtKg": 4.5,
"saisonEnde": null
},
{
"kisteId": "K-GEM-L",
"bezeichnung": "Gemüsekiste groß",
"groesse": "L",
"preis": 27.90,
"bio": true,
"gewichtKg": 8.0,
"saisonEnde": "2027-03-31"
},
{
"kisteId": "K-MIX-S",
"bezeichnung": "Mischkiste klein",
"groesse": "S",
"preis": 14.20,
"bio": false,
"gewichtKg": 3.0,
"saisonEnde": null
}
]
}
inhalt (3 P)Das Objekt der Kiste K-OBST-M aus a) mit eingebettetem Inhalt:
{
"kisteId": "K-OBST-M",
"bezeichnung": "Obstkiste mittel",
"groesse": "M",
"preis": 18.50,
"bio": true,
"gewichtKg": 4.5,
"saisonEnde": null,
"inhalt": [
{ "artikel": "Äpfel Elstar", "menge": 1.5, "einheit": "kg" },
{ "artikel": "Birnen Conference", "menge": 1, "einheit": "kg" },
{ "artikel": "Bananen", "menge": 6, "einheit": "Stück" }
]
}
kisten, drei Objekte, Klammern und Kommas) 2 P; Zahlen ohne Anführungszeichen 1 P; bio als true/false 1 P; leere Zellen als null (Weglassen des Schlüssels wird akzeptiert) und Datum als String 1 P. 18.5 statt 18.50 oder 8 statt 8.0 sind gleichwertig. b) Array mit drei Objekten 1 P, richtige Schlüssel und Werte 1 P, Datentypen (Menge als Zahl) 1 P. c) je Grund 1 P.Erklärung: Die meisten Punktabzüge entstehen durch Datentypen: "ja" statt true, "18.50" als String, ein leerer String "" statt null. Alle Lösungen wurden mit Python json.load auf Syntax geprüft.
{
"kunden": [
{
"kundenNr": 10427,
"name": "Jana Petersen",
"kundentyp": "privat",
"email": "j.petersen@mail.example",
"newsletter": true
},
{
"kundenNr": 20015,
"name": "Kita Sonnenblume",
"kundentyp": "oeffentlich",
"email": "verwaltung@kita-sonnenblume.example",
"newsletter": false
}
],
"lieferadressen": [
{
"adrId": 1,
"kundenNr": 10427,
"strasse": "Lindenweg 4",
"plz": "21391",
"ort": "Heidmoor",
"ablageort": "Garage"
},
{
"adrId": 2,
"kundenNr": 10427,
"strasse": "Hafenstraße 18",
"plz": "21423",
"ort": "Kleinmoor",
"ablageort": null
},
{
"adrId": 3,
"kundenNr": 20015,
"strasse": "Am Mühlenbach 3",
"plz": "21391",
"ort": "Heidmoor",
"ablageort": "Küche, Hintereingang"
}
]
}
{
"kundenNr": 10427,
"name": "Jana Petersen",
"kundentyp": "privat",
"email": "j.petersen@mail.example",
"newsletter": true,
"lieferadressen": [
{
"adrId": 1,
"strasse": "Lindenweg 4",
"plz": "21391",
"ort": "Heidmoor",
"ablageort": "Garage"
},
{
"adrId": 2,
"strasse": "Hafenstraße 18",
"plz": "21423",
"ort": "Kleinmoor",
"ablageort": null
}
]
}
| Datenbestand | Entscheidung | Begründung |
|---|---|---|
| (1) Bestellungen eines Kunden | Referenz | Die Bestellungen wachsen unbegrenzt (mehrere Hundert je Kunde). Eingebettet würde das Kundendokument immer größer und langsamer zu laden, obwohl die App beim Öffnen des Kontos die Bestellungen gar nicht braucht. Bestellungen werden außerdem unabhängig abgefragt (z. B. nach Zeitraum). Daher eigene Sammlung bestellungen mit kundenNr. |
| (2) Positionen einer Bestellung | Embedding | Es sind nur wenige (1–5), sie gehören ausschließlich zu dieser Bestellung, werden immer zusammen mit ihr angezeigt und ändern sich danach nicht mehr. Eingebettet liefert ein einziger Zugriff die vollständige Bestellung. |
funktion offeneStoppsAlsJson(stopps : List<Stopp>) : string
json : string := "["
erster : boolean := true // wurde schon ein Objekt ausgegeben?
zähle i von 0 bis stopps.size() - 1
s : Stopp := stopps.get(i)
wenn nicht s.isZugestellt() dann
wenn nicht erster dann
json := json + ", " // Komma nur ZWISCHEN den Objekten
Ende wenn
json := json + "{\"position\": " + s.getPosition()
+ ", \"kundenNr\": " + s.getKundenNr()
+ ", \"ort\": \"" + s.getOrt() + "\""
+ ", \"kisten\": " + s.getAnzahlKisten() + "}"
erster := false
Ende wenn
Ende zähle
json := json + "]"
rückgabe json
Ende funktion
Java · Aufgabe_SD_02_3.java (Auszug)
static String offeneStoppsAlsJson(List<Stopp> stopps) {
String json = "[";
boolean erster = true;
for (int i = 0; i < stopps.size(); i++) {
Stopp s = stopps.get(i);
if (!s.isZugestellt()) {
if (!erster) {
json = json + ", "; // Trennzeichen nur ZWISCHEN Objekten
}
json = json + "{\"position\": " + s.getPosition()
+ ", \"kundenNr\": " + s.getKundenNr()
+ ", \"ort\": \"" + s.getOrt() + "\"" // Zeichenkette in Anführungszeichen
+ ", \"kisten\": " + s.getAnzahlKisten() + "}";
erster = false;
}
}
json = json + "]";
return json;
}
Ausgabe (die erste Zeile wurde zusätzlich mit Python json.loads als gültiges JSON bestätigt)
Tour T3 (Anlage 9):
[{"position": 2, "kundenNr": 20015, "ort": "Heidmoor", "kisten": 6}, {"position": 3, "kundenNr": 10533, "ort": "Wiesede", "kisten": 1}, {"position": 5, "kundenNr": 20022, "ort": "Wiesede", "kisten": 4}]
Leere Liste: []
Alles zugestellt: []
Nur letzter Stopp offen: [{"position": 2, "kundenNr": 20022, "ort": "Wiesede", "kisten": 4}]
kunden mit passenden Datentypen (newsletter als Boolean) 2 P; Array lieferadressen mit kundenNr als Verweis und null für den leeren Ablageort 2 P; Syntax 1 P. plz als Zahl ist syntaktisch korrekt und wird akzeptiert; fachlich besser ist ein String (Postleitzahlen können mit 0 beginnen). b) Kundendaten 1 P, Array mit zwei Adressobjekten 1 P, Syntax 1 P (die kundenNr in den eingebetteten Adressen ist überflüssig, aber kein Fehler). c) je Datenbestand 2 P (Entscheidung 0,5 P, Begründung 1,5 P); eine andere Entscheidung mit schlüssiger Begründung erhält Teilpunkte. d) Initialisierung ("[", Merker) 1 P; Schleife über alle Stopps 1 P; Bedingung „nicht zugestellt“ 1 P; Komma nur zwischen den Objekten 2 P; Objekt korrekt aufgebaut (Schlüssel und String-Werte in Anführungszeichen, Zahlen ohne) 2 P; "]" und Rückgabe 1 P. Gleichwertig: Teilstrings in einer Liste sammeln und mit ", " verbinden, oder am Ende ein überzähliges Komma entfernen (sofern der Fall „keine offenen Stopps“ richtig bleibt).Erklärung: Der Klassiker ist die Kommaregel. Mit wenn i > 0 entstünde hier [, {"position": 2 …, weil Stopp 1 (Index 0) zugestellt ist und gar nicht ausgegeben wird – ungültiges JSON. Deshalb merkt sich die Lösung, ob schon ein Objekt geschrieben wurde. Zweite häufige Fehlerquelle: der Ort ohne maskierte Anführungszeichen.
| Operation | Beispiel im KistenPortal | HTTP-Methode |
|---|---|---|
| C – Create | neues Abo anlegen: /kunden/10427/abos | POST (PUT, wenn der Client die ID festlegt) |
| R – Read | alle Kistenarten anzeigen: /kisten | GET |
| U – Update | Liefertag eines Abos ändern: /kunden/10427/abos/5531 | PUT (vollständig) oder PATCH (teilweise) |
| D – Delete | Lieferadresse löschen: /kunden/10427/lieferadressen/2 | DELETE |
Der Server speichert zwischen zwei Requests keinen Sitzungszustand der App. Jeder Request muss deshalb alle Informationen enthalten, die zu seiner Bearbeitung nötig sind: den API-Schlüssel bzw. das Token in jedem Request, die betroffene Ressource in der URI (z. B. Kundennummer) und alle Nutzdaten im Body. Der Server kann jeden Request unabhängig von vorherigen bearbeiten; dadurch kann z. B. jeder von mehreren Servern jeden Request beantworten (Lastverteilung).
| Nr. | Methode | Endpunkt | Bemerkung |
|---|---|---|---|
| 1 | GET | /kisten?bio=true&groesse=L | Filter als Query-Parameter |
| 2 | PATCH | /kunden/10427/abos/5531 | Body: {"status": "pausiert", "pausiertBis": "2026-11-24"} |
| 3 | DELETE | /kunden/10427/lieferadressen/2 | |
| 4 | POST | /bestellungen/88120/reklamationen | Body: Daten der Reklamation |
| 5 | PUT | /kunden/10427/lieferadressen/1 | Body: vollständige neue Adresse |
| 6 | GET | /kunden/10427/bestellungen?von=2026-11-01&bis=2026-11-30 | auch: /bestellungen?kundenNr=10427&von=…&bis=… |
Pfadparameter sind Teil der Ressourcenadresse und identifizieren eine bestimmte Ressource, z. B. 10427 und 5531 in /kunden/10427/abos/5531 – ohne sie wäre nicht klar, welches Abo gemeint ist. Query-Parameter stehen nach dem ? als name=wert, sind meist optional und filtern, sortieren oder begrenzen eine Ergebnisliste, z. B. bio=true&groesse=L bei /kisten.
/v1 kennzeichnet die Version der Schnittstelle. Wird die API später inkompatibel geändert (Felder umbenannt, Struktur geändert), wird sie als /v2 veröffentlicht, während /v1 weiterläuft. So funktionieren bereits installierte, nicht aktualisierte Apps und Partner-Anbindungen weiter und können schrittweise umgestellt werden.
/v1 ebenfalls richtig. Verben im Pfad (/abos/5531/pausieren) oder IDs als Query-Parameter (/abos?id=5531) erhalten für den Endpunkt keine Punkte. Zeile 2 mit PUT nur, wenn die vollständige Repräsentation gesendet wird. b), c) je 2 P (Erläuterung 1 P, Beispiel bzw. Folge für Clients 1 P).Eine REST-API stellt die Daten des KistenPortals als Ressourcen bereit (Kisten, Kunden, Abos …), die jeweils über eine eindeutige URI adressiert werden. Clients greifen über die Standardmethoden von HTTP darauf zu (GET lesen, POST anlegen, PUT/PATCH ändern, DELETE löschen) und tauschen Repräsentationen der Ressourcen aus, meist im JSON-Format. Die Kommunikation ist zustandslos: Jede Anfrage enthält alle nötigen Informationen, der Server speichert keine Sitzung. Client und Server sind dadurch lose gekoppelt, sodass App, Webshop und Partner dieselbe Schnittstelle nutzen können.
| Request | sicher | idempotent | Begründung |
|---|---|---|---|
| GET /v1/kisten?bio=true | ja | ja | liest nur, ändert nichts auf dem Server |
| POST /v1/kunden/10427/abos | nein | nein | jeder Aufruf legt ein weiteres Abo an |
| PUT …/abos/5531 | nein | ja | ersetzt das Abo vollständig; nach jeder Wiederholung ist der Zustand derselbe |
| PATCH …/abos/5531 (status, pausiertBis) | nein | ja (hier) | setzt feste Werte; erneutes Senden ändert nichts mehr. PATCH ist im Allgemeinen nicht garantiert idempotent (z. B. relative Änderungen). |
| DELETE …/lieferadressen/2 | nein | ja | nach dem ersten Aufruf ist die Adresse gelöscht; weitere Aufrufe ändern den Zustand nicht (Antwort ggf. 404) |
Ursache: „Abo abschließen“ sendet einen POST-Request, und POST ist nicht idempotent. Der erste Request ist beim Server angekommen und hat das Abo angelegt, nur die Antwort (201) ging bei der schlechten Verbindung verloren. Das erneute Tippen sendet einen zweiten POST, der ein zweites, identisches Abo anlegt.
Lösung: Die App erzeugt für jeden Bestellvorgang einen eindeutigen Idempotenzschlüssel (z. B. eine UUID) und sendet ihn in einem Header (z. B. Idempotency-Key) mit. Der Server merkt sich bereits verarbeitete Schlüssel und liefert bei einer Wiederholung die ursprüngliche Antwort zurück, statt ein neues Abo anzulegen. Weitere mögliche Lösungen: Die App vergibt die Abo-ID selbst und legt das Abo mit PUT /kunden/10427/abos/{uuid} an (idempotent); der Server prüft auf Dubletten (gleiche Kundin, gleiche Kiste innerhalb kurzer Zeit) und antwortet mit 409.
PUT /v1/kunden/10427/abos/5531 HTTP/1.1
{"aboId": 5531, "kundenNr": 10427, "kisteId": "K-OBST-M", "rhythmus": "woechentlich",
"liefertag": "DO", "lieferadresseId": 1, "status": "aktiv", "pausiertBis": null}
PATCH /v1/kunden/10427/abos/5531 HTTP/1.1
{"liefertag": "DO"}
Erklärung: b) prüft Verständnis statt Auswendiglernen: Ob ein PATCH idempotent ist, hängt von der konkreten Änderung ab. Das Setzen fester Werte ist idempotent, „erhöhe um 1“ wäre es nicht. Der Body des PUT in d) wurde gegen die Repräsentation aus Anlage 2 geprüft (alle acht Felder, nur der Liefertag ist geändert).
| Code | Bedeutung |
|---|---|
| 2xx | Erfolg: Der Request wurde empfangen, verstanden und erfolgreich verarbeitet (z. B. 200 OK, 201 Created). |
| 4xx | Client-Fehler: Die Anfrage des Clients ist fehlerhaft oder nicht erlaubt, z. B. falsche URL, fehlende Berechtigung oder ungültige Daten (z. B. 400, 401, 404). Sie muss vom Client geändert werden. |
| 5xx | Server-Fehler: Die Anfrage war in Ordnung, der Server konnte sie aber wegen eines eigenen Problems nicht ausführen (z. B. 500, 503). |
/abo statt /abos, oder eine API-Version, die es nicht mehr gibt).503 Service Unavailable (idealerweise mit dem Header Retry-After).
| Bestandteil | Bedeutung |
|---|---|
| /v1/kunden/10427/abos | Pfad des Endpunkts (Ressource): Version 1 der API, Sammlung der Abos der Kundin 10427, in der das neue Abo angelegt wird |
| Content-Type / Accept | Header: Content-Type gibt an, dass der Body JSON enthält; Accept, dass der Client die Antwort ebenfalls als JSON erwartet |
| X-API-Key: app-7Fq2-Lx91 | Header mit dem API-Schlüssel der Kunden-App; der Server prüft damit, ob die anfragende Anwendung zugriffsberechtigt ist |
| {"kisteId": "K-GEM-L", …} | Body mit den Nutzdaten im JSON-Format: die Daten des neuen Abos (Gemüsekiste groß, 14-täglich, freitags, an Lieferadresse 2) |
| Parameter | Beschreibung |
|---|---|
| start=… / ziel=… | Koordinaten (Breiten- und Längengrad) des Start- und des Zielpunkts der Route, z. B. Lager und Kunde |
| profil=transporter | Fahrzeugprofil: Die Route wird für einen Transporter berechnet (andere erlaubte Straßen, Höhen- und Gewichtsgrenzen, Geschwindigkeiten als z. B. für Pkw oder Fahrrad) |
| apikey=GD-55K2-ZZ81 | API-Schlüssel, mit dem sich das KistenPortal beim Kartendienst ausweist (Zugriffsberechtigung, Abrechnung, Begrenzung der Anfragen) |
URLs werden in Server- und Proxy-Protokollen, im Browserverlauf und in Monitoring-Werkzeugen gespeichert; dort kann der Schlüssel ausgelesen und missbraucht werden (z. B. Anfragen auf Kosten der FrischeKiste). Besser: Übertragung im Header.
| Nr. | Code | Begründung |
|---|---|---|
| 1 | 400 Bad Request | Die Anfrage ist inhaltlich fehlerhaft (Pflichtfeld fehlt); der Client muss sie korrigieren. (422 Unprocessable Content wird ebenfalls akzeptiert.) |
| 2 | 403 Forbidden | Der Erzeugerbetrieb ist mit gültigem Schlüssel authentifiziert, als Partner aber nur zum Lesen berechtigt. 401 wäre falsch, weil der Schlüssel gültig ist. |
| 3 | 204 No Content | Erfolgreich ausgeführt, die Antwort enthält bewusst keinen Body. |
| 4 | 301 Moved Permanently | Die Ressource ist dauerhaft unter einer neuen Adresse erreichbar; der Header Location: /v1/kisten nennt sie, Clients sollen künftig die neue URL nutzen. (308 Permanent Redirect ebenfalls richtig.) |
private funktion createErrorResponse(statusCode : integer, meldung : string) : HttpResponse
body : string := "{\"status\": " + statusCode + ", \"fehler\": \"" + meldung + "\"}"
response : HttpResponse := new HttpResponse()
response.setStatus(statusCode)
response.setHeader("Content-Type", "application/json; charset=utf-8")
response.setHeader("Content-Length", body.byteLength().toString()) // Bytes, nicht Zeichen
response.setBody(body)
rückgabe response
Ende funktion
length() zählt Zeichen, Content-Length muss aber die Länge des Bodys in Bytes angeben. In UTF-8 belegen Umlaute und ß 2 Bytes, das Eurozeichen 3 Bytes. Enthält die Meldung solche Zeichen, ist der Wert zu klein: Der Client liest dann zu wenig Bytes und schneidet den Body ab (ungültiges JSON) bzw. der Rest wird als Beginn der nächsten Nachricht fehlinterpretiert.
Body {"status": 503, "fehler": "Katalog vorübergehend nicht verfügbar"}: 66 Zeichen, davon zweimal ü → Content-Length: 68.
funktion handleGetKiste(request : HttpRequest) : HttpResponse
// 1. Authentifizierung
key : string := request.getHeader("X-API-Key")
wenn key = null oder nicht schluessel.istGueltig(key) dann
rückgabe createErrorResponse(401, "API-Schlüssel fehlt oder ist ungültig")
Ende wenn
// 2. Verfügbarkeit des Katalogs
wenn nicht katalog.istVerfuegbar() dann
rückgabe createErrorResponse(503, "Katalog vorübergehend nicht verfügbar")
Ende wenn
// 3. Kiste suchen
kisteId : string := request.getPfadParameter("kisteId")
kiste : Kiste := katalog.findeKiste(kisteId)
wenn kiste = null dann
rückgabe createErrorResponse(404, "Kiste " + kisteId + " nicht gefunden")
Ende wenn
// 4. Erfolg
body : string := kiste.toJson()
response : HttpResponse := new HttpResponse()
response.setStatus(200)
response.setHeader("Content-Type", "application/json; charset=utf-8")
response.setHeader("Content-Length", body.byteLength().toString())
response.setBody(body)
rückgabe response
Ende funktion
Java · Aufgabe_SD_04_3.java (Auszug)
private HttpResponse createErrorResponse(int statusCode, String meldung) {
String body = "{\"status\": " + statusCode + ", \"fehler\": \"" + meldung + "\"}";
HttpResponse response = new HttpResponse();
response.setStatus(statusCode);
response.setHeader("Content-Type", "application/json; charset=utf-8");
response.setHeader("Content-Length", String.valueOf(byteLength(body))); // Bytes, nicht Zeichen!
response.setBody(body);
return response;
}
HttpResponse handleGetKiste(HttpRequest request) {
String key = request.getHeader("X-API-Key");
if (key == null || !schluessel.istGueltig(key)) {
return createErrorResponse(401, "API-Schlüssel fehlt oder ist ungültig");
}
if (!katalog.istVerfuegbar()) {
return createErrorResponse(503, "Katalog vorübergehend nicht verfügbar");
}
String kisteId = request.getPfadParameter("kisteId");
Kiste kiste = katalog.findeKiste(kisteId);
if (kiste == null) {
return createErrorResponse(404, "Kiste " + kisteId + " nicht gefunden");
}
String body = kiste.toJson();
HttpResponse response = new HttpResponse();
response.setStatus(200);
response.setHeader("Content-Type", "application/json; charset=utf-8");
response.setHeader("Content-Length", String.valueOf(byteLength(body)));
response.setBody(body);
return response;
}
// byteLength() aus Anlage 4:
static int byteLength(String text) {
return text.getBytes(StandardCharsets.UTF_8).length;
}
Ausgabe (Auszug)
=== 1) ohne X-API-Key
HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8
Content-Length: 68
{"status": 401, "fehler": "API-Schlüssel fehlt oder ist ungültig"}
[Kontrolle] Zeichen im Body: 66, Bytes (UTF-8): 68, Content-Length korrekt: true
=== 3) unbekannte Kiste
HTTP/1.1 404 Not Found
Content-Type: application/json; charset=utf-8
Content-Length: 57
{"status": 404, "fehler": "Kiste K-XXL-Z nicht gefunden"}
[Kontrolle] Zeichen im Body: 57, Bytes (UTF-8): 57, Content-Length korrekt: true
=== 4) Kiste gefunden
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 104
{"kisteId": "K-GEM-L", "bezeichnung": "Gemüsekiste groß", "groesse": "L", "preis": 27.90, "bio": true}
[Kontrolle] Zeichen im Body: 102, Bytes (UTF-8): 104, Content-Length korrekt: true
=== 5) Katalog nicht verfügbar (Teil b)
HTTP/1.1 503 Service Unavailable
Content-Type: application/json; charset=utf-8
Content-Length: 68
{"status": 503, "fehler": "Katalog vorübergehend nicht verfügbar"}
[Kontrolle] Zeichen im Body: 66, Bytes (UTF-8): 68, Content-Length korrekt: true
byteLength() 2 P (mit length() 0 P); Body setzen und Rückgabe 1 P. b) Erläuterung 1 P, 66 Zeichen 1 P, 68 Bytes 1 P. c) Header lesen und auf fehlend/ungültig prüfen mit 401 2 P; 503-Prüfung 1 P; Pfadparameter lesen, findeKiste, 404 mit Kisten-ID in der Meldung 2 P; Erfolgsantwort mit Status 200, Body toJson(), beiden Headern 3 P; Reihenfolge eingehalten und nach jedem Fehler sofortige Rückgabe 1 P. Eine Variante mit sonst wenn-Kette statt frühem rückgabe ist gleichwertig.Erklärung: Die Aufgabe entspricht W24 T2 A2a, erweitert um die Prüfreihenfolge. Typische Fehler: kein rückgabe nach einem Fehlerfall (die Methode läuft dann weiter und überschreibt die Antwort), istGueltig(null) vor der Null-Prüfung, Content-Length in Zeichen, und im 404-Text der wörtliche Text „kisteId“ statt des Werts der Variablen.
Das Hauptziel einer eRechnung ist die automatische, elektronische Verarbeitung beim Empfänger: Die Rechnungsdaten liegen strukturiert (als XML) vor und können ohne Abtippen direkt in Buchhaltung bzw. ERP-System übernommen, geprüft und gebucht werden. Das spart Zeit und Kosten und vermeidet Erfassungsfehler. Eine für Menschen lesbare Darstellung ist dabei zweitrangig.
| Nr. | eRechnung? | Begründung |
|---|---|---|
| 1 | nein | Eine PDF enthält keine strukturierten Daten, sondern nur eine Darstellung für Menschen („sonstige Rechnung“). Die Daten müssten abgetippt oder per Texterkennung gelesen werden. |
| 2 | ja | XRechnung ist ein strukturiertes XML-Format nach EN 16931 und maschinell verarbeitbar; der Übertragungsweg (Portal) spielt keine Rolle. |
| 3 | nein | Ein Bild enthält keine strukturierten Daten; es ist eine digitalisierte Papierrechnung. |
| 4 | ja | ZUGFeRD ist ein hybrides Format: Die in das PDF/A-3 eingebettete XML-Datei entspricht im Profil EN 16931 der Norm und kann automatisch verarbeitet werden. |
XRechnung ist ein rein XML-basiertes Format: Die Rechnung besteht nur aus strukturierten Daten für die Software (Datenaspekt). Ein Mensch kann sie nur mit einem Anzeigeprogramm (Viewer) lesen. Viele Behörden verlangen XRechnung und nutzen die Leitweg-ID, um die Rechnung an die zuständige Stelle weiterzuleiten.
ZUGFeRD ist ein hybrides Format: ein PDF/A-3-Dokument, das ein Mensch direkt lesen kann, mit einer eingebetteten XML-Datei, die die Software verarbeitet. Es vereint also visuellen und Datenaspekt. Weichen beide Teile voneinander ab, gelten die XML-Daten.
Geschäftskunden (B2B): Nicht mehr zulässig. Die FrischeKiste erwartet für 2026 einen Umsatz von rund 4,6 Mio. € und liegt damit deutlich über 800.000 €. Die Übergangsregel für 2027 gilt daher nicht; ab dem 1. Januar 2027 müssen Rechnungen an Geschäftskunden als eRechnung ausgestellt werden (ausgenommen Kleinbetragsrechnungen bis 250 €).
Privatkunden (B2C): weiterhin zulässig, denn Rechnungen an Privatkunden sind von der Pflicht nicht betroffen.
Erklärung: c) verlangt, den Text auf die Situation anzuwenden: Entscheidend ist der Umsatz des Vorjahres (2026) – diese Angabe steht in Anlage 1, nicht im Text.
| Zeile | Fehler |
|---|---|
| 5 | Das Fälligkeitsdatum (16.11.2026) liegt vor dem Rechnungsdatum (30.11.2026). |
| 25 (29) | Der Nettobetrag der Position 2 ist falsch: 4 × 27,90 € = 111,60 €, angegeben sind 101,60 €. Dadurch stimmt auch die Summe der Positionen (148,00 € + 101,60 € = 249,60 €) nicht mit dem Nettogesamtbetrag von 259,60 € (Zeile 29) überein. |
| 31 | Der Bruttogesamtbetrag ist falsch: 259,60 € + 18,17 € = 277,77 €, angegeben sind 278,77 €. |
Erläuterung: Das XML-Schema prüft nur die Struktur (Elemente, Reihenfolge, Pflichtangaben) und die Datentypen einzelner Werte – hier, dass die Datumsangaben gültige Daten und die Beträge Dezimalzahlen sind. Das ist bei allen Werten der Fall. Logische und rechnerische Zusammenhänge zwischen mehreren Werten (Fälligkeit nach Rechnungsdatum, Menge × Preis = Nettobetrag, Summen) lassen sich mit einer XSD nicht ausdrücken. Dafür sind zusätzlich Geschäftsregeln nötig, z. B. die Schematron-Regeln der EN 16931/XRechnung im Validator sowie Plausibilitätsprüfungen und Tests der eigenen Berechnungslogik.
Kontrolle · Aufgabe_SD_05_3.java (Schema-Validierung mit javax.xml.validation, danach Geschäftsregeln)
Schritt 1 - Schema-Validierung: XSD-gültig Schritt 2 - Geschäftsregeln: Regel 1 verletzt: Fälligkeitsdatum 2026-11-16 liegt vor dem Rechnungsdatum 2026-11-30 Regel 2 verletzt: Position 2: Menge x Einzelpreis = 111.60, angegeben 101.60 Regel 3 verletzt: Summe der Positionsbeträge = 249.60, Nettogesamt angegeben 259.60 Regel 5 verletzt: Netto + USt = 277.77, Bruttogesamt angegeben 278.77 Gegenprobe A (Dezimalkomma 18,50): XSD-Fehler in Zeile 17: cvc-datatype-valid.1.2.1: '18,50' ist kein gültiger Wert für 'decimal'. Gegenprobe B (ungültiges Datum 2026-11-31): XSD-Fehler in Zeile 4: cvc-datatype-valid.1.2.1: '2026-11-31' ist kein gültiger Wert für 'date'. Gegenprobe C (Element summen fehlt): XSD-Fehler in Zeile 29: cvc-complex-type.2.4.b: Content des Elements 'rechnung' ist nicht vollständig. …
Die Gegenproben zeigen, was das Schema sehr wohl erkennt: falsche Datentypen, unmögliche Daten und fehlende Elemente. Die Umsatzsteuer (7 % von 259,60 € = 18,17 €) ist korrekt (Regel 4 nicht verletzt). Das verwendete Schema ist vereinfacht (Datei im Java-Programm); echte XRechnungen werden gegen die XSD von UBL bzw. CII geprüft.
da) TLS beruht auf einer hybriden Verschlüsselung: asymmetrisch für den sicheren Austausch eines Sitzungsschlüssels, symmetrisch für die schnelle Übertragung der Daten.
db) TLS verschlüsselt nur die jeweilige Teilstrecke (Mailprogramm → Mailserver, Mailserver → Mailserver). Auf jedem beteiligten Mailserver wird die Nachricht entschlüsselt und liegt dort im Klartext vor; Betreiber oder Angreifer mit Zugriff auf einen Server könnten sie lesen oder verändern. Alternative: Ende-zu-Ende-Verschlüsselung der E-Mail mit S/MIME oder PGP – oder die Übermittlung über Peppol bzw. ein Rechnungsportal (HTTPS).
Die Leitweg-ID ist eine eindeutige Kennung des öffentlichen Rechnungsempfängers (hier der Stadt Heidmoor bzw. der Kita Sonnenblume). Der Auftraggeber teilt sie mit, und sie muss in der XRechnung angegeben werden (Käuferreferenz). Mit ihr wird die Rechnung im Rechnungseingangssystem der Verwaltung automatisch an die zuständige Stelle weitergeleitet und zugeordnet; fehlt sie, wird die Rechnung in der Regel abgelehnt.
Erklärung: c) entspricht W25 T1 A3bc: Dort war der Nettogesamtbetrag aus den Bruttowerten statt aus den Nettowerten gebildet – formal korrekt, fachlich falsch. Genau diese Klasse von Fehlern deckt eine XSD nie ab.