Einführung
Wenn Rechtsteams oder forensische Analysten nachweisen müssen, dass ein Dokument nicht manipuliert wurde, reicht es nicht aus, nur den sichtbaren Inhalt zu betrachten. Versteckte Eigenschaften – wie Autor, Erstellungsdatum oder Versionsnummer – können offenbaren, wer eine Datei wann berührt hat. Diese subtilen Änderungen über Dokumentversionen hinweg zu erkennen, ist ein häufiges Problem, das oft eine manuelle Inspektion jeder Eigenschaft erfordert – ein zeitaufwändiger und fehleranfälliger Vorgang.
GroupDocs.Metadata für Java bietet einen programmatischen Weg, jedes Metadatenfeld zu extrahieren und einen strukturierten Unterschied zwischen zwei Versionen zu berechnen. In diesem Tutorial vergleichen wir drei praktische Ansätze: einen vollständigen Metadaten‑Diff, die gezielte Erkennung von Eigentumsänderungen und die Analyse des Versionsverlaufs. Jeder Ansatz wird mit kompakt‑kopierbarem Code demonstriert und wir zeigen zudem, wie die Ergebnisse für Audit‑Berichte nach CSV oder JSON exportiert werden können.
Mir ist das beim Durchsehen eines Vertrags aufgefallen, der über Monate von mehreren Parteien bearbeitet wurde; der sichtbare Text war identisch, aber die Eigentumsfelder hatten sich stillschweigend geändert.
Wie kann ich feststellen, welche Metadatenfelder zwischen zwei Dokumentversionen geändert wurden?
GroupDocs.Metadata lädt jede Datei, extrahiert alle zugänglichen Eigenschaften in eine Map und iteriert anschließend über die Schlüssel, um Hinzufügungen, Entfernungen und Änderungen zu klassifizieren. Die Bibliothek verarbeitet eingebaute und benutzerdefinierte Tags, sodass Sie ein vollständiges Bild erhalten, ohne format‑spezifische Parser schreiben zu müssen. Das Ergebnis ist ein MetadataDiff‑Objekt, das Sie abfragen oder für Compliance‑Berichte serialisieren können.
Voraussetzungen
- Java 8 oder höher
- GroupDocs.Metadata für Java 24.7 (temporäre Lizenz)
- Zwei Dokumentdateien, die Sie vergleichen möchten (z. B.
contract_v1.pdfundcontract_v2.pdf)
Installation
Fügen Sie die Abhängigkeit über Maven hinzu:
<dependency>
<groupId>com.groupdocs</groupId>
<artifactId>groupdocs-metadata</artifactId>
<version>24.7</version>
</dependency>
Methode 1 – Vollständiger Metadaten‑Diff
Dieser Ansatz extrahiert alle Metadaten‑Eigenschaften beider Versionen und meldet hinzugefügte, entfernte und geänderte Einträge.
// CompareMetadataSets.run – returns a MetadataDiff object
Map<String, String> v1 = ExtractAllMetadata.run(pathV1);
Map<String, String> v2 = ExtractAllMetadata.run(pathV2);
MetadataDiff diff = new MetadataDiff();
// Detect added and changed properties
for (Map.Entry<String, String> e : v2.entrySet()) {
String key = e.getKey();
String val = e.getValue();
if (!v1.containsKey(key)) {
diff.added.put(key, val); // New property in v2
} else if (!v1.get(key).equals(val)) {
diff.changed.put(key, new String[]{v1.get(key), val}); // Value changed
}
}
// Detect removed properties
for (Map.Entry<String, String> e : v1.entrySet()) {
if (!v2.containsKey(e.getKey())) {
diff.removed.put(e.getKey(), e.getValue());
}
}
return diff;
Wichtige Punkte:
- Umfassend: Erfasst alle Tags, einschließlich benutzerdefinierter.
- Einfache Map‑Logik: Keine externe Diff‑Bibliothek erforderlich.
- Ergebnisobjekt: Die Maps
added,removedundchangedstehen für weitere Verarbeitung bereit.
💡 Tipp: Verwenden Sie diesen Ansatz, wenn Sie einen vollständigen Prüfpfad für regulatorische Vorgaben benötigen.
Methode 2 – Erkennung von Eigentumsänderungen
Rechtsstreitigkeiten drehen sich häufig darum, wer ein Dokument erstellt oder bearbeitet hat. Dieser Ansatz konzentriert sich auf personenbezogene Tags wie Creator, Editor, Manager und Company.
// DetectOwnershipChanges.run – returns a map of changed ownership fields
Map<String, String> v1 = readOwnership(pathV1);
Map<String, String> v2 = readOwnership(pathV2);
Set<String> allKeys = new HashSet<>(v1.keySet());
allKeys.addAll(v2.keySet());
Map<String, String[]> changes = new LinkedHashMap<>();
for (String key : allKeys) {
String oldVal = v1.getOrDefault(key, "<missing>");
String newVal = v2.getOrDefault(key, "<missing>");
if (!oldVal.equals(newVal)) {
changes.put(key, new String[]{oldVal, newVal});
}
}
return changes;
readOwnership zieht nur die relevanten Tags:
Map<String, String> result = new LinkedHashMap<>();
try (Metadata metadata = new Metadata(path)) {
if (metadata.getFileFormat() == FileFormat.Unknown) return result;
for (MetadataProperty p : metadata.findProperties(
new ContainsTagSpecification(Tags.getPerson().getCreator())
.or(new ContainsTagSpecification(Tags.getPerson().getEditor()))
.or(new ContainsTagSpecification(Tags.getPerson().getManager()))
.or(new ContainsTagSpecification(Tags.getCorporate().getCompany())))) {
String value = "";
if (p.getValue() != null && p.getValue().getRawValue() != null) {
value = String.valueOf(p.getValue().getRawValue());
}
result.put(p.getName(), value);
}
}
return result;
Wichtige Punkte:
- Gezielt: Es werden nur identitätsbezogene Eigenschaften geprüft.
- Klare Ausgabe: Gibt eine Map zurück, bei der jeder Eintrag die Werte
[alt, neu]zeigt. - Compliance‑bereit: Ideal für e‑Discovery oder Streitigkeiten über Vertrags‑Eigentum.
💡 Tipp: Kombinieren Sie diesen Ansatz mit dem vollständigen Diff, wenn Sie sowohl Breite als auch Tiefe benötigen.
Methode 3 – Erkennung von Änderungen im Versionsverlauf
Versionsnummern, Bearbeitungszeitstempel und Druckdaten sind für Endnutzer unsichtbar, aber für forensische Zeitlinien entscheidend. Dieser Ansatz isoliert zeitbezogene Tags.
Map<String, String> v1 = readRevision(pathV1);
Map<String, String> v2 = readRevision(pathV2);
Set<String> allKeys = new HashSet<>(v1.keySet());
allKeys.addAll(v2.keySet());
Map<String, String[]> changes = new LinkedHashMap<>();
for (String key : allKeys) {
String oldVal = v1.getOrDefault(key, "<missing>");
String newVal = v2.getOrDefault(key, "<missing>");
if (!oldVal.equals(newVal)) {
changes.put(key, new String[]{oldVal, newVal});
}
}
return changes;
readRevision extrahiert die Zeitstempel Modified, Created und Printed:
Map<String, String> result = new LinkedHashMap<>();
try (Metadata metadata = new Metadata(path)) {
if (metadata.getFileFormat() == FileFormat.Unknown) return result;
for (MetadataProperty p : metadata.findProperties(
new ContainsTagSpecification(Tags.getTime().getModified())
.or(new ContainsTagSpecification(Tags.getTime().getCreated()))
.or(new ContainsTagSpecification(Tags.getTime().getPrinted())))) {
String value = "";
if (p.getValue() != null && p.getValue().getRawValue() != null) {
value = String.valueOf(p.getValue().getRawValue());
}
result.put(p.getName(), value);
}
}
return result;
Wichtige Punkte:
- Zeitlinien‑Rekonstruktion: Zeigt, wie oft eine Datei bearbeitet oder gedruckt wurde.
- Numerische Deltas: Nützlich, um verdächtige, schnelle Revisionen zu erkennen.
- Leichtgewichtig: Es werden nur drei Tags abgefragt, wodurch die Ausführung schnell bleibt.
💡 Tipp: Nutzen Sie diesen Ansatz, wenn Sie beweisen müssen, dass ein Dokument nach einer bestimmten Frist nicht verändert wurde.
Vergleich der Methoden: Wann welche verwenden
| Methode | Am besten geeignet für | Hauptvorteile | Einschränkungen |
|---|---|---|---|
| Vollständiger Metadaten‑Diff | Vollständiges Audit, regulatorische Compliance | Erfasst jede Eigenschaft, einschließlich benutzerdefinierter Tags | Größerer Speicherverbrauch bei sehr großen Dateien |
| Erkennung von Eigentumsänderungen | Rechtsstreitigkeiten über Eigentum, e‑Discovery | Fokus auf personenbezogene Felder, leicht lesbar | Ignoriert andere nützliche Metadaten |
| Erkennung von Versionsverlauf | Forensik von Zeitlinien, Verifizierung von Änderungsprotokollen | Isoliert Zeitstempel und Versionsnummern | Zeigt keine inhaltlichen Änderungen |
Wählen Sie die Methode, die zu Ihrem Compliance‑Ziel passt. In vielen Fällen liefert eine Kombination – zunächst den Voll‑Diff ausführen und anschließend Eigentums‑ oder Versionsabschnitte vertiefen – die meisten Erkenntnisse.
Export des Diffs
Nachdem Sie ein MetadataDiff erhalten haben, müssen Sie die Ergebnisse häufig teilen. Im Folgenden finden Sie zwei einfache Exporter.
CSV‑Export
StringBuilder sb = new StringBuilder();
sb.append("change_type,property,old_value,new_value\n");
for (Map.Entry<String, String> e : diff.added.entrySet()) {
sb.append("added,").append(esc(e.getKey()))
.append(",,").append(esc(e.getValue())).append("\n");
}
for (Map.Entry<String, String> e : diff.removed.entrySet()) {
sb.append("removed,").append(esc(e.getKey()))
.append(",").append(esc(e.getValue()))
.append(",\n");
}
for (Map.Entry<String, String[]> e : diff.changed.entrySet()) {
sb.append("changed,").append(esc(e.getKey()))
.append(",").append(esc(e.getValue()[0]))
.append(",").append(esc(e.getValue()[1])).append("\n");
}
Files.write(Paths.get(outputPath), sb.toString().getBytes(StandardCharsets.UTF_8));
JSON‑Export
StringBuilder sb = new StringBuilder();
sb.append("{\n");
sb.append(" \"added\": {\n");
writeMap(sb, diff.added);
sb.append(" },\n");
sb.append(" \"removed\": {\n");
writeMap(sb, diff.removed);
sb.append(" },\n");
sb.append(" \"changed\": {\n");
int i = 0;
for (Map.Entry<String, String[]> e : diff.changed.entrySet()) {
String comma = ++i < diff.changed.size() ? "," : "";
sb.append(" \"").append(escape(e.getKey()))
.append("\": { \"from\": \"")
.append(escape(e.getValue()[0])).append("\", \"to\": \"")
.append(escape(e.getValue()[1])).append("\" }")
.append(comma).append("\n");
}
sb.append(" }\n");
sb.append("}\n");
Files.write(Paths.get(outputPath), sb.toString().getBytes(StandardCharsets.UTF_8));
Beide Exporter nutzen Hilfsmethoden (esc, escape, writeMap), die Kommata und Anführungszeichen sicher behandeln.
bewährte Methoden und Tipps
- Diff eingrenzen: Bei großen PDFs den Diff auf Eigentums‑ oder Versions‑Tags beschränken, um die Verarbeitungszeit zu reduzieren.
- Dateiformat prüfen: Immer
metadata.getFileFormat() != FileFormat.Unknownprüfen, bevor Sie Eigenschaften iterieren. - Ressourcen freigeben: Verwenden Sie try‑with‑resources (
try (Metadata metadata = new Metadata(path)) { … }), um native Handles freizugeben. - Versionskonsistenz: Stellen Sie sicher, dass beide Dokumente derselben Dateiformat‑Version angehören; das Mischen von DOCX und älteren DOC kann irreführende Ergebnisse liefern.
- Sicherheit: Roh‑Metadatenwerte niemals ohne Bereinigung in öffentlichen APIs ausgeben; nutzen Sie die
esc/escape‑Hilfen beim Schreiben von CSV/JSON. - Performance: Exportieren Sie nach CSV für die Massenaufnahme in SIEMs; JSON eignet sich besser für menschenlesbare Audit‑Logs.
Fazit
GroupDocs.Metadata für Java macht die forensische Analyse von Metadaten unkompliziert. Durch die Nutzung des Voll‑Diffs, der Eigentums‑Erkennung und der Analyse des Versionsverlaufs können Sie eine robuste Prüf‑Pipeline aufbauen, die versteckte Änderungen aufdeckt, rechtliche Beweise unterstützt und Compliance‑Anforderungen erfüllt. Der Export nach CSV oder JSON ermöglicht nahtlose Integration in Reporting‑Tools oder Data‑Warehouse‑Pipelines.
Nächste Schritte:
- Erkunden Sie erweiterte Tag‑Spezifikationen, um benutzerdefinierte Metadaten zu filtern GroupDocs.Metadata Java docs.
- Lernen Sie, wie Sie mehrere Dokumente stapelweise vergleichen API reference.
- Schauen Sie sich die offiziellen Beispielprojekte für End‑to‑End‑Implementierungen GitHub examples an.