Windows Azure & Java. Основы хранилища. Блобы. Доступ из локального приложения. (ru-RU)

Цикл "Windows Azure & Java": 
Windows Azure, приложение на Java "Hello World" с использованием JDK 1.6 и Tomcat 7. 
Windows Azure, приложение на Java "Hello World" с использованием JDK 1.6 и Glassfish 3
Windows Azure, приложение на Java. Service Runtime Library
Вопросы отладки Java-приложений. 
Дополнительные возможности. Remote Desktop, Session Affinity. 
Windows Azure & Java. Основы хранилища. Блобы. Доступ из локального приложения. 
Windows Azure & Java. Основы хранилища. Очереди. Доступ из локального приложения.
Windows Azure & Java. Что такое Service Bus и как её использовать из Java-приложения. Часть 1.
Windows Azure & Java. Что такое Service Bus и как её использовать из Java-приложения. Часть 2. Топики. 

Для доступа к сервисам хранилищ Windows Azure из Java-приложений существует клиентская библиотека под названием Storage Client for Java. Естественно, что данная библиотека обладает основной функциональностью по доступу ко всем сервисам хранилища, включающих блобы, очереди и таблицы. В этой статье приводятся сведения по доступу к хранилищу блобов из Java-приложения.

Для введения в технологию сервиса хранилища блобов можно прочитать статью “Основы хранилища Windows Azure. Блобы” по адресу https://social.technet.microsoft.com/wiki/contents/articles/8093.windows-azure-ru-ru.aspx .

Для того, чтобы продолжить разработку, создадим новый Java-проект и внесем в него некоторые изменения в Java-проект:

1. Добавьте новый класс TestJavaStorage в Java-проект. Для этого необходимо щелкнуть правой кнопкой мыши на проекте и нажать New, после чего нажать Class (рис.1).

Рис.1. Добавление Java-класса.

2. Добавьте директивы импорта в созданный класс.

import com.microsoft.windowsazure.services.core.storage.*;
import com.microsoft.windowsazure.services.blob.client.*;

3. Настройте строку подключения к хранилищу Windows Azure. Строка подключения к хранилищу следует стандартным конвенциям и синтаксису. Замените значения AccountName и AccountKey на соответcтвующие значения, взятые с портала управления Windows Azure.

public static final String ConnectionString =
    "DefaultEndpointsProtocol=http;AccountName=[accountName]; AccountKey=[accountKey]";

Либо, если вы хотите использовать правильный подход к архитектуре приложения, добавьте следующий код, получающий строку подключения из файла конфигурации сервиса ServiceConfiguration.cscfg.

String ConnectionString =
    RoleEnvironment.getConfigurationSettings().get("StorageConnectionString");

В этом случае добавьте также одним из двумя способов – вручную либо с помощью XML Editor в Eclipse – соответствующий элемент в файл ServiceConfiguration.cscfg. Ваш файл должен иметь в секции ConfigurationSettings следующий код.

<ConfigurationSettings>

<Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="…" />



</ConfigurationSettings>

В данном примере используется первый подход и используется локальный эмулятор хранилища, однако вам ничего не мешает использовать этот код для доступа к облачному хранилищу – достаточно лишь поменять значение параметра строки подключения.

Код, предназначенный для управления блобами, аналогичен коду, используемому в C#. Подробнее про управление блобами и контейнерами можно почитать по следующей ссылке –https://social.technet.microsoft.com/wiki/contents/articles/8093.windows-azure-ru-ru.aspx . Поскольку используемые конструкции достаточно подробно рассмотрены по приведенной ссылке, останавливаться подробно на этих моментах не будем – приведу общий код с комментариями.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.security.InvalidKeyException;

import com.microsoft.windowsazure.services.core.storage.*;
import com.microsoft.windowsazure.services.blob.client.*;

public class TestJavaStorage {
    public static final String StorageConnectionString = "UseDevelopmentStorage=true";

    public static CloudBlobClient getBlobClient() throws InvalidKeyException, URISyntaxException{

        CloudStorageAccount storageAccount = CloudStorageAccount
                .parse(StorageConnectionString);
        return storageAccount.createCloudBlobClient();

    }

    public static void createContainer(String name, CloudBlobClient blobClient) throws URISyntaxException, StorageException{

        CloudBlobContainer container = blobClient.getContainerReference(name);
        container.createIfNotExist();

    }
    public static CloudBlobContainer getContainer(String name, CloudBlobClient blobClient) throws URISyntaxException, StorageException{

        return blobClient.getContainerReference(name);

    }

    public static void listBlobs(CloudBlobContainer container) throws URISyntaxException, StorageException{

       for (ListBlobItem blob : container.listBlobs()) {
            System.out.println(blob.getUri());
        }

    }

    public static void deleteBlob(CloudBlobContainer container, String name) throws StorageException, URISyntaxException{

        CloudBlockBlob blob = container.getBlockBlobReference(name);
        blob.delete();
    }
    public static void deleteContainer(CloudBlobContainer container) throws StorageException, URISyntaxException{

        container.delete();
    }

    public static void downloadBlobs(CloudBlobContainer container) throws StorageException, URISyntaxException{

        for (ListBlobItem blob : container.listBlobs()) {

            if (blob instanceof CloudBlob) {

                CloudBlob blob1 = (CloudBlob) blob;
                try {
                    blob1.download(new FileOutputStream(blob1.getName()));
                } catch (FileNotFoundException e) {

                    e.printStackTrace();
                } catch (IOException e) {

                    e.printStackTrace();
                }
            }
        }

    }

    public static void main(String args[]) throws InvalidKeyException, URISyntaxException, StorageException {

        // Создание клиента блобов
        CloudBlobClient blobClient = getBlobClient();

        // Получение ссылки на контейнер и его создание. Помните, что имя контейнера не должно иметь заглавных букв.
        createContainer("mycontainer", blobClient);

        // Присваивание разрешений на публичный доступ контейнеру блобов
        BlobContainerPermissions containerPermissions = new BlobContainerPermissions();
        containerPermissions.setPublicAccess(BlobContainerPublicAccessType.CONTAINER);

        CloudBlobContainer container = getContainer("mycontainer", blobClient);
        container.uploadPermissions(containerPermissions);

        //удаление блоба
        deleteBlob(container, "untitled.wmv");

        //удаление контейнера
        deleteContainer(container);

        // Получение ссылки на файл, который затем помещается в блоб. 
        CloudBlockBlob blob = container.getBlockBlobReference("untitled.wmv");
        File file = new File("c:\\untitled.wmv");
        try {
            blob.upload(new FileInputStream(file), file.length());
        } catch (FileNotFoundException e) {

            e.printStackTrace();
        } catch (IOException e) {

            e.printStackTrace();
        }

        // вывод URI для всех блобов в конкретном контейнере
        listBlobs(container);
        //сохранение всех блобов
        downloadBlobs(container);

    }
}

Обратите внимание на то, что в коде много раз выбрасывается набор исключений FileNotFoundException (может произойти с конструкторами FileInput(Output)Stream), StorageException (типичное исключение для клиентской библиотеки Windows Azure), URISyntaxException (выбрасывается методом getUri()).

В приведенном коде выбрасываемые исключения никак не обрабатываются. Избегайте подобного подхода, так как это ведёт к непредсказуемому поведению вашего приложения.

Далее просто запустите ваше приложение. Для этого нажмите ALT+Shift+X либо нажмите соответствующую кнопку в меню в Eclipse (рис. 2).

Рис.2. Запуск простого Java-проекта.

В консоли вы должны увидеть результат (рис.3).

Рис.3. Ожидаемый результат выполнения программы.

Для того, чтобы просмотреть содержимое хранилища, вы можете воспользоваться либо Server Explorer в Visual Studio 2010 либо специальной утилитой Windows Azure Storage Explorer (рис. 4).

Рис. 4. Использование Windows Azure Storage Explorer.

К дополнительной функциональности, которая была реализована в последних версиях клиентской библиотеки для доступа к сервисам хранилища Windows Azure, относятся работа с MD5 и разреженные страничные блобы (Sparse Page Blob).

Коды взят с официального руководства по клиентской библиотеке для хранилища Windows Azure для Java, который можно посмотреть тут – https://blogs.msdn.com/b/windowsazurestorage/archive/2012/03/05/windows-azure-storage-client-for-java-blob-features.aspx 

MD5

Есть два подхода к использованию Content-MD5 при работе с сервисом блобов: транзакционный MD5, который используется для обеспечения целостности данных во время передачи блоков или страниц блобов, и полный MD5 для блоба, который хранится вместе с блобом и возвращается с соответствующими операциями GET.

// Define BlobRequestOptions to use sparse page blob
BlobRequestOptions options = new BlobRequestOptions();
options.setUseSparsePageBlob(true);
blob.create(length);

// Alternatively could use blob.openOutputStream
blob.upload(sourceStream blobLength,
            null /* AccessCondition */,
            options,
            null /* OperationContext */);

// Alternatively could use blob.openInputStream
blobRef.download(outStream,
            null /* AccessCondition */,
            options,
            null /* OperationContext */);

Sparse Page Blob

Наиболее типичным способом для использования страничных блобов в облачных приложениях является их использование для образов VHD (Virtual Hard Drive): при создании страничного блоба он создается “забитым” нулями. Сервис хранилища Windows Azure предоставляет возможность инкрементальной записи 512-байтных страниц и отслеживания того, какие страницы были записаны, а также отслеживания того, какие страницы все еще содержат нули в качестве значений и какие страницы содержат записанные данные.

В клиентской библиотеке для Java разработчиками была внесена возможность скачивания только полезных данных без задействования кусков, которые заполнены нулями. Во время скачивания, когда библиотека обнаруживает, что текущие данные равны нулю в страничном блобе, происходит генерация нулей вместо дополнительных запросов.

// Define BlobRequestOptions to use sparse page blob
BlobRequestOptions options = new BlobRequestOptions();
options.setUseSparsePageBlob(true);
blob.create(length);

// Alternatively could use blob.openOutputStream
blob.upload(sourceStream blobLength,
            null /* AccessCondition */,
            options,
            null /* OperationContext */);

// Alternatively could use blob.openInputStream
blobRef.download(outStream,
            null /* AccessCondition */,
            options,
            null /* OperationContext */);

Кроме этого, разработчиками отдельно отмечена дополнительная функциональность в виде продолжения загрузки (Download Resume).

Одной из новых возможностей в библиотеке является возможность продолжения загрузки в случае обрыва связи или появления исключения. Наиболее эффективным путем для скачивания конкретного блоба является использование одного вызова REST GET, однако, если вы скачиваете большой блоб (несколько гигабайт), могут возникать проблемы и вопросы того, как правильно обрабатывать обрывы связи и ошибки без пребуферизации или перекачивания всего блоба. .

Для разрешения этой проблемы функциональность скачивания блоба должна быть дополнена политиками повторения (retry policies), указанными пользователем. Если операция не должна быть повторена, она просто будет закончена как ожидалось, однако, если политика повторения указывает на то, что операция должна быть повторена процесс скачивания будет “откатан” назад к использованию BlobInputStream . Это позволяет пользователю просто продолжить скачивание. Эта функция работает со всеми скачивания с помощью методаCloudBlob.download