Рассмотрим задачу загрузки, хранения и отображения фотографий сотрудников:
-
Сотрудник представлен сущностью
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; } }
Фрагмент XML-дескриптора экрана редактирования Employee
:
<groupBox caption="Photo" spacing="true" height="250px" width="250px" expand="embeddedImage"> <embedded id="embeddedImage" width="100%" align="MIDDLE_CENTER"/> <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. В верхней его части с помощью компонента embedded выводится изображение, а в нижней слева направо расположены компонент upload для загрузки файла и кнопки выгрузки и очистки изображения. В результате эта часть экрана должна выглядеть следующим образом:
Теперь рассмотрим контроллер экрана редактирования.
public class EmployeeEdit extends AbstractEditor<Employee> { private Log log = LogFactory.getLog(EmployeeEdit.class); @Inject private DataSupplier dataSupplier; @Inject private FileStorageService fileStorageService; @Inject private FileUploadingAPI fileUploading; @Inject private ExportDisplay exportDisplay; @Inject private Embedded embeddedImage; @Inject private FileUploadField uploadField; @Inject private Button downloadImageBtn; @Inject private Button clearImageBtn; @Inject private Datasource<Employee> employeeDs; private static final int IMG_HEIGHT = 190; private static final int IMG_WIDTH = 220; @Override public void init(Map<String, Object> params) { uploadField.addListener(new FileUploadField.ListenerAdapter() { @Override public void uploadSucceeded(Event event) { FileDescriptor fd = uploadField.getFileDescriptor(); try { fileUploading.putFileIntoStorage(uploadField.getFileId(), fd); } catch (FileStorageException e) { throw new RuntimeException(e); } getItem().setImageFile(dataSupplier.commit(fd, null)); displayImage(); } @Override public void uploadFailed(Event event) { showNotification("Upload failed", NotificationType.HUMANIZED); } }); employeeDs.addListener(new DsListenerAdapter<Employee>() { @Override public void valueChanged(Employee source, String property, @Nullable Object prevValue, @Nullable Object value) { if ("imageFile".equals(property)) { updateImageButtons(value != null); } } }); } @Override protected void postInit() { displayImage(); updateImageButtons(getItem().getImageFile() != null); } public void onDownloadImageBtnClick(Component source) { if (getItem().getImageFile() != null) exportDisplay.show(getItem().getImageFile(), ExportFormat.OCTET_STREAM); } public void onClearImageBtnClick(Component source) { getItem().setImageFile(null); displayImage(); } private void updateImageButtons(boolean enable) { downloadImageBtn.setEnabled(enable); clearImageBtn.setEnabled(enable); } private void displayImage() { byte[] bytes = null; if (getItem().getImageFile() != null) { try { bytes = fileStorageService.loadFile(getItem().getImageFile()); } catch (FileStorageException e) { log.error("Unable to load image file", e); showNotification("Unable to load image file", NotificationType.HUMANIZED); } } if (bytes != null) { embeddedImage.setSource(getItem().getImageFile().getName(), new ByteArrayInputStream(bytes)); embeddedImage.setType(Embedded.Type.IMAGE); BufferedImage image; try { image = ImageIO.read(new ByteArrayInputStream(bytes)); int width = image.getWidth(); int height = image.getHeight(); if (((double) height / (double) width) > ((double) IMG_HEIGHT / (double) IMG_WIDTH)) { embeddedImage.setHeight(String.valueOf(IMG_HEIGHT)); embeddedImage.setWidth(String.valueOf(width * IMG_HEIGHT / height)); } else { embeddedImage.setWidth(String.valueOf(IMG_WIDTH)); embeddedImage.setHeight(String.valueOf(height * IMG_WIDTH / width)); } } catch (IOException e) { log.error("Unable to resize image", e); } // refresh image embeddedImage.setVisible(false); embeddedImage.setVisible(true); } else { embeddedImage.setVisible(false); } } }
-
В методе
init()
сначала инициализируется компонентuploadField
, предназначенный для загрузки новой фотографии. В случае успешной загрузки из компонента получается экземпляр новогоFileDescriptor
, и соответствующий файл отправляется из временного хранилища в постоянное вызовомFileUploadingAPI.putFileIntoStorage()
. После этогоFileDescriptor
сохраняется в БД вызовом DataSupplier.commit(), и сохраненный экземпляр устанавливается в атрибутеimageFile
редактируемой сущностиEmployee
. Затем вызывается методdisplayImage()
контроллера для отображения загруженной фотографии.Далее в методе
init()
источнику данных, содержащему редактируемый экземплярEmployee
, добавляется слушатель для запрещения или разрешения кнопок выгрузки и очистки файла в зависимости от того, загружен файл или нет. -
Метод
postInit()
вызывает отображение файла и обновляет состояние кнопок в зависимости от наличия загруженного файла. -
Метод
onDownloadImageBtnClick()
вызывается при нажатии кнопкиdownloadImageBtn
и выполняет выгрузку файла с помощью интерфейса ExportDisplay. -
Метод
onClearImageBtnClick()
вызывается при нажатии кнопкиclearImageBtn
и очищает атрибутimageFile
сущностиEmployee
. Удаления файла из хранилища не производится. -
Метод
displayImage()
выгружает файл из хранилища в байтовый массив, устанавливает содержимое компонентаembeddedImage
, и перерасчитывает его размеры для сохранения пропорций изображения.Следует иметь в виду, что выгрузка файлов из хранилища в байтовый массив приемлема только для небольших файлов. Если размер файла непредсказуем, следует использовать только выгрузку через ExportDisplay, при которой файл передается через потоки ввода-вывода и нигде не оказывается в памяти целиком.