Inleiding
Wanneer juridische teams of forensische analisten moeten aantonen dat een document niet is gemanipuleerd, is het simpelweg bekijken van de zichtbare inhoud niet voldoende. Verborgen eigenschappen — zoals auteur, aanmaakdatum of revisienummer — kunnen onthullen wie een bestand heeft aangeraakt en wanneer. Het detecteren van deze subtiele wijzigingen tussen documentversies is een veelvoorkomend pijnpunt dat vaak handmatige inspectie van elke eigenschap vereist, een tijdrovende en foutgevoelige taak.
GroupDocs.Metadata voor Java biedt een programmeerbare manier om elk metadata‑veld te extraheren en een gestructureerd verschil tussen twee versies te berekenen. In deze tutorial vergelijken we drie praktische benaderingen: een volledige metadata‑diff, gerichte detectie van eigendom‑wijzigingen en analyse van revisiegeschiedenis. Elke methode wordt gedemonstreerd met beknopte, copy‑paste‑bare code en we laten ook zien hoe de resultaten naar CSV of JSON kunnen worden geëxporteerd voor auditrapportage.
Ik kwam hiermee in aanraking bij het beoordelen van een contract dat door meerdere partijen over maanden heen was bewerkt; de zichtbare tekst leek identiek, maar de eigendom‑velden waren stilletjes veranderd.
Hoe kan ik zien welke metadata‑velden zijn gewijzigd tussen twee documentversies?
GroupDocs.Metadata laadt elk bestand, extraheert alle toegankelijke eigenschappen naar een map en doorloopt vervolgens de sleutels om toevoegingen, verwijderingen en wijzigingen te classificeren. De bibliotheek behandelt ingebouwde en aangepaste tags, zodat je een compleet beeld krijgt zonder format‑specifieke parsers te schrijven. Het resultaat is een MetadataDiff‑object dat je kunt opvragen of serialiseren voor compliance‑rapporten.
Voorvereisten
- Java 8 of hoger
- GroupDocs.Metadata voor Java 24.7 (tijdelijke licentie)
- Twee documentbestanden die je wilt vergelijken (bijv.
contract_v1.pdfencontract_v2.pdf)
Installatie
Voeg de afhankelijkheid toe via Maven:
<dependency>
<groupId>com.groupdocs</groupId>
<artifactId>groupdocs-metadata</artifactId>
<version>24.7</version>
</dependency>
Methode 1 – Volledige Metadata‑Diff
Deze methode extraheert alle metadata‑eigenschappen uit beide versies en rapporteert toegevoegde, verwijderde en gewijzigde items.
// 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;
Belangrijke punten:
- Uitgebreid: Legt alle tags vast, inclusief aangepaste tags.
- Eenvoudige map‑logica: Geen externe diff‑bibliotheek nodig.
- Resultaatobject:
added,removedenchanged‑maps zijn klaar voor verdere verwerking.
💡 Tip: Gebruik dit wanneer je een volledige audit‑trail nodig hebt voor regelgeving.
Methode 2 – Detectie van Eigendom‑Wijzigingen
Juridische geschillen draaien vaak om wie een document heeft gemaakt of bewerkt. Deze methode richt zich op persoonsgerelateerde tags zoals Creator, Editor, Manager en 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 haalt alleen de relevante tags op:
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;
Belangrijke punten:
- Gericht: Alleen identiteitsgerelateerde eigenschappen worden onderzocht.
- Duidelijke output: Retourneert een map waarbij elke entry
[oud, nieuw]waarden toont. - Compliance‑klaar: Perfect voor e‑discovery of geschillen over contract‑eigendom.
💡 Tip: Combineer dit met de volledige diff als je zowel breedte als diepte nodig hebt.
Methode 3 – Detectie van Revisie‑Geschiedenis
Revisienummers, bewerkingstijdstempels en afdrukdatums zijn onzichtbaar voor eindgebruikers maar cruciaal voor forensische tijdlijnen. Deze methode isoleert tijdgerelateerde 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 extraheert Modified, Created en Printed tijdstempels:
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;
Belangrijke punten:
- Tijdlijnreconstructie: Toont hoe vaak een bestand is bewerkt of afgedrukt.
- Numerieke deltas: Handig voor het detecteren van verdachte snelle revisies.
- Lichtgewicht: Slechts drie tags worden opgevraagd, waardoor de uitvoering snel blijft.
💡 Tip: Gebruik dit wanneer je moet bewijzen dat een document na een specifieke deadline niet is aangepast.
Vergelijking van Methoden: Wanneer Welke Gebruiken
| Methode | Beste Voor | Belangrijkste Voordelen | Beperkingen |
|---|---|---|---|
| Volledige Metadata‑Diff | Volledige audit, regelgeving | Legt elke eigenschap vast, inclusief aangepaste tags | Grotere geheugengebruik bij zeer grote bestanden |
| Detectie van Eigendom‑Wijzigingen | Juridische eigendomsgeschillen, e‑discovery | Richt zich op persoonsgerelateerde velden, makkelijk leesbaar | Negeert andere nuttige metadata |
| Detectie van Revisie‑Geschiedenis | Tijdlijn‑forensisch onderzoek, wijzigingslog‑verificatie | Isoleert tijdstempels en revisienummers | Toont geen inhoudelijke wijzigingen |
Kies de methode die aansluit bij je compliance‑doel. In veel gevallen biedt een combinatie — voer de volledige diff uit en duik vervolgens in eigendom‑ of revisiesecties — de meeste inzichten.
Exporteren van de Diff
Na het verkrijgen van een MetadataDiff moet je de bevindingen vaak delen. Hieronder twee eenvoudige exporters.
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 exporters maken gebruik van hulpfuncties (esc, escape, writeMap) die komma’s en aanhalingstekens veilig afhandelen.
Best Practices en Tips
- Beperk je diff: Voor grote PDF‑s, beperk de diff tot eigendom‑ of revisietags om de verwerkingstijd te verkorten.
- Valideer bestandsformaat: Controleer altijd
metadata.getFileFormat() != FileFormat.Unknownvoordat je eigenschappen doorloopt. - Resources vrijgeven: Gebruik try‑with‑resources (
try (Metadata metadata = new Metadata(path)) { … }) om native handles vrij te geven. - Versie‑consistentie: Zorg dat beide documenten van dezelfde bestandsformaat‑versie zijn; het mixen van DOCX en een oudere DOC kan misleidende resultaten geven.
- Beveiliging: Exposeer nooit ruwe metadata‑waarden in openbare API’s zonder sanitatie; gebruik de
esc/escape‑helpers bij het schrijven van CSV/JSON. - Prestaties: Exporteer naar CSV voor bulk‑invoer in SIEM‑systemen; JSON is beter voor mens‑leesbare audit‑logs.
Conclusie
GroupDocs.Metadata voor Java maakt metadata‑forensisch onderzoek eenvoudig. Door gebruik te maken van de volledige diff, eigendom‑detectie en revisie‑geschiedenis‑analyse kun je een robuuste audit‑pipeline bouwen die verborgen wijzigingen blootlegt, juridisch bewijs ondersteunt en voldoet aan compliance‑eisen. Exporteren naar CSV of JSON maakt naadloze integratie met rapportagetools of data‑warehouse‑pijplijnen mogelijk.
Volgende stappen:
- Verken geavanceerde tag‑specificaties om aangepaste metadata te filteren GroupDocs.Metadata Java docs.
- Leer hoe je meerdere documenten in batch kunt vergelijken API reference.
- Bekijk de officiële voorbeeldprojecten voor end‑to‑end‑implementaties GitHub examples.