Содержание:
0. Что важно знать o StreamedContent
1. Скачивание файлов через REST
2. Проверка расширения файла
3. Сохранение в базу и скачивание из базы
4. Отображение картинки в jsf
5. Добавление кнопки для скачивания файла
6. Добавление кнопки для загрузки данных
Это общая статья с различными примерами использования StreamedContent’a: как скачивать, как загружать и как пользоваться
В этой части статьи я расскажу о работе со treamedContent’ом на стороне бэкенда. В этой статье нет подробного описания, как работает rest и базы данных, а только то, что непосредственно относится к работам со StreamedContent.
Для того, чтобы прочитать касающиеся фронтенда стоит перейти на вторую часть этой статьи:
https://www.tune-it.ru/web/marina/blog/-/blogs/streameddata-frontend
0. Что важно знать
StreamedContent - это потоковый формат данных, для загрузки различных файлов и других потоковых данных. StreamedContent используется в primefaces для того, чтобы работать с файлами, отображать изображения, загружать и скачивать различные данные.
О чтении из StreamedContent
Потоковые данные в java хранятся и передаются при помощи StreamedContent. Этот класс необходимую информацию о файле или других данных потокового типа. В числе этих данных - название, тип, кодировка и размер файла и поток данных типом InputStream.
public interface StreamedContent {
String getName();
InputStream getStream();
String getContentType();
String getContentEncoding();
Integer getContentLength();
}
Самое главное, что нужно знать при работе со StreamedContent - вычитать байты из InputStream мы можем только один раз. При необходимости использования данных несколько раз, можно сохранить данные в массив байтов и потом создавать из него новые стримы.
Для того, чтобы узнать, были ли вычитаны данные из стрима, в InputStream хранится количество доступных байт, получить его можно, воспользовавшись методом available() в Inputstream, а полное количество байт - в сontentLength у StreamedContent.
StreamedContent stream = getStream(); // допустим, у нас есть какой-то стрим
stream.getContentLength(); // общее количество байтов в стриме
stream.getStream().available(); // количество доступных (невычитанных) байт
1. Скачивание файлов через REST
В общем-то получить картинку/файл можно точно также, как и текстовый ответ или json, только в этом случае нужно получить общий ответ в виде ResponseBody и из него взять StreamedContent.
В своем примере я пользуюсь retrofit. Я написала метод для получения фотографии пользователя, который возвращает ResponseBody:
@GET("api/v/user/{id}/photo")
Call<ResponseBody> getUserPhoto(
@Path("id") String userId
);
В ResponseBody хранится вся необходимая нам информация о файле: тип контента, размер и данные.
public abstract MediaType contentType();
public abstract long contentLength();
public final InputStream byteStream();
Как оговаривалось раньше, получить данные из byteStream() можно лишь единожды, но, если необходимо использовать эти данные несколько раз, то нужно сохранить данные в массив байтов.
Ниже указан пример сохранения данных в переменную:
// Получаем файл
ResponceBody res = getUserPhoto(id);
// Получаем размер файла
int available = (int) res.contentLength();
// Создаем массив и выделяем память
byte[] bytes = new byte[available];
// Записываем байты в массив
IOUtils.readFully(res.byteStream(), bytes);
После выполнения этого кода в res.byteStream() ничего не останется.
Для того, чтобы снова создать из массива байтов стрим (например, чтобы передать данные дальше или отобразить фотографию) нужно создать новый стрим.
Воспользуемся builder’om в DefaultStreamedContent для создания стрима:
StreamedContent stream = DefaultStreamedContent.builder()
.stream(() -> new ByteArrayInputStream(bytes))
.contentLength((int) res.contentLength())
.contentType(StringUtil.safeToString(res.contentType()))
.build();
2. Проверка расширения файла
В некоторых случаях необходимо знать расширение файла, например, чтобы провести проверку по типу или для реализации разной логики для разных типов файлов.
Для того, чтобы проверить тип файла есть метод:
String mimeType = URLConnection.guessContentTypeFromStream(stream);
Пример ответа: “image/jpg”
guessContentTypeFromStream отдает строковое описание типа файла.
Чаще всего, необходимых типов файлов больше, чем один. Когда мы ожидаем получить изображение, оно может быть сразу нескольких типов, например, одновременно gif, jpg, jpeg и png
Для того, чтобы проверить, имеет ли файл одно из необходимых расширений можно проверить тип с использованием regex-паттернов. Для этого можно написать regex-правило, по которому будет проверятся, подходит ли нам данный файл.
Продолжим пример с изображением. В этом случае regex-правило будет выглядеть вот так:
".*(gif|jpe?g|png)$" -- паттерн для фотографий типа gif, jpg, jpeg и png
Дальше дело за малым. Проверяем, подойдет ли тип под наше правило.
Pattern p = Pattern.compile(".*(gif|jpe?g|png)$"); // Создать паттерн
if (!p.matcher(mimeType).matches()) { // проверить на совпадение
log.error("Incorrect file type");
return null;
}
3. Сохранение файлов в базу и скачивание их из базы
Способов сохранить файл в базу множество. Поэтому расскажу тут один из возможных способов. Создадим таблицу для хранения файла.
create table file
(
file_name varchar(255),
file_media_type varchar(255),
file_content bytea
);
Сущность, для описания этого файла
public class Document {
private String name;
private String mediaType;
private byte[] content;
}
И репозиторий, для взаимодействия с базой
public interface FileRepository extends CrudRepository<File, Long>
{
}
Отличия в загрузки в базу данных обычных данных от StreamedContent’a нет. Разница только лишь в типе сохраняемых данных.
fileRepository.save(file); // сохранение файла в базу
fileRepository.findAll(); // скачивание всех файлов
fileRepository.findByName(fileName); // cкачивание конкретного файла
Заключение
На этом закончена часть работы с файлами на бэкенде. Здесь мы научились скачивать файлы по рест апи, сохранять файлы и вынимать из базы данных, работать с расширением файла. В следующей части будет описана работа со StreamedContent на стороне фронта с использованием primefaces. [ссылка]
См. также
2 часть статьи:
https://www.tune-it.ru/web/marina/blog/-/blogs/streameddata-frontend
Документация:
https://www.primefaces.org/docs/api/8.0/org/primefaces/model/StreamedContent.html