Servicio de purga de expedientes prescritos
Localizar y eliminar los expedientes prescritos recorriendo el árbol de archivo, aplicando la regla de negocio R4: 3 meses para infracciones leves, 6 meses para graves/muy graves, contados desde la comisión sin notificación.
1Recorrer el árbol con Files.walk
Files.walk devuelve un Stream<Path> perezoso que baja por todos los subdirectorios. Mantiene descriptores de directorio abiertos: try-with-resources es obligatorio, no opcional.
try (Stream<Path> stream = Files.walk(raizExpedientes)) {
List<Path> candidatos = stream
.filter(Files::isRegularFile)
.filter(this::estaPrescrito)
.collect(Collectors.toList());
}
2Aplicar la regla R4 de prescripción
3 meses para infracciones leves, 6 meses para graves/muy graves, desde la comisión sin notificación.
private boolean estaPrescrito(Expediente expediente) {
Period plazo = expediente.getGravedad() == Gravedad.LEVE
? Period.ofMonths(3)
: Period.ofMonths(6); // graves y muy graves
LocalDate limite = expediente.getFechaComision().plus(plazo);
return expediente.getNotificacion() == null
&& LocalDate.now().isAfter(limite);
}
3Filtrar con PathMatcher o Stream
Si el filtro es por patrón de ruta (p.ej. solo ficheros .json), compílalo una sola vez con PathMatcher en vez de repetir la compilación glob en cada iteración del bucle.
PathMatcher matcher = FileSystems.getDefault()
.getPathMatcher("glob:**/*.json");
// reutilizar matcher.matches(path) dentro del filtro del stream
4Borrar ficheros y directorios vacíos
Files.delete sobre un directorio solo funciona si está vacío: borra primero los ficheros del expediente y comprueba después si el directorio quedó vacío.
Files.delete(ficheroExpediente);
Path directorio = ficheroExpediente.getParent();
try (Stream<Path> contenido = Files.list(directorio)) {
if (contenido.findAny().isEmpty()) {
Files.delete(directorio); // ahora sí está vacío
}
}
5Registro y control de ejecución
Deja rastro de qué se purgó antes de borrarlo, y no dejes que la purga se dispare sola.
@Slf4j
public class ServicioPurgaExpedientes {
public int purgar() throws IOException {
int purgados = 0;
for (Expediente expediente : localizarPrescritos()) {
log.info("Purgando expediente prescrito {} (comisión {})",
expediente.getNumero(), expediente.getFechaComision());
eliminar(expediente);
purgados++;
}
return purgados;
}
}
@PostConstruct ni @Scheduled aquí): solo bajo demanda, desde el endpoint.6Endpoint y pruebas
@PostMapping("/expedientes/purga")
public ResponseEntity<Integer> purgar() throws IOException {
int purgados = servicioPurga.purgar();
return ResponseEntity.ok(purgados);
}
!Problemas frecuentes
Stream de Files.walk puede dar comportamiento indefinido. Recolecta primero la lista de candidatos, cierra el stream, y borra después.Files.delete vea el directorio como vacío. Compruébalo con Files.list, no asumas.✓Criterios de aceptación
- Usa la API
java.nio.file, nojava.io.File - Distingue correctamente el plazo por gravedad (3 vs. 6 meses)
- Elimina los directorios que quedan vacíos tras la purga
- Los expedientes no prescritos quedan intactos
- Registra en log cada expediente purgado antes de borrarlo
- El endpoint devuelve el número de expedientes purgados
- La purga no se ejecuta automáticamente al arranque
Adaptado de apartado c — Recorrido de árboles y apartado h — Actividades.