/* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions copyright [year] [name of copyright owner]". * * Copyright 2015 ForgeRock AS. */ package org.forgerock.openidm.maintenance.upgrade; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.Map; import java.util.Set; import javax.xml.bind.annotation.adapters.HexBinaryAdapter; /** * Map-backed checksum file abstraction of the format * <pre> * file1,6cd3556deb0da54bca060b4c39479839 * </pre> */ public class ChecksumFile extends HashMap<Path, String> { static final long serialVersionUID = 1L; /** Hex adapter for converting hex sums in checksum file to byte arrays */ private static final HexBinaryAdapter hexAdapter = new HexBinaryAdapter(); // Path to checksums file private final Path checksums; // MessageDigest appropriate for algorithm used for checksum private final MessageDigest digest; ChecksumFile(Path checksums) throws IOException, NoSuchAlgorithmException { if (!Files.exists(checksums)) { throw new FileNotFoundException(checksums.toString() + " does not exist"); } this.checksums = checksums; // Assumes csv of #File,algorithm. // Supports MD5, SHA-1 and SHA-256 at a minimum. try (final BufferedReader reader = Files.newBufferedReader(this.checksums, Charset.defaultCharset())) { // read first line for algorithm header String line = reader.readLine(); String[] parts = line.split(","); if (!line.startsWith("#") || parts.length < 2) { throw new IllegalArgumentException(checksums.toString() + " format is invalid."); } digest = MessageDigest.getInstance(parts[1]); // read other lines while ((line = reader.readLine()) != null) { parts = line.split(","); if (!line.startsWith("#") && parts.length > 1) { this.put(Paths.get(parts[0]), parts[1]); } else { throw new IllegalArgumentException(checksums.toString() + " has incomplete line."); } } } } /** * Return the set of files represented in the checksum file. * * @return the set of files listed in the checksum file */ Set<Path> getFilePaths() { return keySet(); } /** * Resolve the full path of the provided file relative to the checksum file's path. * * @param file a file in the distribution * @return the path of the file relative to the checksum file (root path) */ Path resolvePath(Path file) { return checksums.getParent().resolve(file); } private String computeDigest(byte[] data) { return hexAdapter.marshal(digest.digest(data)); } /** * Returns the digestCache of the original, shipped file. * * @param shippedFile the original, shipped file. * @return the digestCache */ String getOriginalDigest(Path shippedFile) { return get(shippedFile); } /** * Computes and returns the digestCache of a current file on disk. * * @param currentFile the current file * @return the digestCache */ String getCurrentDigest(Path currentFile) throws IOException { return computeDigest(Files.readAllBytes(resolvePath(currentFile))); } /** * Persist the current digest cache to disk. * * @throws IOException */ void persistChecksums() throws IOException { try (final BufferedWriter writer = Files.newBufferedWriter(checksums, Charset.defaultCharset())) { // Write the header line. writer.write("#File," + digest.getAlgorithm()); writer.newLine(); // Write all of the checksums. for (Map.Entry<Path, String> entry : entrySet()) { writer.write(entry.getKey().toString() + "," + entry.getValue()); writer.newLine(); } } } }