AtomicObjectOutputStream.java

  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. import java.io.IOException;
  12. import java.io.OutputStream;
  13. import java.nio.file.Path;
  14. import java.security.DigestOutputStream;
  15. import java.text.MessageFormat;

  16. import org.eclipse.jgit.annotations.Nullable;
  17. import org.eclipse.jgit.internal.storage.file.LockFile;
  18. import org.eclipse.jgit.lfs.errors.CorruptLongObjectException;
  19. import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
  20. import org.eclipse.jgit.lfs.lib.Constants;
  21. import org.eclipse.jgit.lfs.lib.LongObjectId;

  22. /**
  23.  * Output stream writing content to a
  24.  * {@link org.eclipse.jgit.internal.storage.file.LockFile} which is committed on
  25.  * close(). The stream checks if the hash of the stream content matches the id.
  26.  */
  27. public class AtomicObjectOutputStream extends OutputStream {

  28.     private LockFile locked;

  29.     private DigestOutputStream out;

  30.     private boolean aborted;

  31.     private AnyLongObjectId id;

  32.     /**
  33.      * Constructor for AtomicObjectOutputStream.
  34.      *
  35.      * @param path
  36.      *            a {@link java.nio.file.Path} object.
  37.      * @param id
  38.      *            a {@link org.eclipse.jgit.lfs.lib.AnyLongObjectId} object.
  39.      * @throws java.io.IOException
  40.      */
  41.     public AtomicObjectOutputStream(Path path, AnyLongObjectId id)
  42.             throws IOException {
  43.         locked = new LockFile(path.toFile());
  44.         locked.lock();
  45.         this.id = id;
  46.         out = new DigestOutputStream(locked.getOutputStream(),
  47.                 Constants.newMessageDigest());
  48.     }

  49.     /**
  50.      * Constructor for AtomicObjectOutputStream.
  51.      *
  52.      * @param path
  53.      *            a {@link java.nio.file.Path} object.
  54.      * @throws java.io.IOException
  55.      */
  56.     public AtomicObjectOutputStream(Path path) throws IOException {
  57.         this(path, null);
  58.     }

  59.     /**
  60.      * Get the <code>id</code>.
  61.      *
  62.      * @return content hash of the object which was streamed through this
  63.      *         stream. May return {@code null} if called before closing this
  64.      *         stream.
  65.      */
  66.     @Nullable
  67.     public AnyLongObjectId getId() {
  68.         return id;
  69.     }

  70.     /** {@inheritDoc} */
  71.     @Override
  72.     public void write(int b) throws IOException {
  73.         out.write(b);
  74.     }

  75.     /** {@inheritDoc} */
  76.     @Override
  77.     public void write(byte[] b) throws IOException {
  78.         out.write(b);
  79.     }

  80.     /** {@inheritDoc} */
  81.     @Override
  82.     public void write(byte[] b, int off, int len) throws IOException {
  83.         out.write(b, off, len);
  84.     }

  85.     /** {@inheritDoc} */
  86.     @Override
  87.     public void close() throws IOException {
  88.         out.close();
  89.         if (!aborted) {
  90.             if (id != null) {
  91.                 verifyHash();
  92.             } else {
  93.                 id = LongObjectId.fromRaw(out.getMessageDigest().digest());
  94.             }
  95.             locked.commit();
  96.         }
  97.     }

  98.     private void verifyHash() {
  99.         AnyLongObjectId contentHash = LongObjectId
  100.                 .fromRaw(out.getMessageDigest().digest());
  101.         if (!contentHash.equals(id)) {
  102.             abort();
  103.             throw new CorruptLongObjectException(id, contentHash,
  104.                     MessageFormat.format(LfsText.get().corruptLongObject,
  105.                             contentHash, id));
  106.         }
  107.     }

  108.     /**
  109.      * Aborts the stream. Temporary file will be deleted
  110.      */
  111.     public void abort() {
  112.         locked.unlock();
  113.         aborted = true;
  114.     }
  115. }