1e4e39a6dSChristian Halstrick /* 2*5c5f7c6bSMatthias Sohn * Copyright (C) 2015, Matthias Sohn <matthias.sohn@sap.com> and others 3e4e39a6dSChristian Halstrick * 4*5c5f7c6bSMatthias Sohn * This program and the accompanying materials are made available under the 5*5c5f7c6bSMatthias Sohn * terms of the Eclipse Distribution License v. 1.0 which is available at 6*5c5f7c6bSMatthias Sohn * https://www.eclipse.org/org/documents/edl-v10.php. 7e4e39a6dSChristian Halstrick * 8*5c5f7c6bSMatthias Sohn * SPDX-License-Identifier: BSD-3-Clause 9e4e39a6dSChristian Halstrick */ 10e4e39a6dSChristian Halstrick package org.eclipse.jgit.lfs.internal; 11e4e39a6dSChristian Halstrick 12e4e39a6dSChristian Halstrick import java.io.IOException; 13e4e39a6dSChristian Halstrick import java.io.OutputStream; 14e4e39a6dSChristian Halstrick import java.nio.file.Path; 15e4e39a6dSChristian Halstrick import java.security.DigestOutputStream; 16e4e39a6dSChristian Halstrick import java.text.MessageFormat; 17e4e39a6dSChristian Halstrick 1883555e7eSMatthias Sohn import org.eclipse.jgit.annotations.Nullable; 19e4e39a6dSChristian Halstrick import org.eclipse.jgit.internal.storage.file.LockFile; 20e4e39a6dSChristian Halstrick import org.eclipse.jgit.lfs.errors.CorruptLongObjectException; 21e4e39a6dSChristian Halstrick import org.eclipse.jgit.lfs.lib.AnyLongObjectId; 22e4e39a6dSChristian Halstrick import org.eclipse.jgit.lfs.lib.Constants; 23e4e39a6dSChristian Halstrick import org.eclipse.jgit.lfs.lib.LongObjectId; 24e4e39a6dSChristian Halstrick 25e4e39a6dSChristian Halstrick /** 26e0332bfbSMatthias Sohn * Output stream writing content to a 27e0332bfbSMatthias Sohn * {@link org.eclipse.jgit.internal.storage.file.LockFile} which is committed on 28e0332bfbSMatthias Sohn * close(). The stream checks if the hash of the stream content matches the id. 29e4e39a6dSChristian Halstrick */ 30e4e39a6dSChristian Halstrick public class AtomicObjectOutputStream extends OutputStream { 31e4e39a6dSChristian Halstrick 32e4e39a6dSChristian Halstrick private LockFile locked; 33e4e39a6dSChristian Halstrick 34e4e39a6dSChristian Halstrick private DigestOutputStream out; 35e4e39a6dSChristian Halstrick 36e4e39a6dSChristian Halstrick private boolean aborted; 37e4e39a6dSChristian Halstrick 38e4e39a6dSChristian Halstrick private AnyLongObjectId id; 39e4e39a6dSChristian Halstrick 40e4e39a6dSChristian Halstrick /** 41e0332bfbSMatthias Sohn * Constructor for AtomicObjectOutputStream. 42e0332bfbSMatthias Sohn * 43e4e39a6dSChristian Halstrick * @param path 44e0332bfbSMatthias Sohn * a {@link java.nio.file.Path} object. 45e4e39a6dSChristian Halstrick * @param id 46e0332bfbSMatthias Sohn * a {@link org.eclipse.jgit.lfs.lib.AnyLongObjectId} object. 47e0332bfbSMatthias Sohn * @throws java.io.IOException 48e4e39a6dSChristian Halstrick */ AtomicObjectOutputStream(Path path, AnyLongObjectId id)49e4e39a6dSChristian Halstrick public AtomicObjectOutputStream(Path path, AnyLongObjectId id) 50e4e39a6dSChristian Halstrick throws IOException { 51e4e39a6dSChristian Halstrick locked = new LockFile(path.toFile()); 52e4e39a6dSChristian Halstrick locked.lock(); 53e4e39a6dSChristian Halstrick this.id = id; 54e4e39a6dSChristian Halstrick out = new DigestOutputStream(locked.getOutputStream(), 55e4e39a6dSChristian Halstrick Constants.newMessageDigest()); 56e4e39a6dSChristian Halstrick } 57e4e39a6dSChristian Halstrick 5883555e7eSMatthias Sohn /** 59e0332bfbSMatthias Sohn * Constructor for AtomicObjectOutputStream. 60e0332bfbSMatthias Sohn * 6183555e7eSMatthias Sohn * @param path 62e0332bfbSMatthias Sohn * a {@link java.nio.file.Path} object. 63e0332bfbSMatthias Sohn * @throws java.io.IOException 6483555e7eSMatthias Sohn */ AtomicObjectOutputStream(Path path)6583555e7eSMatthias Sohn public AtomicObjectOutputStream(Path path) throws IOException { 6683555e7eSMatthias Sohn this(path, null); 6783555e7eSMatthias Sohn } 6883555e7eSMatthias Sohn 6983555e7eSMatthias Sohn /** 70e0332bfbSMatthias Sohn * Get the <code>id</code>. 71e0332bfbSMatthias Sohn * 7283555e7eSMatthias Sohn * @return content hash of the object which was streamed through this 73e0332bfbSMatthias Sohn * stream. May return {@code null} if called before closing this 74e0332bfbSMatthias Sohn * stream. 7583555e7eSMatthias Sohn */ 76608b6b03SJonathan Nieder @Nullable getId()77608b6b03SJonathan Nieder public AnyLongObjectId getId() { 7883555e7eSMatthias Sohn return id; 7983555e7eSMatthias Sohn } 8083555e7eSMatthias Sohn 81e0332bfbSMatthias Sohn /** {@inheritDoc} */ 82e4e39a6dSChristian Halstrick @Override write(int b)83e4e39a6dSChristian Halstrick public void write(int b) throws IOException { 84e4e39a6dSChristian Halstrick out.write(b); 85e4e39a6dSChristian Halstrick } 86e4e39a6dSChristian Halstrick 87e0332bfbSMatthias Sohn /** {@inheritDoc} */ 88e4e39a6dSChristian Halstrick @Override write(byte[] b)89e4e39a6dSChristian Halstrick public void write(byte[] b) throws IOException { 90e4e39a6dSChristian Halstrick out.write(b); 91e4e39a6dSChristian Halstrick } 92e4e39a6dSChristian Halstrick 93e0332bfbSMatthias Sohn /** {@inheritDoc} */ 94e4e39a6dSChristian Halstrick @Override write(byte[] b, int off, int len)95e4e39a6dSChristian Halstrick public void write(byte[] b, int off, int len) throws IOException { 96e4e39a6dSChristian Halstrick out.write(b, off, len); 97e4e39a6dSChristian Halstrick } 98e4e39a6dSChristian Halstrick 99e0332bfbSMatthias Sohn /** {@inheritDoc} */ 100e4e39a6dSChristian Halstrick @Override close()101e4e39a6dSChristian Halstrick public void close() throws IOException { 102e4e39a6dSChristian Halstrick out.close(); 103e4e39a6dSChristian Halstrick if (!aborted) { 10483555e7eSMatthias Sohn if (id != null) { 105e4e39a6dSChristian Halstrick verifyHash(); 10683555e7eSMatthias Sohn } else { 10783555e7eSMatthias Sohn id = LongObjectId.fromRaw(out.getMessageDigest().digest()); 10883555e7eSMatthias Sohn } 109e4e39a6dSChristian Halstrick locked.commit(); 110e4e39a6dSChristian Halstrick } 111e4e39a6dSChristian Halstrick } 112e4e39a6dSChristian Halstrick verifyHash()113e4e39a6dSChristian Halstrick private void verifyHash() { 114e4e39a6dSChristian Halstrick AnyLongObjectId contentHash = LongObjectId 115e4e39a6dSChristian Halstrick .fromRaw(out.getMessageDigest().digest()); 116e4e39a6dSChristian Halstrick if (!contentHash.equals(id)) { 117e4e39a6dSChristian Halstrick abort(); 118e4e39a6dSChristian Halstrick throw new CorruptLongObjectException(id, contentHash, 119e4e39a6dSChristian Halstrick MessageFormat.format(LfsText.get().corruptLongObject, 120e4e39a6dSChristian Halstrick contentHash, id)); 121e4e39a6dSChristian Halstrick } 122e4e39a6dSChristian Halstrick } 123e4e39a6dSChristian Halstrick 124e4e39a6dSChristian Halstrick /** 125e4e39a6dSChristian Halstrick * Aborts the stream. Temporary file will be deleted 126e4e39a6dSChristian Halstrick */ abort()127e4e39a6dSChristian Halstrick public void abort() { 128e4e39a6dSChristian Halstrick locked.unlock(); 129e4e39a6dSChristian Halstrick aborted = true; 130e4e39a6dSChristian Halstrick } 131e4e39a6dSChristian Halstrick } 132