1

Greetings to everyone,

I am using primefaces 4 and Tomcat 7. I want users to be able to upload multiple images and see each uploaded image instantly (while they are in memory), before these are written to the disk. The images will only be written in the disk after form submission. I am using p:fileUpload component.

Here is the relevant code:

...
<p:tab id="imageTab" title="#{msgs.images}">

    <p:dataGrid id="imagesDataGrid" columns="4" value="#{modifyProductAdminBean.imageIds}" 
                                    var="imgId" >
        <p:graphicImage value="#{pA_ImageService.image}" >
            <f:param name="id" value="#{imgId}" />
        </p:graphicImage>    
    </p:dataGrid>

    <p:fileUpload fileUploadListener="#{modifyProductAdminBean.handleFileUpload}" mode="advanced"
        dragDropSupport="true" multiple="true" sizeLimit="5242880"
        invalidFileMessage="#{msgs.invalidFileType}"
        invalidSizeMessage="#{msgs.fileTooLarge}"
        allowTypes="/(\.|\/)(gif|jpe?g|png|jpg)$/"
        cancelLabel="#{msgs.cancel}"
        uploadLabel="#{msgs.upload}"
        label="#{msgs.choose}"
        update="imagesDataGrid" />
</p:tab>
...


@ManagedBean
@ViewScoped
public class ModifyProductAdminBean implements Serializable {
    private Map<String, UploadedFile> uploadedImages;

    public void handleFileUpload(FileUploadEvent event) {
        UploadedFile file = event.getFile(); 
        String uniqueId = UUID.randomUUID().toString();
        this.getUploadedImages().put(uniqueId, file);
    }

    public Set<String> getImageIds() {
        return this.getUploadedImages().keySet();
    }

    public Map<String, UploadedFile> getUploadedImages() {
        return uploadedImages;
    }
    ...
}


@ManagedBean
@ApplicationScoped
public class PA_ImageService implements Serializable {

    private final ModifyProductAdminBean modifyProductAdminBean;

    public PA_ImageService() {
        this.modifyProductAdminBean = BeanManager.findBean("modifyProductAdminBean");
    }

    // Taken from http://stackoverflow.com/questions/8207325/display-image-from-database-with-pgraphicimage
    public StreamedContent getImage() {
        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 image. Return a real StreamedContent with the image bytes.
            String imageId = context.getExternalContext().getRequestParameterMap().get("id");
            // remove [, ] characters between
            imageId = imageId.substring(1, imageId.length() - 1);
            UploadedFile uFile = this.modifyProductAdminBean.getUploadedImages().get(imageId);
            return new DefaultStreamedContent(new ByteArrayInputStream(uFile.getContents()));
        }
    }

    ...
}


public class BeanManager implements Serializable {
    @SuppressWarnings("unchecked")
    public static <T> T findBean(String beanName) {
        FacesContext context = FacesContext.getCurrentInstance();
        return (T) context.getApplication().evaluateExpressionGet(context, "#{" + beanName + "}", Object.class);
    }
    ...
}

When I run this code I get a NullPointerException at the last line of “PA_ImageService” (return new ...). More precisely, although uFile is not null “uFile.getContents()” returns null. Why? What am I doing wrong?


More details that I observed:

I noticed that when I upload a file, Tomcat stores it temporarily inside E:\Program Files (x86)\Apache Software Foundation\Apache Tomcat 7.0.41\work\Catalina\localhost\MyProject directory in a .tmp file.

By debugging the project, I can see that: When I reach the if (context... == PhaseId.RENDER_RESPONSE) line of PA_ImageService, the .tmp file still exists. However, in the second access of getImage() method, when the control moves to the else block, I can see that the tmp file no longer exists. Therefore, its contents cannot be retrieved and hence the null result.

Any ideas of how this is happening?

GP_
  • 114
  • 1
  • 8
  • I have added additional observations at the end of this question. Please take a look and let me know if you can help! Thanks – GP_ Dec 04 '13 at 10:56

2 Answers2

1

You need to store the image in a (temporary) disk/DB location instead of as a property of a view scoped bean. You can maybe store it as a property of a session scoped bean, but I wouldn't recommend carrying memory consuming bytes around in the HTTP session, this hurts when it need to get serialized in e.g. a server cluster.

You can easily use File#renameTo() to move from temporary location to a fixed location. You can easily run a session listener to reap/cleanup any user-associated temporary files.

BalusC
  • 992,635
  • 352
  • 3,478
  • 3,452
  • Thanks for the reply. I am using Netbeans and when I debug the project I see that `this.modifyProductAdminBean = BeanManager.findBean("modifyProductAdminBean")` returns a bean which has exactly the same value as the intended view scoped bean. This suggests to me that it returns the intended view scoped bean that I want. Correct me if I am wrong please. – GP_ Dec 04 '13 at 12:04
  • Right, it'll work if the application scoped bean is created for the first time in the same view, but it surely won't work for other/subsequent views as the applciation scoped bean is already created. I removed the not-so-relevant-anymore part form the answer. – BalusC Dec 04 '13 at 12:07
  • Also I just found your answer in this [question](http://stackoverflow.com/questions/18422268/file-not-found-exception-for-temporary-files) where you also recommended the use of `bytes[]` but here you do not recommend it right? – GP_ Dec 04 '13 at 12:07
  • I do not recommend storing it in the session (as a session scoped bean property which would theoretically be the other solution). That's all. – BalusC Dec 04 '13 at 12:07
  • Ok thank you very much. I will change my code to store it in a temporary location. – GP_ Dec 04 '13 at 12:09
0

The most glaring problem here is the fact that you're attempting to access a @ViewScoped bean from within an @ApplicationScoped bean; That's illegal in JSF.

You're allowed to inject beans only of a broader scope than the scope of the injection target. That means you can inject beans in the following order of scopes:

ApplicationScope >> SessionScope >> ViewScope >> RequestScope

That being said, while I can't see how you're injecting ModifyProductAdminBean into PA_ImageService (no annotations or faces-config.xml visible), it's safe to say that the following line should not work

UploadedFile uFile = this.modifyProductAdminBean.getUploadedImages().get(imageId);
kolossus
  • 19,953
  • 3
  • 45
  • 94
  • I have updated my question to show how I inject `ModifyProductAdminBean`. I made `PA_ImageService` an `ApplicationScoped` bean as it is suggested [here](http://stackoverflow.com/questions/8207325/display-image-from-database-with-pgraphicimage). This code works because I only access `PA_ImageService` within the .xhtml file which uses the `ModifyProductAdminBean`. So `ModifyProductAdminBean` always exists. As I say in the question, `uFile` is retrieved successfully. The problem is `uFile.getContents()` returns `null`. Why does `uFile` have a `null` content? – GP_ Dec 03 '13 at 09:43