Pendahuluan

Ketika tim hukum atau analis forensik perlu membuktikan bahwa sebuah dokumen tidak diubah, sekadar melihat konten yang terlihat tidak cukup. Properti tersembunyi—seperti penulis, tanggal pembuatan, atau nomor revisi—dapat mengungkap siapa yang menyentuh file dan kapan. Mendeteksi perubahan halus ini di antara versi dokumen merupakan titik sakit yang sering memerlukan inspeksi manual setiap properti, sebuah tugas yang memakan waktu dan rawan kesalahan.

GroupDocs.Metadata untuk Java menyediakan cara programatik untuk mengekstrak setiap bidang metadata dan menghitung diff terstruktur antara dua versi. Dalam tutorial ini kami akan membandingkan tiga pendekatan praktis: diff metadata lengkap, deteksi perubahan kepemilikan yang terfokus, dan analisis riwayat revisi. Setiap metode ditunjukkan dengan kode singkat yang dapat disalin‑tempel, dan kami juga akan menunjukkan cara mengekspor hasil ke CSV atau JSON untuk pelaporan audit.

Saya mengalami hal ini saat meninjau kontrak yang telah diedit oleh banyak pihak selama berbulan‑bulan; teks yang terlihat tampak identik, tetapi bidang kepemilikan telah berubah secara diam‑diam.

Bagaimana saya dapat mengetahui bidang metadata apa yang berubah antara dua versi dokumen?

GroupDocs.Metadata memuat setiap file, mengekstrak semua properti yang dapat diakses ke dalam peta, dan kemudian mengiterasi kunci‑kunci untuk mengklasifikasikan penambahan, penghapusan, dan modifikasi. Perpustakaan ini menangani tag bawaan maupun kustom, sehingga Anda mendapatkan gambaran lengkap tanpa menulis parser khusus format. Hasilnya adalah objek MetadataDiff yang dapat Anda query atau serialisasi untuk laporan kepatuhan.

Prasyarat

  • Java 8 atau lebih baru
  • GroupDocs.Metadata untuk Java 24.7 (lisensi sementara)
  • Dua file dokumen yang ingin Anda bandingkan (misalnya, contract_v1.pdf dan contract_v2.pdf)

Instalasi

Tambahkan dependensi melalui Maven:

<dependency>
    <groupId>com.groupdocs</groupId>
    <artifactId>groupdocs-metadata</artifactId>
    <version>24.7</version>
</dependency>

Metode 1 – Diff Metadata Lengkap

Metode ini mengekstrak setiap properti metadata dari kedua versi dan melaporkan entri yang ditambahkan, dihapus, dan diubah.

// 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;

Poin penting:

  • Komprehensif: Menangkap semua tag, termasuk yang kustom.
  • Logika peta sederhana: Tidak memerlukan perpustakaan diff eksternal.
  • Objek hasil: Peta added, removed, dan changed siap untuk diproses lebih lanjut.

💡 Tip: Gunakan ini ketika Anda memerlukan jejak audit lengkap untuk kepatuhan regulasi.

Metode 2 – Deteksi Perubahan Kepemilikan

Sengketa hukum sering bergantung pada siapa yang membuat atau mengedit dokumen. Metode ini memfokuskan pada tag yang berhubungan dengan orang seperti Creator, Editor, Manager, dan 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 mengambil hanya tag yang relevan:

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;

Poin penting:

  • Terarah: Hanya properti yang berhubungan dengan identitas yang diperiksa.
  • Output jelas: Mengembalikan peta di mana setiap entri menampilkan nilai [lama, baru].
  • Siap kepatuhan: Sempurna untuk e‑discovery atau sengketa kepemilikan kontrak.

💡 Tip: Gabungkan ini dengan diff lengkap jika Anda memerlukan kedalaman dan keluasan sekaligus.

Metode 3 – Deteksi Perubahan Riwayat Revisi

Nomor revisi, cap waktu edit, dan tanggal cetak tidak terlihat oleh pengguna akhir tetapi krusial untuk timeline forensik. Metode ini memisahkan tag yang berhubungan dengan waktu.

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 mengekstrak cap waktu Modified, Created, dan 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;

Poin penting:

  • Rekonstruksi timeline: Menunjukkan berapa kali file diedit atau dicetak.
  • Delta numerik: Berguna untuk mendeteksi revisi cepat yang mencurigakan.
  • Ringan: Hanya tiga tag yang diquery, sehingga eksekusi tetap cepat.

💡 Tip: Gunakan ini ketika Anda perlu membuktikan bahwa dokumen tidak diubah setelah batas waktu tertentu.

Membandingkan Metode: Kapan Menggunakan Masing‑Masing

Metode Terbaik Untuk Keunggulan Utama Keterbatasan
Full Metadata Diff Audit lengkap, kepatuhan regulasi Menangkap setiap properti, termasuk tag kustom Jejak memori lebih besar untuk file sangat besar
Ownership Change Detection Sengketa kepemilikan hukum, e‑discovery Fokus pada bidang yang berhubungan dengan orang, mudah dibaca Mengabaikan metadata lain yang berguna
Revision History Detection Forensik timeline, verifikasi log perubahan Memisahkan cap waktu dan nomor revisi Tidak menampilkan perubahan pada level konten

Pilih metode yang sesuai dengan tujuan kepatuhan Anda. Dalam banyak kasus kombinasi—menjalankan diff lengkap lalu menelusuri bagian kepemilikan atau revisi—memberikan wawasan paling lengkap.

Mengekspor Diff

Setelah memperoleh MetadataDiff, Anda sering perlu membagikan temuan. Berikut dua exporter sederhana.

Ekspor CSV

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));

Ekspor JSON

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));

Kedua exporter mengandalkan metode bantu (esc, escape, writeMap) yang menangani koma dan tanda kutip dengan aman.

Praktik Terbaik dan Tips

  • Batasi ruang lingkup diff: Untuk PDF besar, batasi diff pada tag kepemilikan atau revisi untuk mengurangi waktu pemrosesan.
  • Validasi format file: Selalu periksa metadata.getFileFormat() != FileFormat.Unknown sebelum mengiterasi properti.
  • Bebaskan sumber daya: Gunakan try‑with‑resources (try (Metadata metadata = new Metadata(path)) { … }) untuk melepaskan handle native.
  • Konsistensi versi: Pastikan kedua dokumen berasal dari versi format file yang sama; mencampur DOCX dengan DOC lama dapat menghasilkan hasil yang menyesatkan.
  • Keamanan: Jangan pernah mengekspose nilai metadata mentah dalam API publik tanpa sanitasi; gunakan helper esc/escape saat menulis CSV/JSON.
  • Kinerja: Ekspor ke CSV untuk ingest massal ke SIEM; JSON lebih cocok untuk log audit yang dapat dibaca manusia.

Kesimpulan

GroupDocs.Metadata untuk Java membuat forensik metadata menjadi sederhana. Dengan memanfaatkan metode diff lengkap, deteksi kepemilikan, dan analisis riwayat revisi, Anda dapat membangun pipeline audit yang kuat yang menampilkan perubahan tersembunyi, mendukung bukti hukum, dan memenuhi persyaratan kepatuhan. Mengekspor ke CSV atau JSON memungkinkan integrasi mulus dengan alat pelaporan atau pipeline data‑warehouse.

Langkah selanjutnya:

Sumber Daya Tambahan