first, the introduction
Browser-based file uploads, especially for uploading via the
In order to make file uploads as comfortable as possible, once a user submits a file, many sites will display an intermediate process animation (such as a rotating icon). Although this technique has some effect when uploading to the server, it provides too little information about the file upload status. Another attempt to solve this problem is to implement a applet--that uploads files to the server via FTP. The downside of this scenario is that you have to have a Java-enabled browser that restricts your users.
In this article, we will implement an AJAX-capable component that not only implements the actual process of uploading files to the server, but also "real-time" monitoring of file uploads. The four phases of this component's work are shown in Figures 1,2,3 and 4 below:
Figure 1. Phase 1: Select File Upload
Figure 2. Phase 2: Uploading the file to the server
Figure 3. Phase 3: Upload complete
Figure 4. Phase 4: File Upload Summary |
Second, the implementation of the component
First, we analyze the process of creating a multiple-part filter that will allow us to process and monitor file uploads. We will then continue to implement the JavaServer Faces (JSF) component-it will provide continuous feedback to the user to support the Ajax progress bar approach.
(i) Multi-part filtration: Uploadmultipartfilter
The task of multi-part filtering is to intercept incoming file uploads and write the file to a temporary directory on a server. It also monitors the number of bytes received and determines the extent to which the file has been uploaded. Fortunately, there is now an excellent jakarta-commons Open Source Library available (FileUpload), which is responsible for parsing an HTTP multi-part Request and uploading the file to the server. What we need to do is expand the library and add the "hooks" we need to monitor how many bytes have been processed.
public class Uploadmultipartfilter implements filter{ public void Dofilter (ServletRequest request,servletresponse response,filterchain chain) Throws IOException, Servletexception { HttpServletRequest hrequest = (httpservletrequest) request; Check if we're dealing with a multi-part Request String Contentheader = Hrequest.getheader ("Content-type"); Boolean Ismultipart = (contentheader!= null && contentheader.indexof ("Multipart/form-data")!=-1); if (Ismultipart = = False) { Chain.dofilter (Request,response); }else{ Uploadmultipartrequestwrapper wrapper = new Uploadmultipartrequestwrapper (hrequest); Chain.dofilter (Wrapper,response); } ... } |
As you can see, the Uploadmultipartfilter class simply checks whether the current request is a multiple-part request. If the request does not include a file upload, the request is passed to the next filter in the request chain without any additional processing. Otherwise, the request will be wrapped in a uploadmultipartrequestwrapper.
(ii) Uploadmultipartrequestwrapper class
public class Uploadmultipartrequestwrapper Extends httpservletrequestwrapper{ Private Map formparameters; Private Map fileparameters; Public Uploadmultipartrequestwrapper (HttpServletRequest request) { Super (Request); try{ Servletfileupload upload = new Servletfileupload (); Upload.setfileitemfactory (Request) (new progressmonitorfileitemfactory); List Fileitems = upload.parserequest (request); Formparameters = new HashMap (); Fileparameters = new HashMap (); for (int i=0;iFileitem item = (fileitem) fileitems.get (i); if (Item.isformfield () = = True) { Formparameters.put (Item.getfieldname (), item.getstring ()); }else{ Fileparameters.put (Item.getfieldname (), item); Request.setattribute (Item.getfieldname (), item); } } }catch (fileuploadexception fe) { Request time Exceeded-The user may have moved to another page. Make some Records //... } ... |
In the Uploadmultipartrequestwrapper class, we will initialize the Servletfileupload class, which is responsible for parsing our request and writing the file to the default temp directory on the server. The Servletfileupload instance creates a Fileitem instance for each field encountered in the request (they contain file uploads and normal form elements). A Fileitem instance is then used to retrieve the properties of a submitted field, or, in the case of a file upload, to retrieve a inputstream to the underlying temporary file. In summary, Uploadmultipartrequestwrapper is responsible for parsing the file and setting any fileitem-it describes the file upload as an attribute in the request. These properties are then further collected by the JSF component, and the behavior of normal form fields remains unchanged.
By default, the generic FileUpload library uses an instance of the Diskfileitems class to process file uploads. Although Diskfileitem is useful for handling the entire temporary file business, there is little support for accurate monitoring of how well the file has been handled. Since version 1.1, the Universal FileUpload Library has enabled developers to specify the factories that are used to create fileitem. We will use the Progressmonitorfileitemfactory and Progressmonitorfileitem classes to overload the default behavior and monitor the file upload process.
(iii) Progressmonitorfileitemfactory class
public class Progressmonitorfileitemfactory extends Diskfileitemfactory { Private File temporarydirectory; Private HttpServletRequest Requestref; Private long requestlength; Public progressmonitorfileitemfactory (HttpServletRequest request) { Super (); Temporarydirectory = (File) request.getsession (). Getservletcontext (). getattribute ("Javax.servlet.context.tempdir" ); Requestref = Request; String contentlength = Request.getheader ("Content-length"); if (contentlength!= null) {requestlength = Long.parselong (Contentlength.trim ());} } Public Fileitem CreateItem (string fieldName, String Contenttype,boolean Isformfield, string fileName) { Sessionupdatingprogressobserver observer = NULL; if (Isformfield = false)//This must be a file upload. Observer = new Sessionupdatingprogressobserver (fieldname,filename); Progressmonitorfileitem item = new Progressmonitorfileitem ( Fieldname,contenttype,isformfield, Filename,2048,temporarydirectory, Observer,requestlength); return item; } ... public class Sessionupdatingprogressobserver implements Progressobserver { Private String FieldName; Private String FileName; ... public void setprogress (double progress) { if (request!= null) { Request.getsession (). setattribute ("fileupload.progress.") +fieldname,progress); Request.getsession (). setattribute ("Fileupload.filename.") +fieldname,filename); } } } } |
The Progressmonitorfileitemfactory content-length header is set by the browser and is assumed to be the exact length of the uploaded file being set. This method of determining file lengths does limit the files that you upload in each request-if multiple files are encoded in the request, this value is inaccurate. This is because browsers only send a content-length header, regardless of the number of files uploaded.
In addition to creating a Progressmonitorfileitem instance, Progressmonitorfileitemfactory also registers a progressobserver instance. It will be sent by Progressmonitorfileitem to update the file during the upload process. The implementation of the progressobserver that we are using (Sessionupdatingprogressobserver) Sets the progress percentage to the user's session for the ID of the submitted field. This value can then be accessed by the JSF component to send the update to the user.
(iv) Progressmonitorfileitem class
public class Progressmonitorfileitem extends Diskfileitem { Private Progressobserver Observer; Private Long passedinfilesize; ... Private Boolean Isformfield; ... @Override Public OutputStream Getoutputstream () throws IOException { OutputStream baseoutputstream = super.g Etoutputstream (); if (Isformfield = = False) { return new Bytescountingoutputstream (Baseoutputstream); }else{return Baseoutputstream;} } ... Private class Bytescountingoutputstream extends outputstream{ Private long previousprogressupdate Private OutputStream base; Public Bytescountingoutputstream (OutputStream ous) {base = ous;} ... private void fireprogressevent (int b) { Bytesread + = b; ... Double progress = ((double) (bytesread)/passedinfilesize); Progress *= 100.0 Observer.setprogress (); } } } |
Progressmonitorfileitem wraps the default outputstream of Diskfileitem into a bytescountingoutputstream, This can update the associated progressobserver each time a certain number of bytes are read.
(v) AJAX-enabled JavaServer Faces (JSF) Upload components
This component is responsible for generating HTML file upload tags, displaying a progress bar to monitor file uploads, and generating components that need to be displayed once the file is uploaded successfully. One of the main advantages of using JavaServer faces to implement this component is that most of the complexity is hidden. Developers only need to add component tags to the JSP, and then the component will be responsible for all Ajax and related progress bars to monitor the details. The following JSP code fragment is used to add the upload component to the page.
Value= "#{uploadpagebean.uploadedfile}" uploadicon= "Images/upload.png" Styleclass= "Progressbardiv" Progressbarstyleclass= "ProgressBar" Cellstyleclass= "Progressbarcell" Activestyleclass= "Progressbaractivecell" > <%--below is the component that will become visible once the file upload is completed--%> Value= "file uploaded successfully."/> Image= "Images/reset.png"/> Image= "Images/continue.png"/> |
The Value property of the file upload component needs to be bound to a bean with an attribute with a fileitem. The component is displayed only if the file is successfully received by the server.
third, the realization of Ajax file upload components
Essentially, uploading a component or generating a complete self, or, in the case of an AJAX request, only generates part of the XML to update the status of the progress bar on the page. To prevent the JavaServer faces from generating the complete component tree (which can result in unnecessary load), We also need to implement a Phaselistener (Pagephaselistener) to cancel the other parts of the faces request processing-If an AJAX request is encountered. I've omitted all the discussion about standard configurations (Faces-config.xml and tag libraries) in this article because they are fairly straightforward and have been discussed before, and are all contained in the source code accompanying this article, which you can analyze in detail.
(i) Ajax file Upload Component Builder
The implementation of this component and tag class is simpler. A large number of logic is included in the builder, specifically, it is responsible for the following:
· Encode the entire upload component (and the full HTML file upload tag), the component to display when the file is uploaded, and the client JavaScript code that implements the AJAX request.
· Handle partially AJAX requests appropriately and send back the necessary XML.
· Decode a file upload and set it to a Fileitem instance.
(ii) encoding the entire upload component
As mentioned earlier, the file Upload component consists of three phases. During the entire encoding of the component, we will analyze the encoding for these three phases in detail. Note that the visualization of the component on the page (using CSS Display) attributes will be controlled by Ajax JavaScript.
(iii) Phase I
Figure 5 shows the first phase of the upload component.
Figure 5. Select File Upload |
In the first phase, we need to generate HTML file upload tags and click the upload button when the corresponding execution code. Once the user clicks on the Upload button, the form is submitted and initialized to the second phase by an IFRAME (to prevent the page from blocking). The following is part of the generated code:
File Upload Component Writer.startelement ("input", component); Writer.writeattribute ("type", "file", null); Writer.writeattribute ("Name", Component.getclientid (context), "id"); Writer.writeattribute ("id", Component.getclientid (context), "id"); if (Input.getvalue ()!= null) { If available, the file name is generated. Fileitem filedata = (fileitem) input.getvalue (); Writer.writeattribute ("Value", Filedata.getname (), Filedata.getname ()); } Writer.endelement ("input"); String Iconurl = Input.getuploadicon (); Generate images and attach JavaScript events to them. Writer.startelement ("div", component); Writer.writeattribute ("Style", "display:block;width:100%;text-align:center;", "style"); Writer.startelement ("img", component); Writer.writeattribute ("src", Iconurl, "src"); Writer.writeattribute ("type", "image", "type"); Writer.writeattribute ("Style", "cursor:hand;cursor:pointer;", "style"); UIForm form = Facesutils.getform (context,component); if (form!= null) { String Getformjs = "document.getElementById ('" + form.getclientid (context) + "')"; String Jsfriendlyclientid = Input.getclientid (context). Replace (":", "_"); Set the encoding of the form to multipart for file uploads, and pass an IFRAME To submit its contents. The second phase of the component is also initialized after 500 milliseconds. Writer.writeattribute ("onclick", Getformjs + ". encoding= ' Multipart/form-data ';" + Getformjs + ". target= '" + Iframename + "" + Getformjs + ". Submit ();" + Getformjs + ". encoding= ' application/x-www-form-urlencoded ';" + Getformjs + ". target= ' _self ';" + "SetTimeout (' refreshprogress" + Jsfriendlyclientid + "(); ';"; ", null); } ... Writer.endelement ("img"); Now implement the IFRAME we are going to submit the file/form to. Writer.startelement ("iframe", component); Writer.writeattribute ("id", iframename, NULL); Writer.writeattribute ("name", Iframename,null); Writer.writeattribute ("Style", "display:none;", null); Writer.endelement ("iframe"); Writer.endelement ("div"); Writer.endelement ("div"); Phase 1 over |
(iv) Phase II
The second stage is a progress bar and label showing the current percentage, as shown in Figure 6. The progress bar is implemented as a DIV tag with 100 inline span tags. These will be set by Ajax JavaScript based on the response from the server.
Figure 6. Uploading files to the server |
Writer.startelement ("div", component); Writer.writeattribute ("id", Input.getclientid (context) + "_stage2", "id"); ... Writer.writeattribute ("Style", "Display:none", "style"); String Progressbarid = Component.getclientid (context) + "_progressbar"; String Progressbarlabelid = Component.getclientid (context) + "_progressbarlabel"; Writer.startelement ("div", component); Writer.writeattribute ("id", Progressbarid, "id"); String Progressbarstyleclass = Input.getprogressbarstyleclass (); if (Progressbarstyleclass!= null) Writer.writeattribute ("Class", Progressbarstyleclass, "class"); for (int i=0;i<100;i++) { Writer.write (" "); } Writer.endelement ("div"); Writer.startelement ("div", component); Writer.writeattribute ("id", Progressbarlabelid, "id"); ... Writer.endelement ("div"); Writer.endelement ("div"); Phase 2 over |
(v) Phase three
Finally, as stage three, once the file is uploaded successfully, the component that needs to be displayed is generated, as shown in Figure 7. These are implemented in the Encodechildren method of the generator.
Figure 7. Upload Complete |
public void Encodechildren (Facescontext context, UIComponent component) throws IOException { Responsewriter writer = Context.getresponsewriter (); Uifileupload input = (uifileupload) component; Once the file is uploaded successfully, handle the child nodes that will be displayed Writer.startelement ("div", component); Writer.writeattribute ("id", Input.getclientid (context) + "_stage3", "id"); Stage 3. if (input.getvalue () = null) { Writer.writeattribute ("Style", "display:none;", null); }else{ Writer.writeattribute ("Style", "Display:block", null); } List children = Input.getchildren (); for (UIComponent Child:children) { Facesutils.encoderecursive (Context,child); } Writer.endelement ("div"); Phase 3 over } |
Iv. handling Ajax requests
The generation of AJAX requests is handled in the decoding method of this component. We need to check if this is an actual Ajax request (in order to distinguish it from normal compilation behavior), An XML response is then sent back to the client based on the value set in the session by the Sessionupdatingprogressobserver instance of the Progressmonitorfileitemfactory class.
public void decode (Facescontext context, uicomponent component) { Uifileupload input = (uifileupload) component; Check if this is an upload progress request, or an actual upload request. Externalcontext Extcontext = Context.getexternalcontext (); Map Parametermap = Extcontext.getrequestparametermap (); String clientId = Input.getclientid (context); Map Requestmap = Extcontext.getrequestparametermap (); if (Requestmap.get (clientId) = = null) { return;//do nothing, return } if (Parametermap.containskey (Progress_request_param_name)) { This is a request for progress information in the file request. Get the progress information and make it into XML HttpServletResponse response = (httpservletresponse) context.getexternalcontext (). GetResponse (); Set header information for a response Response.setcontenttype ("Text/xml"); Response.setheader ("Cache-control", "No-cache"); try { Responsewriter writer = Facesutils.setupresponsewriter (context); Writer.startelement ("Progress", input); Writer.startelement ("percentage", input); Gets the current percentage of progress from the session (set by the filter). Double progresscount = (double) extcontext.getsessionmap (). Get ("fileupload.progress." +input.getclientid); if (Progresscount!= null) { Writer.writetext (Progresscount, NULL); }else{ Writer.writetext ("1", null);/We haven't received the upload yet. } Writer.endelement ("percentage"); Writer.startelement ("clientId", input); Writer.writetext (Input.getclientid (context), NULL); Writer.endelement ("ClientId"); Writer.endelement ("Progress"); catch (Exception e) { Make some error records ... } }else{ A normal decoding request. ... |
v. Normal decoding behavior
During normal compilation, the file upload generator retrieves fileitem from the request attribute, where it is set by the filter and updates the component's value bindings. The progress in the session is then updated to 100%, so that JavaScript on the page can put the component into phase 3rd.
A normal decoding request. if (Requestmap.get (clientId). toString (). Equals ("file")) { try{ HttpServletRequest request = (HttpServletRequest) extcontext.getrequest (); Fileitem filedata = (fileitem) request.getattribute (clientId); if (filedata!= null) input.setsubmittedvalue (filedata); Now we need to clear any progress associated with this item Extcontext.getsessionmap ("fileupload.progress." + input.getclientid (context), new Double (100)); }catch (Exception e) { throw new RuntimeException ("Cannot process file upload" + "-Please configure filter.", e); } } |
Client JavaScript is responsible for making progress requests to the server and moving components through different stages. To simplify the problem of handling all browser-specific XMLHttpRequest objects, I chose the Ajaxrequest.js library provided by Matt Krause. The library minimizes the number of JavaScript code that we need to write, and can make this component work properly. Perhaps it would be better to package this part of the JavaScript code as part of the component and then build it from Phaselistener, but I've tried to make it simple by defining a link to the JavaScript Library on the JSP page.
The Getprogressbarjavascript method in the component is called to generate JavaScript. Making JavaScript work normally is the hardest part of implementing AJAX components, but I think the following code is very clear and easy to understand. Although JavaScript is embedded in Java code in my example, it might be better to put it in an externally independent file. In this article, I just want to make the problem simpler and care only about the topic of this article. The following is an example of a JavaScript that will be generated by a component. This assumes that FILEUPLOAD1 is the JSF ID of the client being assigned to the file component, and Uploadform is the ID of the HTML form.
function refreshprogress () { Suppose we are entering into Phase 2. document.getElementById (' Fileupload1_stage1 '). style.display = ' None '; document.getElementById (' Fileupload1_stage2 '). style.display = '; document.getElementById (' Fileupload1_stage3 '). style.display = ' None '; Create Ajax Send Ajaxrequest.post ( { Specify the correct parameters so that The component is handled correctly on the server side ' Parameters ': {' uploadform ': ' Uploadform ', ' FileUpload1 ': ' FileUpload1 ', ' Jsf.component.UIFileUpload ': ' 1 ', ' Ajax.abortphase ': ' 4 '}//abort at Phase 4. Specifies that the appropriate callback method is successfully processed. , ' onsuccess ': function (req) { var xml = Req.responsexml; if (Xml.getelementsbytagname (' clientId '). Length = = 0) { SetTimeout (' refreshprogress () ', 200); Return } var clientId = xml.getelementsbytagname (' clientId '); ClientId = clientid[0].firstchild.nodevalue + ' _progressbar '; Get percent from XML var percentage = xml.getelementsbytagname (' percentage ') [0].firstchild.nodevalue; var Innerspans = document.getElementById (clientId). getElementsByTagName (' span '); document.getElementById (clientId + ' label '). InnerHTML = Math.Round (percentage) + '% '; Sets the style classes for these spans based on the current progress. for (Var i=0;iif (I percentage) { Innerspans[i].classname = ' active '; }else{ Innerspans[i].classname = ' passive '; } } If the progress is not 100, we need to continue querying the server to implement the update. if (percentage!= 100) { SetTimeout (' refreshprogress () ', 400); } else { The file upload is complete and we now need to get the component into phase 3rd. document.getElementById (' Fileupload1_stage1 '). style.display = ' None '; document.getElementById (' Fileupload1_stage2 '). style.display = ' None '; document.getElementById (' Fileupload1_stage3 '). style.display = '; } } }); } return builder.tostring (); |
Vi. Conclusion
I am hopeful that this article will give you further thought about how to make file uploads more user-friendly and the possibility of using Ajax and JavaServer faces to implement advanced user interface components. There is no doubt that the scenarios in this article are lengthy and likely to be further improved. I would like you to give a detailed analysis of the complete source code provided in this article to get a thorough understanding of the concepts discussed in this article.