BlobStorageResource.java

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.spring.autoconfigure.storage.resource;

import com.azure.storage.blob.BlobContainerClient;
import com.azure.storage.blob.BlobServiceClient;
import com.azure.storage.blob.models.BlobStorageException;
import com.azure.storage.blob.specialized.BlockBlobClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.WritableResource;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.InputStream;
import java.net.URL;

/**
 * Implements {@link WritableResource} for reading and writing objects in Azure
 * StorageAccount blob. An instance of this class represents a handle to a blob.
 *
 * @author Warren Zhu
 */
public class BlobStorageResource extends AzureStorageResource {
    private static final Logger LOG = LoggerFactory.getLogger(BlobStorageResource.class);
    private static final String MSG_FAIL_GET = "Failed to get blob or container";
    private static final String MSG_FAIL_OPEN_OUTPUT = "Failed to open output stream of cloud blob";
    private static final String MSG_FAIL_CHECK_EXIST = "Failed to check existence of blob or container";
    private static final String MSG_FAIL_OPEN_INPUT = "Failed to open input stream of blob";
    private final BlobServiceClient blobServiceClient;
    private final String location;
    private final BlobContainerClient blobContainerClient;
    private final BlockBlobClient blockBlobClient;
    private final boolean autoCreateFiles;

    BlobStorageResource(BlobServiceClient blobServiceClient, String location) {
        this(blobServiceClient, location, false);
    }

    BlobStorageResource(BlobServiceClient blobServiceClient, String location, boolean autoCreateFiles) {
        assertIsAzureStorageLocation(location);
        this.autoCreateFiles = autoCreateFiles;
        this.blobServiceClient = blobServiceClient;
        this.location = location;

        this.blobContainerClient = blobServiceClient.getBlobContainerClient(getContainerName(location));
        this.blockBlobClient = blobContainerClient.getBlobClient(getFilename(location)).getBlockBlobClient();
    }

    @Override
    public OutputStream getOutputStream() throws IOException {
        try {
            if (!exists()) {
                if (autoCreateFiles) {
                    create();
                } else {
                    throw new FileNotFoundException("The blob was not found: " + this.location);
                }
            }
            return this.blockBlobClient.getBlobOutputStream(true);
        } catch (BlobStorageException e) {
            LOG.error(MSG_FAIL_OPEN_OUTPUT, e);
            throw new IOException(MSG_FAIL_OPEN_OUTPUT, e);
        }
    }

    @Override
    public boolean exists() {
        return this.blobContainerClient.exists() && blockBlobClient.exists();
    }

    @Override
    public URL getURL() throws IOException {
        return new URL(this.blockBlobClient.getBlobUrl());
    }

    @Override
    public File getFile() throws IOException {
        throw new UnsupportedOperationException(getDescription() + " cannot be resolved to absolute file path");
    }

    @Override
    public long contentLength() throws IOException {
        return this.blockBlobClient.getProperties().getBlobSize();
    }

    @Override
    public long lastModified() throws IOException {
        return this.blockBlobClient.getProperties().getLastModified().toEpochSecond();
    }

    @Override
    public Resource createRelative(String relativePath) throws IOException {
        String newLocation = this.location + "/" + relativePath;
        return new BlobStorageResource(this.blobServiceClient, newLocation, autoCreateFiles);
    }

    @Override
    public String getFilename() {
        return this.blockBlobClient.getBlobName();
    }

    @Override
    public String getDescription() {
        return String.format("Azure storage account blob resource [container='%s', blob='%s']",
            this.blockBlobClient.getContainerName(), this.blockBlobClient.getBlobName());
    }

    @Override
    public InputStream getInputStream() throws IOException {
        try {
            assertExisted();
            return this.blockBlobClient.openInputStream();
        } catch (BlobStorageException e) {
            LOG.error(MSG_FAIL_OPEN_INPUT, e);
            throw new IOException(MSG_FAIL_OPEN_INPUT);
        }
    }

    @Override
    StorageType getStorageType() {
        return StorageType.BLOB;
    }

    private void assertExisted() throws FileNotFoundException {
        if (!exists()) {
            throw new FileNotFoundException("Blob or container not existed.");
        }
    }

    private void create() {
        if (!this.blobContainerClient.exists()) {
            this.blobContainerClient.create();
        }
    }

}