1 /* 2 * Copyright (C) 2015, Matthias Sohn <matthias.sohn@sap.com> 3 * and other copyright owners as documented in the project's IP log. 4 * 5 * This program and the accompanying materials are made available 6 * under the terms of the Eclipse Distribution License v1.0 which 7 * accompanies this distribution, is reproduced below, and is 8 * available at http://www.eclipse.org/org/documents/edl-v10.php 9 * 10 * All rights reserved. 11 * 12 * Redistribution and use in source and binary forms, with or 13 * without modification, are permitted provided that the following 14 * conditions are met: 15 * 16 * - Redistributions of source code must retain the above copyright 17 * notice, this list of conditions and the following disclaimer. 18 * 19 * - Redistributions in binary form must reproduce the above 20 * copyright notice, this list of conditions and the following 21 * disclaimer in the documentation and/or other materials provided 22 * with the distribution. 23 * 24 * - Neither the name of the Eclipse Foundation, Inc. nor the 25 * names of its contributors may be used to endorse or promote 26 * products derived from this software without specific prior 27 * written permission. 28 * 29 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 30 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 31 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 32 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 33 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 34 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 35 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 36 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 37 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 38 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 39 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 40 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 41 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 42 */ 43 package org.eclipse.jgit.lfs.internal; 44 45 import java.io.IOException; 46 import java.io.OutputStream; 47 import java.nio.file.Path; 48 import java.security.DigestOutputStream; 49 import java.text.MessageFormat; 50 51 import org.eclipse.jgit.annotations.Nullable; 52 import org.eclipse.jgit.internal.storage.file.LockFile; 53 import org.eclipse.jgit.lfs.errors.CorruptLongObjectException; 54 import org.eclipse.jgit.lfs.lib.AnyLongObjectId; 55 import org.eclipse.jgit.lfs.lib.Constants; 56 import org.eclipse.jgit.lfs.lib.LongObjectId; 57 58 /** 59 * Output stream writing content to a 60 * {@link org.eclipse.jgit.internal.storage.file.LockFile} which is committed on 61 * close(). The stream checks if the hash of the stream content matches the id. 62 */ 63 public class AtomicObjectOutputStream extends OutputStream { 64 65 private LockFile locked; 66 67 private DigestOutputStream out; 68 69 private boolean aborted; 70 71 private AnyLongObjectId id; 72 73 /** 74 * Constructor for AtomicObjectOutputStream. 75 * 76 * @param path 77 * a {@link java.nio.file.Path} object. 78 * @param id 79 * a {@link org.eclipse.jgit.lfs.lib.AnyLongObjectId} object. 80 * @throws java.io.IOException 81 */ 82 public AtomicObjectOutputStream(Path path, AnyLongObjectId id) 83 throws IOException { 84 locked = new LockFile(path.toFile()); 85 locked.lock(); 86 this.id = id; 87 out = new DigestOutputStream(locked.getOutputStream(), 88 Constants.newMessageDigest()); 89 } 90 91 /** 92 * Constructor for AtomicObjectOutputStream. 93 * 94 * @param path 95 * a {@link java.nio.file.Path} object. 96 * @throws java.io.IOException 97 */ 98 public AtomicObjectOutputStream(Path path) throws IOException { 99 this(path, null); 100 } 101 102 /** 103 * Get the <code>id</code>. 104 * 105 * @return content hash of the object which was streamed through this 106 * stream. May return {@code null} if called before closing this 107 * stream. 108 */ 109 @Nullable 110 public AnyLongObjectId getId() { 111 return id; 112 } 113 114 /** {@inheritDoc} */ 115 @Override 116 public void write(int b) throws IOException { 117 out.write(b); 118 } 119 120 /** {@inheritDoc} */ 121 @Override 122 public void write(byte[] b) throws IOException { 123 out.write(b); 124 } 125 126 /** {@inheritDoc} */ 127 @Override 128 public void write(byte[] b, int off, int len) throws IOException { 129 out.write(b, off, len); 130 } 131 132 /** {@inheritDoc} */ 133 @Override 134 public void close() throws IOException { 135 out.close(); 136 if (!aborted) { 137 if (id != null) { 138 verifyHash(); 139 } else { 140 id = LongObjectId.fromRaw(out.getMessageDigest().digest()); 141 } 142 locked.commit(); 143 } 144 } 145 146 private void verifyHash() { 147 AnyLongObjectId contentHash = LongObjectId 148 .fromRaw(out.getMessageDigest().digest()); 149 if (!contentHash.equals(id)) { 150 abort(); 151 throw new CorruptLongObjectException(id, contentHash, 152 MessageFormat.format(LfsText.get().corruptLongObject, 153 contentHash, id)); 154 } 155 } 156 157 /** 158 * Aborts the stream. Temporary file will be deleted 159 */ 160 public void abort() { 161 locked.unlock(); 162 aborted = true; 163 } 164 }