Using File Storage

File Storage is an abstraction enabling different implementations of how and where the files are stored and providing a unified interface for accessing files and referring to them from the data model entities.

Jmix comes with two file storage implementations: Local and AWS. When you create a new project in Studio, it includes the local implementation.

You can store files of any size in the file storage, because transferring files to and from the file storage is performed by copying small chunks of data between input and output streams, so files are never fully loaded into memory.

Example

In this section, we will give an example of working with files in the file storage using the Backoffice UI components.

Firstly, create an attribute of the FileRef type in your entity, for example:

@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;
    }

When you run the application, Studio generates a database migration script for creating a corresponding column of the string type, because FileRef has a string representation in the URI format.

For uploading files from a Backoffice UI screen, use the FileStorageUploadField component bound to the entity attribute:

<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"/>

To download attached files, add a custom column to the table in the browser screen:

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

And define the column generator:

@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 Use the Downloader bean to download files.
2 The download() method accepts the FileRef value and takes the file from the file storage specified in the FileRef object. The name and type of the file are also encoded in FileRef, so the web browser correctly chooses whether to download or display the file.

Local File Storage

The Local File Storage implementation allows you to store files on the local file system of the application server or on any network-attached storage (NAS).

To use Local File Storage in your application, make sure your build.gradle file contains the following line in the dependencies section:

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

Files are stored in a special directory structure which is maintained by the file storage. By default, the root directory is ${user.dir}/.jmix/work/filestorage, where ${user.dir} is the user working directory (where JVM was started). You can change it by specifying a path in the jmix.localfs.storageDir application property, for example:

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

AWS File Storage

The AWS File Storage implementation allows you to store files in Amazon S3.

To use AWS File Storage in your application, install the AWS File Storage add-on from the marketplace as described in the Add-ons section, or manually add the following line to the dependencies section of your build.gradle file:

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

If you plan to use AWS File Storage only, remove the Local File Storage dependency from build.gradle. Otherwise, see the next section for how to configure multiple file storages.

Define application properties:

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

Using Multiple File Storages

If you want to use more than one file storage in your application, specify a default one using its name in the application property:

jmix.core.defaultFileStorage = fs

The Local File Storage name is fs, the AWS File Storage name is s3.

If you want to use multiple storages of the same type, define additional storages with unique names. For example, to define an additional Local File Storage with the root directory at /var/tmp/myfs, add the following code to the main application class:

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

To work with different file storages programmatically, use the FileStorageLocator bean. It allows you to get a file storage by its name.

The FileStorageUploadField component has the fileStorage attribute to specify a file storage name. If it’s not set, the component uses the default file storage.