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