How to bind dynamic content using <p:media>? - jsf

I use the <p:media> to display static PDF content.
<p:media value="/resource/test.pdf"
width="100%" height="300px" player="pdf">
</p:media>
How can I change it to display dynamic content?

Like as in <p:graphicImage>, the value attribute can point to a bean property returning StreamedContent. This only requires a special getter method for the reasons which is explained in detail in the following answer on using <p:graphicImage> with a dynamic resource from a database: Display dynamic image from database with p:graphicImage and StreamedContent.
In your particular example, it would look like this:
<p:media value="#{mediaManager.stream}" width="100%" height="300px" player="pdf">
<f:param name="id" value="#{bean.mediaId}" />
</p:media>
With
#ManagedBean
#ApplicationScoped
public class MediaManager {
#EJB
private MediaService service;
public StreamedContent getStream() throws IOException {
FacesContext context = FacesContext.getCurrentInstance();
if (context.getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) {
// So, we're rendering the HTML. Return a stub StreamedContent so that it will generate right URL.
return new DefaultStreamedContent();
} else {
// So, browser is requesting the media. Return a real StreamedContent with the media bytes.
String id = context.getExternalContext().getRequestParameterMap().get("id");
Media media = service.find(Long.valueOf(id));
return new DefaultStreamedContent(new ByteArrayInputStream(media.getBytes()));
}
}
}

Related

Display p:fileupload image in p:graphicImage preview without saving it

I am using PrimeFaces 5.3 <p:fileUpload> to upload a PNG image and I would like to show a preview of it in <p:graphicImage> before saving in database.
Here's a MCVE:
<h:form enctype="multipart/form-data">
<p:fileUpload value="#{bean.uploadedFile}" mode="simple" />
<p:graphicImage value="#{bean.image}" />
<p:commandButton action="#{bean.preview}" ajax="false" value="Preview" />
</h:form>
private UploadedFile uploadedFile;
public UploadedFile getUploadedFile() {
return uploadedFile;
}
public void setUploadedFile(UploadedFile uploadedFile) {
this.uploadedFile = uploadedFile;
}
public void preview() {
// NOOP for now.
}
public StreamedContent getImage() {
if (uploadedFile == null) {
return new DefaultStreamedContent();
} else {
return new DefaultStreamedContent(new ByteArrayInputStream(uploadedFile.getContents()), "image/png");
}
}
No error occurring on the backing bean, and the image won't be load and display at front-end. The client mentions that the image returned a 404 not found error.
Your problem is two-fold. It failed because the uploaded file contents is request scoped and because the image is requested in a different HTTP request. To better understand the inner working, carefully read the answers on following closely related Q&A:
Display dynamic image from database with p:graphicImage and StreamedContent
How to choose the right bean scope?
To solve the first problem, you need to read the uploaded file contents immediately in the action method associated with the form submit. In your specific case, that would look like:
private UploadedFile uploadedFile;
private byte[] fileContents;
public void preview() {
fileContents = uploadedFile.getContents();
}
// ...
To solve the second problem, your best bet is to use the data URI scheme. This makes it possible to render the image directly in the very same response and therefore you can safely use a #ViewScoped bean without facing "context not active" issues or saving the byte[] in session or disk in order to enable serving the image in a different request. Browser support on data URI scheme is currently pretty good. Replace the entire <p:graphicImage> with below:
<ui:fragment rendered="#{not empty bean.uploadedFile}">
<img src="data:image/png;base64,#{bean.imageContentsAsBase64}" />
</ui:fragment>
public String getImageContentsAsBase64() {
return Base64.getEncoder().encodeToString(imageContents);
}
Note: I assume that Java 8 is available to you as java.util.Base64 was only introduced in that version. In case you're using an older Java version, use DatatypeConverter.printBase64Binary(imageContents) instead.
In case you happen to use JSF utility library OmniFaces, you can also just use its <o:graphicImage> component instead which is on contrary to <p:graphicImage> capable of directly referencing a byte[] and InputStream bean property and rendering a data URI.
<o:graphicImage value="#{bean.imageContents}" dataURI="true" rendered="#{not empty bean.imageContents}">

Passing a parameter to the method in each iteration of the loop [duplicate]

I was trying to display a panel where user can see a list of items category(displayed as images) and on clicking they can view products within the category(images will be displayed)
For displaying the item category, i used the ui:repeat nad the supporting bean calss
Below is my xhtml code
<ui:repeat id="repeat" value="#{getData.images}" var="img" varStatus="loop">
<h:panelGroup>
<p:graphicImage id="img1" value="#{img}" alt="image not available" >
</p:graphicImage>
</h:panelGroup>
</ui:repeat>
And the Managed Bean Code parts
private ByteArrayOutputStream baos = new ByteArrayOutputStream();
private List<StreamedContent> imageList = new ArrayList<StreamedContent>();
public List<StreamedContent> getImages(){
for (int i = 0; i < sdh.getNumOfImages(); i++) {
imageID = imageIDArray.get(i);
ImageService imgSer = new ImageService();
imgList.add(imageID);
imgSer.setData(imageID);
baos = imgSer.getImage();
try {
imageList.add(new DefaultStreamedContent(new
ByteArrayInputStream(baos.toByteArray())));
} catch (Exception ex) {
ex.printStackTrace();
}
}
imageNum = 0;
return imageList;
}
public StreamedContent getData() {
baos = imageList.get(imageNum);
//imageList.add(baos);
imageNum++;
return new DefaultStreamedContent(new ByteArrayInputStream(baos.toByteArray()));
}
Now my problem if i don't uncomment the 'imageList.add(baos)' in 'getData', the images are not displayed.
Now i really wants to know how the 'ui:repeat' works, since the 'imageList' contains the images and i can save the same if required in either of the method. If i specify a fixed number (ex:'imageList.get(0)') in the 'getData' method then the same image is show multiple times. Where as if i put the 'imageNum' without the 'imageList.add(baos)' it throw error 'Error in streaming dynamic resource'
I tired Bjorn Pollex's suggestion and made the necessary changes but now images don't appear
It is not possible to use <p:graphicImage> this way. You should rather iterate over a collection of unique image identifiers, not over a collection of StreamedContent. Those unique image identifiers have then to be passed as a <f:param> to <p:graphicImage> which in turn will generate the right URLs for the browser.
<ui:repeat value="#{data.imageIds}" var="imageId">
<p:graphicImage value="#{imageStreamer.image}">
<f:param name="id" value="#{imageId}" />
</p:graphicImage>
</ui:repeat>
Your #{data} managed bean must just have a:
private List<Long> imageIds; // +getter
The #{imageStreamer} should be a separate application scoped managed bean which look basically like this:
#ManagedBean
#ApplicationScoped
public class ImageStreamer {
#EJB
private ImageService service;
public StreamedContent getImage() throws IOException {
FacesContext context = FacesContext.getCurrentInstance();
if (context.getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) {
// So, we're rendering the view. Return a stub StreamedContent so that it will generate right URL.
return new DefaultStreamedContent();
}
else {
// So, browser is requesting the image. Get ID value from actual request param.
String id = context.getExternalContext().getRequestParameterMap().get("id");
Image image = service.find(Long.valueOf(id));
return new DefaultStreamedContent(new ByteArrayInputStream(image.getBytes()));
}
}
}
You used wrong ui:repeat tag. You have var attribute but you can't use this in p:graphicImage tag value attribute.Please see sample usage,
<ui:repeat value="#{yourBean.images}" var="img">
<p:graphicImage value="/images/#{img}" />
</ui:repeat>

StreamedContent created based on uploaded file is not found during preview

I created a fileupload dialog and a image gallery on a jsf page. After each image upload the gallery should show all so far uploaded images. The images will be stored in a backend bean and should be fetched by the gallery dynamically from the backend bean. For some reason the gallery shows the image labels uploaded but not the referring image since the image resource could not be found.
I use spring, primefaces on tomcat. Thanks for any help in advance!
My JSF Page:
<p:fileUpload id="imageUpldoad" update="galleryPanel" fileUploadListener="#{wizzardBean.handleFileUpload}" mode="advanced" dragDropSupport="true"
sizeLimit="10000000" multiple="true" auto="false" fileLimit="100" allowTypes="/(\.|\/)(gif|jpe?g|png)$/" />
<p:panel id="galleryPanel">
<p:galleria id="gallery" value="#{wizzardBean.getHotelImages()}" var="img" panelWidth="500" panelHeight="313" showCaption="true" rendered="#{wizzardBean.showGallery()}">
<p:graphicImage name="#{img.name}" value="#{wizzardBean.hotelImage}" alt="Image Description for #{img.name}" title="#{img}">
<f:param id="imgId" name="imgId" value="#{img.id}" />
</p:graphicImage>
</p:galleria>
My Backend Bean:
public class WizzardBean extends BaseBean {
private List<HotelImage> hotelImages;
public void handleFileUpload(FileUploadEvent event) throws IOException {
if (event.getFile() != null) {
HotelImage hotelImage = new HotelImage(hotelImages.size(), event.getFile().getFileName(), event.getFile());
hotelImages.add(hotelImage);
}
}
public StreamedContent getHotelImage() {
ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
String photoId = externalContext.getRequestParameterMap().get("imgId");
if (photoId == null || photoId.equals("")) {
return null;
} else {
int parsedId = Integer.parseInt(photoId);
return hotelImages.get(parsedId).getImage();
}
}
}
The HotelImage class:
public class HotelImage {
private int id;
private String name;
private StreamedContent image;
public HotelImage(int id, String name, UploadedFile file) {
this.id = id;
this.name = name;
try {
image = new DefaultStreamedContent(file.getInputstream(), "image/jpg");
} catch (IOException e) {
}
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public StreamedContent getImage() {
return image;
}
The browser says:
<img id="mainFormId:j_idt52:j_idt55" src="RES_NOT_FOUND" alt="Image Description for twitter.png" title="twitter.png">
There are several problems with this approach. Those boil down to the incorrect assumption that an uploaded file, an InputStream and StreamedContent can be read and reused multiple times. This is incorrect. The uploaded file will be sent only once and only be available during the original request, and the streams can be read only once after they are created.
Moreover, having a PrimeFaces-specific StreamedContent or even UploadedFile as a bean property is wrong. The bean property should at least be a File referring the physical file on server's local disk file system, or a byte[] representing the raw content in server memory or a Long representing the insert ID of blob entry in database.
You need to adjust your code to save the uploaded file content to a permanent storage location as soon as possible it comes in and then assign the result as a bean property of type File or byte[] or Long. Then, let the rest of the code use that bean property instead to create a StreamedContent within the getter method. Do absolutely not assign that StreamedContent to another bean property.
You can find concrete and elaborate examples in the answer of the questions linked below.
How to save uploaded file in JSF
Display dynamic image from database or remote source with p:graphicImage and StreamedContent

How to use Streamed Content with p:fileDownload to Download Non class path File

I'm using Primefaces
p:fileDownload
to download a file which is not in class path.
So I'm passing FileInputStream as parameter to DefaultStreamedContent.
Every thing works fine when my bean is kept at #SessionScoped...,
But
java.io.NotSerializableException: java.io.FileInputStream
is thrown when I keep my bean in #Viewscoped.
My Code:
DownloadBean.java
#ManagedBean
#ViewScoped
public class DownloadBean implements Serializable {
private StreamedContent dFile;
public StreamedContent getdFile() {
return dFile;
}
public void setdFile(StreamedContent dFile) {
this.dFile = dFile;
}
/**
* This Method will be called when download link is clicked
*/
public void downloadAction()
{
File tempFile = new File("C:/temp.txt");
try {
dFile = new DefaultStreamedContent(new FileInputStream(tempFile), new MimetypesFileTypeMap().getContentType(tempFile));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
index.xhtml
<h:form>
<h:commandLink action="#{downloadBean.downloadAction}">
Download
<p:fileDownload value="#{downloadBean.dFile}"/>
</h:commandLink>
</h:form>
Isn't there any method to make it work?
The NotSerializableException is thrown because the view scope is represented by the JSF view state which can in turn be serialized to HTTP session in case of server side state saving or a HTML hidden input field in case of client side state saving. The FileInputStream can in no way be represented in a serialized form.
If you absolutely need to keep the bean view scoped, then you should not be declaring StreamedContent as an instance variable, but instead recreate it in the getter method. True, doing business logic in a getter method is usually frowned upon, but the StreamedContent is a rather special case. In the action method, you should then only prepare serializable variables which are later to be used during DefaultStreamedContent construction.
#ManagedBean
#ViewScoped
public class DownloadBean implements Serializable {
private String path;
private String contentType;
public void downloadAction() {
path = "C:/temp.txt";
contentType = FacesContext.getCurrentInstance().getExternalContext().getMimeType(path);
}
public StreamedContent getdFile() throws IOException {
return new DefaultStreamedContent(new FileInputStream(path), contentType);
}
}
(note that I also fixed your way to get the content type; you have this way much more freedom to configure mime types via <mime-mapping> entries in web.xml)
The <p:graphicImage> has by the way exactly the same problem with StreamedContent. See also among others Display dynamic image from database with p:graphicImage and StreamedContent.
#BalusC, for p:fileDownload, is there a way to offload the creation of the StreamedContent to another object which could then be called directly from JSF? Similar to the way you offload p:graphicImage here. If so what would be the scope of this special object? I'm guessing RequestScoped since there would be no connection between initDownload and getDownload. ApplicationScoped would not be able to keep track of all downloads within a single session, right? I also wonder if creating a new Apache FOP object in every Request is too expensive?
Here's an example:
jsf:
<h:commandButton value="print/download" action="#{streamhelper.initDownload()}">
<p:fileDownload value="#{streamhelper.download}"/>
<f:param name="html" value="#{bean.html}" />
<f:param name="idNum" value="#{bean.idNum}" />
</h:commandButton>
special object:
#Named("streamhelper") #RequestScoped #Getter #Setter #Slf4j
public class StreamedContentHelper
{
#PostConstruct #SneakyThrows({NamingException.class})
public void init(){
fop = util.getLocator().getObject(util.getLocator().prependPortableName(FOPEngineImpl.class.getSimpleName()));
}
public void initDownload() throws Exception
{
FacesContext context = FacesContext.getCurrentInstance();
log.trace("context PhaseID: {}", context.getCurrentPhaseId());
String html = context.getExternalContext().getRequestParameterMap().get("html");
String idNum = context.getExternalContext().getRequestParameterMap().get("idNum");
byte[] attachBytes = fop.getPDFBytes(html);
InputStream stream = new ByteArrayInputStream(attachBytes);
stream.mark(0); //remember to this position!
String filename = String.format("%s-download.pdf", loadNum);
download = new DefaultStreamedContent(stream, "application/pdf", filename);
}
private StreamedContent download;
private FOPEngineLocal fop;
private #Inject Util util;
}

How to use <p:graphicImage> with DefaultStreamedContent in an ui:repeat?

I was trying to display a panel where user can see a list of items category(displayed as images) and on clicking they can view products within the category(images will be displayed)
For displaying the item category, i used the ui:repeat nad the supporting bean calss
Below is my xhtml code
<ui:repeat id="repeat" value="#{getData.images}" var="img" varStatus="loop">
<h:panelGroup>
<p:graphicImage id="img1" value="#{img}" alt="image not available" >
</p:graphicImage>
</h:panelGroup>
</ui:repeat>
And the Managed Bean Code parts
private ByteArrayOutputStream baos = new ByteArrayOutputStream();
private List<StreamedContent> imageList = new ArrayList<StreamedContent>();
public List<StreamedContent> getImages(){
for (int i = 0; i < sdh.getNumOfImages(); i++) {
imageID = imageIDArray.get(i);
ImageService imgSer = new ImageService();
imgList.add(imageID);
imgSer.setData(imageID);
baos = imgSer.getImage();
try {
imageList.add(new DefaultStreamedContent(new
ByteArrayInputStream(baos.toByteArray())));
} catch (Exception ex) {
ex.printStackTrace();
}
}
imageNum = 0;
return imageList;
}
public StreamedContent getData() {
baos = imageList.get(imageNum);
//imageList.add(baos);
imageNum++;
return new DefaultStreamedContent(new ByteArrayInputStream(baos.toByteArray()));
}
Now my problem if i don't uncomment the 'imageList.add(baos)' in 'getData', the images are not displayed.
Now i really wants to know how the 'ui:repeat' works, since the 'imageList' contains the images and i can save the same if required in either of the method. If i specify a fixed number (ex:'imageList.get(0)') in the 'getData' method then the same image is show multiple times. Where as if i put the 'imageNum' without the 'imageList.add(baos)' it throw error 'Error in streaming dynamic resource'
I tired Bjorn Pollex's suggestion and made the necessary changes but now images don't appear
It is not possible to use <p:graphicImage> this way. You should rather iterate over a collection of unique image identifiers, not over a collection of StreamedContent. Those unique image identifiers have then to be passed as a <f:param> to <p:graphicImage> which in turn will generate the right URLs for the browser.
<ui:repeat value="#{data.imageIds}" var="imageId">
<p:graphicImage value="#{imageStreamer.image}">
<f:param name="id" value="#{imageId}" />
</p:graphicImage>
</ui:repeat>
Your #{data} managed bean must just have a:
private List<Long> imageIds; // +getter
The #{imageStreamer} should be a separate application scoped managed bean which look basically like this:
#ManagedBean
#ApplicationScoped
public class ImageStreamer {
#EJB
private ImageService service;
public StreamedContent getImage() throws IOException {
FacesContext context = FacesContext.getCurrentInstance();
if (context.getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) {
// So, we're rendering the view. Return a stub StreamedContent so that it will generate right URL.
return new DefaultStreamedContent();
}
else {
// So, browser is requesting the image. Get ID value from actual request param.
String id = context.getExternalContext().getRequestParameterMap().get("id");
Image image = service.find(Long.valueOf(id));
return new DefaultStreamedContent(new ByteArrayInputStream(image.getBytes()));
}
}
}
You used wrong ui:repeat tag. You have var attribute but you can't use this in p:graphicImage tag value attribute.Please see sample usage,
<ui:repeat value="#{yourBean.images}" var="img">
<p:graphicImage value="/images/#{img}" />
</ui:repeat>

Resources