4.5.2. Загрузка и вывод изображений

Рассмотрим задачу загрузки, хранения и отображения фотографий сотрудников:

  • Сотрудник представлен сущностью Employee.

  • Файлы изображений хранятся в FileStorage. Сущность Employee содержит ссылку на соответствующий FileDescriptor.

  • Экран редактирования Employee отображает фотографию, а также дает возможность загрузить, выгрузить и очистить изображение.

Класс сущности со ссылкой на файл изображения:

@Table(name = "SAMPLE_EMPLOYEE")
@Entity(name = "sample$Employee")
public class Employee extends StandardEntity {
...
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "IMAGE_FILE_ID")
    protected FileDescriptor imageFile;

    public void setImageFile(FileDescriptor imageFile) {
        this.imageFile = imageFile;
    }

    public FileDescriptor getImageFile() {
        return imageFile;
    }
}

Представление для загрузки Employee вместе с FileDescriptor должно содержать все локальные атрибуты FileDescriptor:

<view class="com.company.sample.entity.Employee"
      name="employee-edit">
    <property name="name"/>
    ...
    <property name="imageFile"
              view="_local">
    </property>
</view>

Фрагмент XML-дескриптора экрана редактирования Employee:

<groupBox caption="Photo" spacing="true"
          height="250px" width="250px" expand="image">
    <image id="image"
           width="100%"
           align="MIDDLE_CENTER"
           scaleMode="CONTAIN"/>
    <hbox align="BOTTOM_LEFT"
          spacing="true">
        <upload id="uploadField"/>
        <button id="downloadImageBtn"
                caption="Download"
                invoke="onDownloadImageBtnClick"/>
        <button id="clearImageBtn"
                caption="Clear"
                invoke="onClearImageBtnClick"/>
    </hbox>
</groupBox>

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

images recipe
import com.haulmont.cuba.core.entity.FileDescriptor;
import com.haulmont.cuba.core.global.FileStorageException;
import com.haulmont.cuba.gui.components.*;
import com.company.employeeimages.entity.Employee;
import com.haulmont.cuba.gui.data.DataSupplier;
import com.haulmont.cuba.gui.data.Datasource;
import com.haulmont.cuba.gui.export.ExportDisplay;
import com.haulmont.cuba.gui.export.ExportFormat;
import com.haulmont.cuba.gui.upload.FileUploadingAPI;

import javax.inject.Inject;
import java.util.Map;

public class EmployeeEdit extends AbstractEditor<Employee> {

    @Inject
    private DataSupplier dataSupplier;
    @Inject
    private FileUploadingAPI fileUploadingAPI;
    @Inject
    private ExportDisplay exportDisplay;
    @Inject
    private FileUploadField uploadField;
    @Inject
    private Button downloadImageBtn;
    @Inject
    private Button clearImageBtn;
    @Inject
    private Datasource<Employee> employeeDs;

    @Inject
    private Image image;

    @Override
    public void init(Map<String, Object> params) {
        uploadField.addFileUploadSucceedListener(event -> {
            FileDescriptor fd = uploadField.getFileDescriptor();
            try {
                fileUploadingAPI.putFileIntoStorage(uploadField.getFileId(), fd);
            } catch (FileStorageException e) {
                throw new RuntimeException("Error saving file to FileStorage", e);
            }
            getItem().setImageFile(dataSupplier.commit(fd));
            displayImage();
        });

        uploadField.addFileUploadErrorListener(event ->
                showNotification("File upload error", NotificationType.HUMANIZED));

        employeeDs.addItemPropertyChangeListener(event -> {
            if ("imageFile".equals(event.getProperty()))
                updateImageButtons(event.getValue() != null);
        });
    }

    @Override
    protected void postInit() {
        displayImage();
        updateImageButtons(getItem().getImageFile() != null);
    }

    public void onDownloadImageBtnClick() {
        if (getItem().getImageFile() != null)
            exportDisplay.show(getItem().getImageFile(), ExportFormat.OCTET_STREAM);
    }

    public void onClearImageBtnClick() {
        getItem().setImageFile(null);
        displayImage();
    }

    private void updateImageButtons(boolean enable) {
        downloadImageBtn.setEnabled(enable);
        clearImageBtn.setEnabled(enable);
    }

    private void displayImage() {
        if (getItem().getImageFile() != null) {
            image.setSource(FileDescriptorResource.class).setFileDescriptor(getItem().getImageFile());
            image.setVisible(true);
        } else {
            image.setVisible(false);
        }
    }
}
  • В методе init() сначала инициализируется компонент uploadField, предназначенный для загрузки новой фотографии. В случае успешной загрузки из компонента получается экземпляр нового FileDescriptor, и соответствующий файл отправляется из временного хранилища в постоянное вызовом FileUploadingAPI.putFileIntoStorage(). После этого FileDescriptor сохраняется в БД вызовом DataSupplier.commit(), и сохраненный экземпляр устанавливается в атрибуте imageFile редактируемой сущности Employee. Затем вызывается метод displayImage() контроллера для отображения загруженной фотографии.

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

  • Метод postInit() вызывает отображение файла и обновляет состояние кнопок в зависимости от наличия загруженного файла.

  • Метод onDownloadImageBtnClick() вызывается при нажатии кнопки downloadImageBtn и выполняет выгрузку файла с помощью интерфейса ExportDisplay.

  • Метод onClearImageBtnClick() вызывается при нажатии кнопки clearImageBtn и очищает атрибут imageFile сущности Employee. Удаления файла из хранилища не производится.

  • Метод displayImage() выгружает файл из хранилища и устанавливает его в качестве содержимого компонента image.