Introdução
Quando equipes jurídicas ou analistas forenses precisam provar que um documento não foi adulterado, simplesmente observar o conteúdo visível não é suficiente. Propriedades ocultas — como autor, data de criação ou número da revisão — podem revelar quem manipulou um arquivo e quando. Detectar essas alterações sutis entre versões de documentos é um ponto de dor comum que frequentemente requer inspeção manual de cada propriedade, uma tarefa demorada e propensa a erros.
GroupDocs.Metadata for Java fornece uma maneira programática de extrair todos os campos de metadados e calcular uma diferença estruturada entre duas versões. Neste tutorial compararemos três abordagens práticas: uma diferença completa de metadados, detecção focada de alterações de propriedade e análise de histórico de revisões. Cada método é demonstrado com código conciso, pronto para copiar e colar, e também mostraremos como exportar os resultados para CSV ou JSON para relatórios de auditoria.
Eu me deparei com isso ao revisar um contrato que havia sido editado por várias partes ao longo de meses; o texto visível parecia idêntico, mas os campos de propriedade haviam mudado silenciosamente.
Como posso saber quais campos de metadados mudaram entre duas versões de documento?
GroupDocs.Metadata carrega cada arquivo, extrai todas as propriedades acessíveis em um mapa e, em seguida, itera sobre as chaves para classificar adições, remoções e modificações. A biblioteca lida com tags incorporadas e personalizadas, de modo que você obtém uma visão completa sem precisar escrever analisadores específicos de formato. O resultado é um objeto MetadataDiff que pode ser consultado ou serializado para relatórios de conformidade.
Pré-requisitos
- Java 8 ou posterior
- GroupDocs.Metadata for Java 24.7 (temporary license)
- Dois arquivos de documento que você deseja comparar (por exemplo,
contract_v1.pdfecontract_v2.pdf)
Instalação
Adicione a dependência via Maven:
<dependency>
<groupId>com.groupdocs</groupId>
<artifactId>groupdocs-metadata</artifactId>
<version>24.7</version>
</dependency>
Método 1 – Diferença Completa de Metadados
Este método extrai todos os metadados de ambas as versões e relata entradas adicionadas, removidas e alteradas.
// 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;
Pontos principais:
- Abrangente: Captura todas as tags, incluindo as personalizadas.
- Lógica simples de mapa: Não requer biblioteca externa de diff.
- Objeto de resultado: Os mapas
added,removedechangedestão prontos para processamento adicional.
💡 Dica: Use este método quando precisar de um registro completo para conformidade regulatória.
Método 2 – Detectar Alterações de Propriedade
Disputas legais frequentemente giram em torno de quem criou ou editou um documento. Este método foca em tags relacionadas a pessoas, como Creator, Editor, Manager e 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 extrai apenas as tags relevantes:
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;
Pontos principais:
- Direcionado: Apenas propriedades que carregam identidade são examinadas.
- Saída clara: Retorna um mapa onde cada entrada mostra valores
[antigo, novo]. - Pronto para conformidade: Ideal para e‑discovery ou disputas de propriedade de contrato.
💡 Dica: Combine este método com a diferença completa se precisar de amplitude e profundidade.
Método 3 – Detectar Alterações no Histórico de Revisões
Números de revisão, carimbos de edição e datas de impressão são invisíveis para os usuários finais, mas cruciais para linhas do tempo forenses. Este método isola tags relacionadas ao tempo.
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 extrai os carimbos Modified, Created e 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;
Pontos principais:
- Reconstrução de linha do tempo: Mostra quantas vezes um arquivo foi editado ou impresso.
- Delas numéricas: Útil para detectar revisões suspeitas em ritmo rápido.
- Leve: Apenas três tags são consultadas, mantendo a execução rápida.
💡 Dica: Use este método quando precisar provar que um documento não foi alterado após um prazo específico.
Comparando os Métodos: Quando Usar Cada Um
| Método | Melhor Para | Principais Vantagens | Limitações |
|---|---|---|---|
| Diferença Completa de Metadados | Auditoria completa, conformidade regulatória | Captura todas as propriedades, incluindo tags personalizadas | Maior consumo de memória para arquivos muito grandes |
| Detecção de Alterações de Propriedade | Disputas de propriedade legal, e‑discovery | Foca em campos relacionados a pessoas, fácil de ler | Ignora outros metadados úteis |
| Detecção de Histórico de Revisões | Forense de linha do tempo, verificação de registro de alterações | Isola timestamps e números de revisão | Não mostra alterações ao nível do conteúdo |
Escolha o método que se alinha ao seu objetivo de conformidade. Na maioria dos casos, uma combinação — executar a diferença completa e depois aprofundar nas seções de propriedade ou revisão — fornece a visão mais completa.
Exportando a Diferença
Depois de obter um MetadataDiff, frequentemente é necessário compartilhar as descobertas. Abaixo estão dois exportadores simples.
Exportação 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));
Exportação 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));
Ambos os exportadores dependem de métodos auxiliares (esc, escape, writeMap) que tratam com segurança vírgulas e aspas.
Melhores Práticas e Dicas
- Delimite seu diff: Para PDFs grandes, limite a diferença a tags de propriedade ou revisão para reduzir o tempo de processamento.
- Valide o formato do arquivo: Sempre verifique
metadata.getFileFormat() != FileFormat.Unknownantes de iterar sobre as propriedades. - Libere recursos: Use try‑with‑resources (
try (Metadata metadata = new Metadata(path)) { … }) para liberar handles nativos. - Consistência de versão: Garanta que ambos os documentos sejam da mesma versão de formato; misturar DOCX com DOC antigo pode gerar resultados enganosos.
- Segurança: Nunca exponha valores brutos de metadados em APIs públicas sem sanitização; use os auxiliares
esc/escapeao escrever CSV/JSON. - Desempenho: Exporte para CSV para ingestão em massa em SIEMs; JSON é melhor para logs de auditoria legíveis por humanos.
Conclusão
GroupDocs.Metadata for Java torna a forense de metadados simples. Ao aproveitar os métodos de diferença completa, detecção de propriedade e análise de histórico de revisões, você pode construir um pipeline de auditoria robusto que revela mudanças ocultas, apoia evidências legais e satisfaz requisitos de conformidade. Exportar para CSV ou JSON permite integração perfeita com ferramentas de relatório ou pipelines de data‑warehouse.
Próximos passos:
- Explore especificações avançadas de tags para filtrar metadados personalizados GroupDocs.Metadata Java docs.
- Aprenda a comparar múltiplos documentos em lote API reference.
- Confira os projetos de exemplo oficiais para implementações de ponta a ponta GitHub examples.