Использование хранилища файлов

Хранилище файлов – это абстракция, обеспечивающая различные реализации того, как и где хранятся файлы, и предоставляющая единый ttps://github.com/jmix-framework/jmix/blob/master/jmix-core/core/src/main/java/io/jmix/core/FileStorage.java[интерфейс^] для доступа к файлам и создания ссылок на них из сущностей модели данных.

Jmix поставляется с двумя реализациями хранения файлов: локальной и AWS. При создании нового проекта в Studio в него включена локальная реализация.

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

Примеры

Работа с файлами в UI

В этом разделе приводится пример работы с файлами в файловом хранилище с использованием компонентов UI.

Во-первых, создайте в сущности атрибут типа FileRef, например:

@JmixEntity
@Entity
@Table(name = "ATTACHMENT")
public class Attachment {
    // ...

    @Column(name = "FILE_")
    private FileRef file;

    public FileRef getFile() {
        return file;
    }

    public void setFile(FileRef file) {
        this.file = file;
    }

При запуске приложения Studio генерирует скрипт миграции базы данных для создания соответствующего столбца строкового типа, поскольку FileRef имеет строковое представление в формате URI.

Для загрузки файлов с экрана UI используйте компонент FileStorageUploadField, привязанный к атрибуту сущности:

<data>
    <instance id="attachmentDc"
              class="files.ex1.entity.Attachment">
        <fetchPlan extends="_base"/>
        <loader/>
    </instance>
</data>
<layout spacing="true">
    <form dataContainer="attachmentDc">
        <column>
            <fileStorageUpload id="fileField" property="file"/>

Чтобы скачать прикрепленные файлы, добавьте в таблицу на экране браузера специальный столбец:

<groupTable id="attachmentsTable"
            width="100%"
            dataContainer="attachmentsDc">
    <columns>
        <column id="fileName" caption="File"/>

И определите генератор столбца:

@Autowired
private UiComponents uiComponents;
@Autowired
private Downloader downloader; (1)

@Install(to = "attachmentsTable.fileName", subject = "columnGenerator")
private Component attachmentsTableFileColumnGenerator(Attachment attachment) {
    if (attachment.getFile() != null) {
        LinkButton linkButton = uiComponents.create(LinkButton.class);
        linkButton.setAction(new BaseAction("download")
                .withCaption(attachment.getFile().getFileName())
                .withHandler(actionPerformedEvent ->
                        downloader.download(attachment.getFile()) (2)
                )
        );
        return linkButton;
    } else {
        return new Table.PlainTextCell("<empty>");
    }
}
1 Используйте бин Downloader для скачивания файлов.
2 Метод download() принимает значение FileRef и извлекает файл из хранилища файлов, указанного в объекте FileRef. Имя и тип файла также закодированы в FileRef, поэтому веб-браузер правильно выбирает, загружать или отображать файл.

Использование интерфейса FileStorage

В следующем примере показано, как работать напрямую с интерфейсом FileStorage.

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

Используется та же сущность Attachment, что и в предыдущем примере.

@Autowired
private FileStorageLocator fileStorageLocator; (1)
@Autowired
private DataManager dataManager;

private void getAndSaveImage() {
    try {
        (2)
        URLConnection connection = new URL("https://picsum.photos/300").openConnection();
        try (InputStream responseStream = connection.getInputStream()) {
            (3)
            FileStorage fileStorage = fileStorageLocator.getDefault();
            FileRef fileRef = fileStorage.saveStream("photo.jpg", responseStream);
            (4)
            Attachment attachment = dataManager.create(Attachment.class);
            attachment.setFile(fileRef);
            dataManager.save(attachment);
        }
    } catch (IOException e) {
        throw new RuntimeException("Error getting image", e);
    }
}

private void saveToLocalFile(Attachment attachment, Path path) {
    FileStorage fileStorage = fileStorageLocator.getDefault();
    FileRef fileRef = attachment.getFile();
    (5)
    InputStream inputStream = fileStorage.openStream(fileRef);
    try {
        (6)
        Files.copy(inputStream, path.resolve(fileRef.getFileName()),
                StandardCopyOption.REPLACE_EXISTING);
    } catch (IOException e) {
        throw new RuntimeException("Error saving image", e);
    }
}
1 FileStorageLocator позволяет работать с определенным хранилищем файлов, если вы определили в проекте несколько хранилищ. Если у вас одно хранилище файлов (что является ситуацией по умолчанию), интерфейс FileStorage можно внедрить напрямую.
2 Получение входного потока для веб-ресурса. Вместо класса URLConnection можно использовать HttpClient, представленный в Java 11, или стороннюю библиотеку, такую как Apache HttpClient.
3 Сохранение содержимого ресурса в хранилище файлов. Возвращаемый объект FileRef является ссылкой на файл в хранилище.
4 Сохранение ссылки на атрибут сущности.
5 Получение входного потока для загрузки файла из хранилища файлов.
6 Сохранение файла в локальной файловой системе.

Локальное хранилище файлов

Реализация локального хранилища позволяет хранить файлы в локальной файловой системе сервера приложения или в любом сетевом хранилище (NAS).

Чтобы использовать локальное хранилище файлов в своем приложении, убедитесь, что файл build.gradle содержит следующую строку в разделе dependencies:

implementation 'io.jmix.localfs:jmix-localfs-starter'

Файлы хранятся в специальной структуре каталогов, которая поддерживается файловым хранилищем. По умолчанию корневым каталогом является ${user.dir}/.jmix/work/filestorage, где ${user.dir} является рабочим каталогом пользователя (где была запущена JVM). Его можно изменить, указав нужный путь в свойстве приложения jmix.localfs.storage-dir, например:

jmix.localfs.storage-dir = /opt/file-storage

Хранилище файлов AWS

Реализация хранилища файлов AWS позволяет хранить файлы в Amazon S3.

Чтобы использовать в приложении хранилище файлов AWS, установите дополнение AWS File Storage из каталога, как описано в разделе Add-ons, или вручную добавьте следующую строку в раздел dependencies файла build.gradle:

implementation 'io.jmix.awsfs:jmix-awsfs-starter'

Если вы планируете использовать только хранилище файлов AWS, удалите зависимость локального хранилища файлов из build.gradle. В противном случае см. следующий раздел о том, как настроить несколько хранилищ файлов.

Определите свойства приложения:

jmix.awsfs.access-key = <access key ID>
jmix.awsfs.secret-access-key = <secret access key>
jmix.awsfs.region = <AWS region, for example eu-north-1>
jmix.awsfs.bucket = <S3 bucket name>
jmix.awsfs.chunk-size = <S3 multipart upload chunk size in KB, default is 8192>
jmix.awsfs.endpoint-url = <optional endpoint URL for S3-compatible cloud storages>

Использование нескольких хранилищ

Если вы хотите использовать в приложении несколько хранилищ файлов, укажите имя хранилища по умолчанию в свойстве приложения:

jmix.core.default-file-storage = fs

Имя локального хранилища файлов – fs; имя хранилища файлов AWS – s3.

Чтобы использовать несколько хранилищ одного типа, определите дополнительные хранилища с уникальными именами. Например, чтобы определить дополнительное локальное хранилище файлов с корневым каталогом в /var/tmp/myfs, добавьте следующий код в основной класс приложения:

@Bean
FileStorage myFileStorage() {
    return new LocalFileStorage("myfs", "/var/tmp/myfs");
}

Для программной работы с различными хранилищами используйте бин FileStorageLocator. Он позволяет получить файловое хранилище по его имени.

Компонент FileStorageUploadField имеет атрибут fileStorage для указания имени хранилища файлов. Если он не установлен, компонент использует файловое хранилище по умолчанию.