/* * Copyright 2014-2020 Andrew Gaul <[email protected]> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.gaul.s3proxy; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.List; import javax.annotation.Nullable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.hash.HashCode; import com.google.common.io.ByteSource; import com.google.common.io.ByteStreams; import com.google.common.primitives.Longs; import org.jclouds.blobstore.BlobStore; import org.jclouds.blobstore.domain.Blob; import org.jclouds.blobstore.domain.BlobMetadata; import org.jclouds.blobstore.domain.MultipartPart; import org.jclouds.blobstore.domain.MultipartUpload; import org.jclouds.blobstore.domain.MutableStorageMetadata; import org.jclouds.blobstore.domain.PageSet; import org.jclouds.blobstore.domain.StorageMetadata; import org.jclouds.blobstore.domain.internal.MutableStorageMetadataImpl; import org.jclouds.blobstore.domain.internal.PageSetImpl; import org.jclouds.blobstore.options.GetOptions; import org.jclouds.blobstore.options.PutOptions; import org.jclouds.blobstore.util.ForwardingBlobStore; import org.jclouds.io.Payload; import org.jclouds.io.payloads.ByteSourcePayload; final class NullBlobStore extends ForwardingBlobStore { private NullBlobStore(BlobStore blobStore) { super(blobStore); } static BlobStore newNullBlobStore(BlobStore blobStore) { return new NullBlobStore(blobStore); } @Override @Nullable public BlobMetadata blobMetadata(String container, String name) { Blob blob = getBlob(container, name); if (blob == null) { return null; } return blob.getMetadata(); } @Override @Nullable public Blob getBlob(String container, String name) { return getBlob(container, name, GetOptions.NONE); } @Override @Nullable public Blob getBlob(String container, String name, GetOptions options) { Blob blob = super.getBlob(container, name, options); if (blob == null) { return null; } byte[] array; try (InputStream is = blob.getPayload().openStream()) { array = ByteStreams.toByteArray(is); } catch (IOException ioe) { throw new RuntimeException(ioe); } long length = Longs.fromByteArray(array); ByteSourcePayload payload = new ByteSourcePayload( new NullByteSource().slice(0, length)); payload.setContentMetadata(blob.getPayload().getContentMetadata()); payload.getContentMetadata().setContentLength(length); payload.getContentMetadata().setContentMD5((HashCode) null); blob.setPayload(payload); blob.getMetadata().setSize(length); return blob; } @Override public PageSet<? extends StorageMetadata> list(String container) { ImmutableSet.Builder<StorageMetadata> builder = ImmutableSet.builder(); PageSet<? extends StorageMetadata> pageSet = super.list(container); for (StorageMetadata sm : pageSet) { MutableStorageMetadata msm = new MutableStorageMetadataImpl(sm); msm.setSize(0L); builder.add(msm); } return new PageSetImpl<>(builder.build(), pageSet.getNextMarker()); } @Override public String putBlob(String containerName, Blob blob) { return putBlob(containerName, blob, PutOptions.NONE); } @Override public String putBlob(String containerName, Blob blob, PutOptions options) { long length; try (InputStream is = blob.getPayload().openStream()) { length = ByteStreams.copy(is, ByteStreams.nullOutputStream()); } catch (IOException ioe) { throw new RuntimeException(ioe); } byte[] array = Longs.toByteArray(length); ByteSourcePayload payload = new ByteSourcePayload( ByteSource.wrap(array)); payload.setContentMetadata(blob.getPayload().getContentMetadata()); payload.getContentMetadata().setContentLength((long) array.length); payload.getContentMetadata().setContentMD5((HashCode) null); blob.setPayload(payload); return super.putBlob(containerName, blob, options); } @Override public String completeMultipartUpload(final MultipartUpload mpu, final List<MultipartPart> parts) { long length = 0; for (MultipartPart part : parts) { length += part.partSize(); super.removeBlob(mpu.containerName(), mpu.id() + "-" + part.partNumber()); } byte[] array = Longs.toByteArray(length); ByteSourcePayload payload = new ByteSourcePayload( ByteSource.wrap(array)); payload.getContentMetadata().setContentLength((long) array.length); super.abortMultipartUpload(mpu); MultipartUpload mpu2 = super.initiateMultipartUpload( mpu.containerName(), mpu.blobMetadata(), mpu.putOptions()); MultipartPart part = super.uploadMultipartPart(mpu2, 1, payload); return super.completeMultipartUpload(mpu2, ImmutableList.of(part)); } @Override public void abortMultipartUpload(MultipartUpload mpu) { for (MultipartPart part : super.listMultipartUpload(mpu)) { super.removeBlob(mpu.containerName(), mpu.id() + "-" + part.partNumber()); } super.abortMultipartUpload(mpu); } @Override public MultipartPart uploadMultipartPart(MultipartUpload mpu, int partNumber, Payload payload) { long length; try (InputStream is = payload.openStream()) { length = ByteStreams.copy(is, ByteStreams.nullOutputStream()); } catch (IOException ioe) { throw new RuntimeException(ioe); } byte[] array = Longs.toByteArray(length); ByteSourcePayload newPayload = new ByteSourcePayload( ByteSource.wrap(array)); newPayload.setContentMetadata(payload.getContentMetadata()); newPayload.getContentMetadata().setContentLength((long) array.length); newPayload.getContentMetadata().setContentMD5((HashCode) null); // create a single-part object which contains the logical length which // list and complete will read later Blob blob = blobBuilder(mpu.id() + "-" + partNumber) .payload(newPayload) .build(); super.putBlob(mpu.containerName(), blob); MultipartPart part = super.uploadMultipartPart(mpu, partNumber, newPayload); return MultipartPart.create(part.partNumber(), length, part.partETag(), part.lastModified()); } @Override public List<MultipartPart> listMultipartUpload(MultipartUpload mpu) { ImmutableList.Builder<MultipartPart> builder = ImmutableList.builder(); for (MultipartPart part : super.listMultipartUpload(mpu)) { // get real blob size from stub blob Blob blob = getBlob(mpu.containerName(), mpu.id() + "-" + part.partNumber()); long length = blob.getPayload().getContentMetadata() .getContentLength(); builder.add(MultipartPart.create(part.partNumber(), length, part.partETag(), part.lastModified())); } return builder.build(); } private static final class NullByteSource extends ByteSource { @Override public InputStream openStream() throws IOException { return new NullInputStream(); } } private static final class NullInputStream extends InputStream { private boolean closed; @Override public int read() throws IOException { if (closed) { throw new IOException("Stream already closed"); } return 0; } @Override public int read(byte[] b, int off, int len) throws IOException { if (closed) { throw new IOException("Stream already closed"); } Arrays.fill(b, off, off + len, (byte) 0); return len; } @Override public void close() throws IOException { super.close(); closed = true; } } }