package gov.loc.repository.bagit.writer; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.HashSet; import java.util.ResourceBundle; import java.util.Map.Entry; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import gov.loc.repository.bagit.domain.Bag; import gov.loc.repository.bagit.domain.Manifest; import gov.loc.repository.bagit.hash.Hasher; import gov.loc.repository.bagit.util.PathUtils; import gov.loc.repository.bagit.verify.FileCountAndTotalSizeVistor; /** * responsible for writing out a {@link Bag} */ public final class BagWriter { private static final Logger logger = LoggerFactory.getLogger(BagWriter.class); private static final ResourceBundle messages = ResourceBundle.getBundle("MessageBundle"); private BagWriter(){ //intentionally left empty } /** * Write the bag out to the specified directory. * If an error occurs some of the files may have been written out to the filesystem. * tag manifest(s) are updated prior to writing to ensure bag is valid after completion, * it is therefore recommended if you are going to further interact with the bag to read it from specified outputDir path * * @param bag the {@link Bag} object to write out * @param outputDir the output directory that will become the root of the bag * * @throws IOException if there is a problem writing a file * @throws NoSuchAlgorithmException when trying to generate a {@link MessageDigest} which is used during update. */ public static void write(final Bag bag, final Path outputDir) throws IOException, NoSuchAlgorithmException{ logger.debug(messages.getString("writing_payload_files")); final Path bagitDir = PayloadWriter.writeVersionDependentPayloadFiles(bag, outputDir); logger.debug(messages.getString("upsert_payload_oxum")); final String payloadOxum = generatePayloadOxum(PathUtils.getDataDir(bag.getVersion(), outputDir)); bag.getMetadata().upsertPayloadOxum(payloadOxum); logger.debug(messages.getString("writing_bagit_file")); BagitFileWriter.writeBagitFile(bag.getVersion(), bag.getFileEncoding(), bagitDir); logger.debug(messages.getString("writing_payload_manifests")); ManifestWriter.writePayloadManifests(bag.getPayLoadManifests(), bagitDir, bag.getRootDir(), bag.getFileEncoding()); if(!bag.getMetadata().isEmpty()){ logger.debug(messages.getString("writing_bag_metadata")); MetadataWriter.writeBagMetadata(bag.getMetadata(), bag.getVersion(), bagitDir, bag.getFileEncoding()); } if(bag.getItemsToFetch().size() > 0){ logger.debug(messages.getString("writing_fetch_file")); FetchWriter.writeFetchFile(bag.getItemsToFetch(), bagitDir, bag.getRootDir(), bag.getFileEncoding()); } if(bag.getTagManifests().size() > 0){ logger.debug(messages.getString("writing_tag_manifests")); writeTagManifestFiles(bag.getTagManifests(), bagitDir, bag.getRootDir()); final Set<Manifest> updatedTagManifests = updateTagManifests(bag, outputDir); bag.setTagManifests(updatedTagManifests); ManifestWriter.writeTagManifests(updatedTagManifests, bagitDir, outputDir, bag.getFileEncoding()); } } /** * Calculate the total file and byte count of the files in the payload directory * * @param dataDir the directory to calculate the payload-oxum * * @return the string representation of the payload-oxum value * * @throws IOException if there is an error reading any of the files */ private static String generatePayloadOxum(final Path dataDir) throws IOException{ final FileCountAndTotalSizeVistor visitor = new FileCountAndTotalSizeVistor(); Files.walkFileTree(dataDir, visitor); return visitor.getTotalSize() + "." + visitor.getCount(); } /* * Update the tag manifest cause the checksum of the other tag files will have changed since we just wrote them out to disk */ @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") private static Set<Manifest> updateTagManifests(final Bag bag, final Path newBagRootDir) throws NoSuchAlgorithmException, IOException{ final Set<Manifest> newManifests = new HashSet<>(); for(final Manifest tagManifest : bag.getTagManifests()){ final Manifest newManifest = new Manifest(tagManifest.getAlgorithm()); for(final Path originalPath : tagManifest.getFileToChecksumMap().keySet()){ final Path relativePath = bag.getRootDir().relativize(originalPath); final Path pathToUpdate = newBagRootDir.resolve(relativePath); final MessageDigest messageDigest = MessageDigest.getInstance(tagManifest.getAlgorithm().getMessageDigestName()); final String newChecksum = Hasher.hash(pathToUpdate, messageDigest); newManifest.getFileToChecksumMap().put(pathToUpdate, newChecksum); } newManifests.add(newManifest); } return newManifests; } /* * Write the tag manifest files */ private static void writeTagManifestFiles(final Set<Manifest> manifests, final Path outputDir, final Path bagRootDir) throws IOException{ for(final Manifest manifest : manifests){ for(final Entry<Path, String> entry : manifest.getFileToChecksumMap().entrySet()){ final Path relativeLocation = bagRootDir.relativize(entry.getKey()); final Path writeTo = outputDir.resolve(relativeLocation); final Path writeToParent = writeTo.getParent(); if(!Files.exists(writeTo) && writeToParent != null){ Files.createDirectories(writeToParent); Files.copy(entry.getKey(), writeTo); } } } } }