0

I'm trying to implement JSF 2.3 file upload:

<context-param>
    <param-name>javax.faces.ENABLE_WEBSOCKET_ENDPOINT</param-name>
    <param-value>true</param-value>
</context-param>

XHTML page:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html lang="en"
      xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://xmlns.jcp.org/jsf/core">
    <h:head>
        <title>About</title>
        <script type="text/javascript">
            function progressBar(data) {
                if (data.status == "begin") {
                    document.getElementById("progressBarId").style.display = "block";
                }
                else if (data.status == "complete") {
                    document.getElementById("progressBarId").style.display = "none";
                }
            }

            function updateProgressBar(percent) {
                var emptyColor = "#cccccc";
                var progressColor = "#3333cc";
                document.getElementById("progressBarId").style.background = "linear-gradient(to right, " + progressColor + " 0%, " + progressColor + " " + percent + "%, " + emptyColor + " " + percent + "%, " + emptyColor + " 100%)";
            }
        </script>
    </h:head>
    <h:body>
        <f:websocket channel="uploadProgress" scope="view" onmessage="updateProgressBar" />

        <h:messages id="uploadMsgId" globalOnly="true" showDetail="false" showSummary="true" style="color:red"/>
        <h:form id="uploadFormId" enctype="multipart/form-data">
            <h:inputFile id="fileToUpload" required="true" requiredMessage="No file selected ..." value="#{uploadBean.file}"/>
            <h:message showDetail="false" showSummary="true" for="fileToUpload" style="color:red"/>
            <h:commandButton value="Upload" action="#{uploadBean.upload()}">
                <f:ajax execute="fileToUpload" onevent="progressBar" render=":uploadMsgId @form"/>
            </h:commandButton>
        </h:form>
        <div>
            <div id="progressBarId" style="display: none; width: 250px; height: 23px; border: 1px solid black;" width="250px;" height="23"/>
        </div>

    </h:body>
</html>

Bean:

@Named
@RequestScoped
public class UploadBean
{
    @Inject
    @Push
    private PushContext uploadProgress;

    private static final Logger logger = Logger.getLogger(UploadBean.class.getName());
    private Part file;

    public Part getFile()
    {
        return file;
    }

    public void setFile(Part file)
    {
        this.file = file;
    }

    public void upload()
    {

        if (file != null)
        {

            logger.info("File Details:");
            logger.log(Level.INFO, "File name:{0}", file.getName());
            logger.log(Level.INFO, "Content type:{0}", file.getContentType());
            logger.log(Level.INFO, "Submitted file name:{0}", file.getSubmittedFileName());
            logger.log(Level.INFO, "File size:{0}", file.getSize());

            try (InputStream inputStream = file.getInputStream(); FileOutputStream outputStream = new FileOutputStream("C:" + File.separator + "jsf_files_test_for_delete" + File.separator + file.getSubmittedFileName()))
            {

                long lastTimestamp = System.currentTimeMillis();
                int pushInterval = 1000;
                long totalRead = 0;
                long size = file.getSize();

                int bytesRead = 0;
                final byte[] chunck = new byte[1024];
                while ((bytesRead = inputStream.read(chunck)) != -1)
                {
                    outputStream.write(chunck, 0, bytesRead);
                    totalRead += bytesRead;

                    if (System.currentTimeMillis() > lastTimestamp + pushInterval)
                    {
                        lastTimestamp = System.currentTimeMillis();
                        uploadProgress.send(100.0 * totalRead / size); // Make sure this isn't sent more often than once per second.
                    }
                }

                FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("Upload successfully ended!"));
            }
            catch (IOException e)
            {
                FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("Upload failed!"));
            }
        }
    }
}

After I press Upload I wait some time for the upload process. Looks like there is a issue with the code that I can't find. Can you give some idea how I can fix this?

Peter Penzov
  • 1,126
  • 134
  • 430
  • 808

1 Answers1

1

File upload works as follows:

  1. Client sends file to server.
  2. Server retrieves file and saves in memory or tempdisk.
  3. JSF bean method saves/moves file to permanent location.

This code only calculates progress of step 3, not steps 1 and 2. There is with <h:inputFile> unfortunately no way to calculate the progress of the first two steps because:

  • <h:inputFile> doesn't use HTML5+XHR file upload, but an iframe trick for backwards compatibility with older browsers. There is no way to hook iframe submit progress in JavaScript.
  • Servlet 3.0 Part API, which is utilized by <h:inputFile>, doesn't use streaming, but gets saved fully in memory or temp disk first before JSF backing bean method will be hit. There is no way to hook server side upload request parsing progress using native Servlet API.

So, the progress of sending the file over network from browser to server before hitting JSF method remains unknown. That is, when you rely on standard API. A theoretical solution would be to create a custom component which utilizes HTML5+XHR file upload API.

The progress of saving uploaded file in JSF backing bean method is possible with e.g. websocket based push, but saving a file in server side is so fast as compared to sending the file to server that it's after all pointless to display only this part of the progress.

Unfortunately, it is what it is. Consider using <p:fileUpload>.

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555