1 //
2 // $Id: DefaultFileUploadThread.java 287 2007-06-17 09:07:04 +0000 (dim., 17
3 // juin 2007) felfert $
4 //
5 // jupload - A file upload applet.
6 // Copyright 2007 The JUpload Team
7 //
8 // Created: ?
9 // Creator: William JinHua Kwong
10 // Last modified: $Date: 2015-03-10 20:59:42 +0100 (mar., 10 mars 2015) $
11 //
12 // This program is free software; you can redistribute it and/or modify it under
13 // the terms of the GNU General Public License as published by the Free Software
14 // Foundation; either version 2 of the License, or (at your option) any later
15 // version. This program is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17 // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
18 // details. You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software Foundation, Inc.,
20 // 675 Mass Ave, Cambridge, MA 02139, USA.
21
22 package wjhk.jupload2.upload;
23
24 import java.io.OutputStream;
25 import java.net.SocketException;
26 import java.util.concurrent.BlockingQueue;
27 import java.util.regex.Pattern;
28
29 import wjhk.jupload2.exception.JUploadException;
30 import wjhk.jupload2.exception.JUploadExceptionUploadFailed;
31 import wjhk.jupload2.exception.JUploadIOException;
32 import wjhk.jupload2.exception.JUploadInterrupted;
33 import wjhk.jupload2.gui.DialogUploadRetry;
34 import wjhk.jupload2.policies.UploadPolicy;
35
36 /**
37 * This class is based on the {@link FileUploadThread} class. It's an abstract class that contains the default
38 * implementation for the {@link FileUploadThread} interface. <BR>
39 * It contains the following abstract methods, which must be implemented in the children classes. These methods are
40 * called in this order: <DIR> <LI>For each upload request (for instance, upload of 3 files with nbFilesPerRequest to 2,
41 * makes 2 request: 2 files, then the last one): <DIR> <LI><I>try</I> <LI>
42 * {@link #startRequest}: start of the
43 * UploadRequest. <LI>Then, for each file to upload (according to the nbFilesPerRequest and maxChunkSize applet
44 * parameters) <DIR> <LI>beforeFile(int) is called before writting the bytes for this file (or this chunk) <LI>
45 * afterFile(int) is called after writting the bytes for this file (or this chunk) </DIR> <LI>finishRequest() </DIR></LI>
46 * <I>finally</I>cleanRequest() <LI>Call of cleanAll(), to clean up any used resources, common to the whole upload.
47 * </DIR>
48 */
49 public abstract class DefaultFileUploadThread extends Thread implements FileUploadThread {
50
51 // ////////////////////////////////////////////////////////////////////////
52 // /////////////////////// VARIABLES //////////////////////////////////////
53 // ////////////////////////////////////////////////////////////////////////
54
55 /**
56 * The queue that'll transmit each packet to upload to the server.
57 */
58 BlockingQueue<UploadFilePacket> packetQueue = null;
59
60 /**
61 * The upload manager. The thread that prepares files, and is responsible to manage the upload process.
62 *
63 * @see FileUploadManagerThread
64 */
65 FileUploadManagerThread fileUploadManagerThread = null;
66
67 /**
68 * The upload policy contains all parameters needed to define the way files should be uploaded, including the URL.
69 */
70 protected UploadPolicy uploadPolicy = null;
71
72 /**
73 * The value of the applet parameter maxChunkSize, or its default value.
74 */
75 long maxChunkSize;
76
77 // ////////////////////////////////////////////////////////////////////////////////////
78 // /////////////////////// PRIVATE ATTRIBUTES
79 // ////////////////////////////////////////////////////////////////////////////////////
80
81 // This variable should only be set to false. Setting to true should be used
82 // only to
83 // debug the JUPload retry feature.
84 static boolean sendResumableError = false;
85
86 /**
87 * Current retry number, for the resume upload feature. The first try occurs with nbRetry=0.
88 */
89 int nbRetry = 0;
90
91 /**
92 * The full response message from the server, if any. For instance, in HTTP mode, this contains both the headers and
93 * the body.
94 */
95 protected String responseMsg = "";
96
97 /**
98 * The response message from the application. For instance, in HTTP mode, this contains the body response.<BR>
99 * Note: for easier management on the various server configurations, all end or line characters (CR, LF or CRLF) are
100 * changed to uniform CRLF.
101 */
102 protected String responseBody = "";
103
104 /**
105 * Creates a new instance.
106 *
107 * @param threadName The name of the thread, that will be displayed in the debugger and in the logs.
108 * @param packetQueue The queue from wich packets to upload are available.
109 * @param uploadPolicy The upload policy to be applied.
110 * @param fileUploadManagerThread The thread that is managing the upload.
111 */
112 public DefaultFileUploadThread(String threadName, BlockingQueue<UploadFilePacket> packetQueue,
113 UploadPolicy uploadPolicy, FileUploadManagerThread fileUploadManagerThread) {
114 // Thread parameters.
115 super(threadName);
116
117 // Specific stuff.
118 this.packetQueue = packetQueue;
119 this.uploadPolicy = uploadPolicy;
120 this.fileUploadManagerThread = fileUploadManagerThread;
121 // Let's read up to date upload parameters.
122 this.maxChunkSize = this.uploadPolicy.getMaxChunkSize();
123
124 this.uploadPolicy.displayDebug("DefaultFileUploadThread created", 30);
125 }
126
127 /**
128 * This method calculates the upload overhead for the file number indexFile in the filesDataParam given to the
129 * constructor. For instance, in HTTP, the upload contains a head and a tail for each files.
130 *
131 * @param uploadFileData The file whose additional length is asked.
132 * @return The additional number of bytes for this file.
133 */
134 abstract long getAdditionnalBytesForUpload(UploadFileData uploadFileData) throws JUploadIOException;
135
136 /**
137 * This method is called before starting of each request. It can be used to prepare any work, before starting the
138 * request. For instance, in HTTP, the tail must be properly calculated, as the last one must be different from the
139 * others.<BR>
140 * The packets to send are available through the {@link #packetQueue} queue.
141 */
142 abstract void beforeRequest(UploadFilePacket packet) throws JUploadException;
143
144 /**
145 * This method is called for each upload request to the server. The number of request to the server depends on:
146 * <DIR> <LI>The total number of files to upload. <LI>The value of the nbFilesPerRequest applet parameter. <LI>The
147 * value of the maxChunkSize applet parameter. </DIR> The main objective of this method is to open the connection to
148 * the server, where the files to upload will be written. It should also send any header necessary for this upload
149 * request. The {@link #getOutputStream()} methods is then called to know where the uploaded files should be
150 * written. <BR>
151 * Note: it's up to the class containing this method to internally manage the connection.
152 *
153 * @param contentLength The total number of bytes for the files (or the chunk) to upload in this query.
154 * @param bChunkEnabled True if this upload is part of a file (can occurs only if the maxChunkSize applet parameter
155 * is set). False otherwise.
156 * @param chunkPart The chunk number. Should be ignored if bChunkEnabled is false.
157 * @param bLastChunk True if in chunk mode, and this upload is the last one. Should be ignored if bChunkEnabled is
158 * false.
159 */
160 abstract void startRequest(long contentLength, boolean bChunkEnabled, int chunkPart, boolean bLastChunk)
161 throws JUploadException;
162
163 /**
164 * This method is called at the end of each request.
165 *
166 * @return The response status code from the server (200 == OK)
167 * @see #startRequest(long, boolean, int, boolean)
168 */
169 abstract int finishRequest() throws JUploadException;
170
171 /**
172 * Reaction of the upload thread, when an interruption has been received. This method should close all resource to
173 * the server, to allow the server to free any resource (temporary file, network connection...).
174 */
175 abstract void interruptionReceived();
176
177 /**
178 * This method is called before sending the bytes corresponding to the file whose index is given in argument. If the
179 * file is splitted in chunks (see the maxChunkSize applet parameter), this method is called before each chunk for
180 * this file.
181 *
182 * @param uploadFilePacket The bunch of files in the current request
183 * @param uploadFileData The next file that will be sent
184 */
185 abstract void beforeFile(UploadFilePacket uploadFilePacket, UploadFileData uploadFileData) throws JUploadException;
186
187 /**
188 * Idem as {@link #beforeFile(UploadFilePacket, UploadFileData)}, but is called after each file (and each chunks for
189 * each file).
190 *
191 * @param uploadFileData The file that was just sent.
192 */
193 abstract void afterFile(UploadFileData uploadFileData) throws JUploadException;
194
195 /**
196 * Clean any used resource of the last executed request. In HTTP mode, the output stream, input stream and the
197 * socket should be cleaned here.
198 */
199 abstract void cleanRequest() throws JUploadException;
200
201 /**
202 * Clean any used resource, like a 'permanent' connection. This method is called after the end of the last request
203 * (see on the top of this page for details).
204 */
205 abstract void cleanAll() throws JUploadException;
206
207 /**
208 * Get the output stream where the files should be written for upload.
209 *
210 * @return The target output stream for upload.
211 */
212 abstract OutputStream getOutputStream() throws JUploadException;
213
214 /**
215 * Return the the body for the server response. That is: the server response without the http header. This is the
216 * functional response from the server application, that has been as the HTTP reply body, for instance: all 'echo'
217 * PHP commands. <BR>
218 *
219 * @return The last application response (HTTP body, in HTTP upload)
220 */
221 public String getResponseBody() {
222 return this.responseBody;
223 }
224
225 /**
226 * Get the server Output.
227 *
228 * @return The status message from the first line of the response (e.g. "200 OK").
229 */
230 public String getResponseMsg() {
231 return this.responseMsg;
232 }
233
234 /**
235 * @return the packetQueue
236 */
237 public BlockingQueue<UploadFilePacket> getPacketQueue() {
238 return packetQueue;
239 }
240
241 /**
242 * Unused Store the String that contains the server response body.
243 *
244 * @param body The response body that has been read.
245 */
246 void setResponseBody(String body) {
247 this.responseBody = normalizeCRLF(body);
248 }
249
250 /**
251 * Add a String that has been read from the server response.
252 *
253 * @param msg The status message from the first line of the response (e.g. "200 OK").
254 */
255 void setResponseMsg(String msg) {
256 this.responseMsg = normalizeCRLF(msg);
257 }
258
259 // ////////////////////////////////////////////////////////////////////////////////////
260 // /////////////////////// PRIVATE FUNCTIONS
261 // ////////////////////////////////////////////////////////////////////////////////////
262
263 /**
264 * This method waits for a packet on the packetQueue. Then, it calls the doUpload() method, to send these files to
265 * the server.
266 */
267 @Override
268 final public void run() {
269 this.uploadPolicy.displayDebug("Start of the FileUploadThread", 5);
270 int nbSentFiles = 0;
271
272 // We'll stop the upload if an error occurs. So the try/catch is
273 // outside the while.
274 while (!this.fileUploadManagerThread.isUploadFinished()) {
275 UploadFilePacket packet = null;
276
277 try {
278 // We take the next packet. This method will block until a packet is ready.
279 packet = packetQueue.take();
280
281 // If the packet is 'poisonned', then it's the standard end of work.
282 if (packet.isPoisonned()) {
283 break;
284 }
285
286 // /////////////////////////////////////////////////////////////////////////////////
287 // Let's go to work : THIS IS THE UPLOAD, surrounded by the
288 // RESUME LOOP
289 // /////////////////////////////////////////////////////////////////////////////////
290 nbRetry = 0;
291 while (true) {
292 try {
293 // Let's try to upload the current packet.
294 doUpload(packet);
295
296 // If we are here, the last upload is a success. Let's
297 // exit the loop.
298 break;
299 } catch (JUploadException jue) {
300 // manageRetry throw the exception, if no retry should
301 // be done.
302 manageRetry(jue);
303
304 // If we are here, the applet should retry the upload.
305 // We let it loop again.
306 nbRetry += 1;
307
308 // We must clean the resources of the previous attempt.
309 beforeRetry(packet);
310 }
311 }// while(resume)
312 // /////////////////////////////////////////////////////////////////////////////////
313 // //////////////// ENF OF RESUME LOOP
314 // /////////////////////////////////////////////////////////////////////////////////
315
316 this.uploadPolicy.displayDebug("After do upload", 50);
317 } catch (InterruptedException e) {
318 this.uploadPolicy.displayWarn(this.getClass().getName() + ".run(): received in "
319 + e.getClass().getName() + ", exiting...");
320 break;
321 } catch (JUploadException e) {
322 if (this.fileUploadManagerThread.isUploadFinished()) {
323 // We ignore the error
324 this.uploadPolicy.displayWarn("Ignoring " + e.getClass().getName() + " because upload is finished");
325 } else {
326 this.fileUploadManagerThread.setUploadException(e);
327 }
328 } catch (JUploadInterrupted e) {
329 // The upload has been interrupted, probably by the user
330 // (stop
331 // button). The fileManagerUploadThread aleady knows this.
332 this.uploadPolicy.displayInfo("Upload stopped by the user");
333 this.uploadPolicy.displayDebug(e.getMessage(), 30);
334 } finally {
335 // Let's free any locked resource for the current packet.
336 // This is done here, to allow the resume feature (and, even in
337 // case an error occurs, we free resources only after the last
338 // retry)
339 for (UploadFileData uploadFileData : packet) {
340 if (uploadFileData.isPreparedForUpload()) {
341 uploadFileData.afterUpload();
342 }
343 }
344 // We free any resources associated with this thread.
345 try {
346 cleanAll();
347 } catch (Exception e) {
348 // We ignore the error
349 this.uploadPolicy.displayWarn("[run(), After cleanAll] Ignoring " + e.getClass().getName()
350 + " because upload is finished");
351 }
352 }
353
354 nbSentFiles += packet.size();
355 }// while (!isUploadFinished)
356
357 this.uploadPolicy.displayDebug("End of the FileUploadThread (" + nbSentFiles + " have been sent)", 5);
358 }// run
359
360 /**
361 * @param jue
362 * @throws JUploadException
363 */
364 private void manageRetry(JUploadException jue) throws JUploadException {
365 String exceptionCauseClass = (jue.getCause() == null) ? "no exception cause" : jue.getCause().getClass()
366 .getName();
367 String errMsg = jue.getClass().getName() + " (" + jue.getMessage() + "), caused by: " + exceptionCauseClass;
368
369 if (this.fileUploadManagerThread.isUploadFinished()) {
370 // The upload is stopped. This error may be caused
371 this.uploadPolicy.displayWarn("The following error occurs, but the upload is stopped, ignoring it ]"
372 + errMsg + "]");
373 throw jue;
374 } else if (jue.getCause() instanceof SocketException) {
375 this.uploadPolicy.displayWarn("A 'resumable' error occurred: " + errMsg);
376 // If it was the last retry, we stop here.
377 if (nbRetry >= this.uploadPolicy.getRetryMaxNumberOf()) {
378 this.uploadPolicy.displayWarn("Too much retries (" + nbRetry + "), exiting...");
379 throw jue;
380 }
381
382 DialogUploadRetry dialogUploadRetry = new DialogUploadRetry(this.uploadPolicy.getContext().getFrame(), jue,
383 nbRetry, this.uploadPolicy);
384 // The constructor returns, when the dialog is closed. Let's check
385 // the user answer:
386 if (dialogUploadRetry.isRetryValidated()) {
387 this.uploadPolicy.displayDebug("The user (or the timer) choosed to retry the upload", 30);
388 } else {
389 this.uploadPolicy.displayDebug("The user refuses to retry the upload, exiting...", 30);
390 // No retry, let's note the exception and go out
391 throw jue;
392 }// End of resumable exceptions management.
393 } else {
394 // This exception can't be resumed. We transmit it.
395 this.uploadPolicy.displayWarn("Non resumable error occured, exiting...");
396 throw jue;
397 }
398 }
399
400 /**
401 * Actual execution file(s) upload. It's called by the run methods, once for all files, or file by file, depending
402 * on the UploadPolicy. The list of files to upload is stored in the packet parameter.<BR>
403 * This method is called by the run() method. The prerequisite about the filesToUpload array are: <DIR> <LI>If the
404 * sum of contentLength for the files in the array is more than the maxChunkSize, then nbFilesToUploadParam is one.
405 * <LI>The number of elements in filesToUpload is less (or equal) than the nbMaxFilesPerUpload. </DIR>
406 *
407 * @throws JUploadException
408 * @throws JUploadInterrupted Thrown when an interruption of the thread is detected.
409 */
410 void doUpload(UploadFilePacket packet) throws JUploadException, JUploadInterrupted {
411 boolean bChunkEnabled = false;
412 long totalContentLength = 0;
413 long totalFileLength = 0;
414
415 // We are about to start a new upload.
416 this.fileUploadManagerThread.setUploadStatus(packet, packet.get(0),
417 FileUploadManagerThread.UPLOAD_STATUS_UPLOADING);
418
419 // Prepare upload, for all files to be uploaded.
420 beforeRequest(packet);
421
422 for (UploadFileData uploadFileData : packet) {
423 // The upload may be finished, while we're working on the files...
424 if (this.fileUploadManagerThread.isUploadFinished()) {
425 // Let's stop our work here.
426 return;
427 }
428 // Total length, for HTTP upload.
429 totalContentLength += uploadFileData.getUploadLength();
430 totalContentLength += getAdditionnalBytesForUpload(uploadFileData);
431 // Total file length: used to manage the progress bar (we don't
432 // follow the bytes uploaded within headers and forms).
433 totalFileLength += uploadFileData.getUploadLength();
434
435 this.uploadPolicy.displayDebug(
436 "file " + uploadFileData.getFileName() + ": content=" + uploadFileData.getUploadLength()
437 + " bytes, getAdditionnalBytesForUpload=" + getAdditionnalBytesForUpload(uploadFileData)
438 + " bytes", 50);
439 }// for
440
441 // Ok, now we check that the totalContentLength is less than the chunk
442 // size.
443 if (totalFileLength >= this.maxChunkSize) {
444 // hum, hum, we have to download file by file, with chunk enabled.
445 // This a prerequisite of this method.
446 if (packet.size() > 1) {
447 this.fileUploadManagerThread.setUploadException(new JUploadException(
448 "totalContentLength >= chunkSize: this.filesToUpload.length should be 1 (doUpload)"));
449 }
450 bChunkEnabled = true;
451 }
452
453 // Now, we can actually do the job. This is delegate into smaller
454 // method, for easier understanding.
455 if (bChunkEnabled) {
456 // No more than one file, when in chunk mode.
457 if (packet.size() > 1) {
458 throw new JUploadException(
459 "totalContentLength >= chunkSize: this.filesToUpload.length should not be more than 1 (doUpload)");
460 }
461 doChunkedUpload(packet);
462 } else {
463 doNonChunkedUpload(packet, totalContentLength, totalFileLength);
464 }
465
466 // If the request properly finished, we remove the files from the list
467 // of files to upload.
468 if (this.fileUploadManagerThread.getUploadException() == null
469 && !this.fileUploadManagerThread.isUploadStopped()) {
470 this.fileUploadManagerThread.currentRequestIsFinished(packet);
471 }
472 }
473
474 /**
475 * Execution of an upload, in chunk mode. This method expects that the given packet contains only one file.
476 *
477 * @param packet The packet that contains the file to upload in chunk mode
478 * @throws JUploadException When any error occurs, or when there is more than one file in packet.
479 * @throws JUploadInterrupted Thrown when an interruption of the thread is detected.
480 */
481 void doChunkedUpload(UploadFilePacket packet) throws JUploadException, JUploadInterrupted {
482 boolean bLastChunk = false;
483 int chunkPart = 0;
484
485 long contentLength = 0;
486 long thisChunkSize = 0;
487
488 if (packet.size() > 1) {
489 throw new JUploadException("doChunkedUpload called with a packet of more than 1 file (" + packet.size()
490 + " files)");
491 }
492 UploadFileData uploadFileData = packet.get(0);
493
494 // This while enables the chunk management:
495 // In chunk mode, it loops until the last chunk is uploaded. This works
496 // only because, in chunk mode, files are uploaded one y one (the for
497 // loop within the while loops through ... 1 unique file).
498 // In normal mode, it does nothing, as the bLastChunk is set to true in
499 // the first test, within the while.
500 while (!bLastChunk && !this.fileUploadManagerThread.isUploadFinished()) {
501 // Let's manage chunk:
502 // Files are uploaded one by one. This is checked just above.
503 chunkPart += 1;
504 bLastChunk = (uploadFileData.getRemainingLength() <= this.maxChunkSize);
505
506 // Is this the last chunk ?
507 if (bLastChunk) {
508 thisChunkSize = uploadFileData.getRemainingLength();
509 } else {
510 thisChunkSize = this.maxChunkSize;
511 }
512 contentLength = thisChunkSize + getAdditionnalBytesForUpload(uploadFileData);
513
514 // We are about to start a new upload.
515 this.fileUploadManagerThread.setUploadStatus(packet, uploadFileData,
516 FileUploadManagerThread.UPLOAD_STATUS_UPLOADING);
517
518 // Ok, we've prepare the job for chunk upload. Let's do it!
519 startRequest(contentLength, true, chunkPart, bLastChunk);
520
521 try {
522
523 // Let's add any file-specific header.
524 beforeFile(packet, uploadFileData);
525
526 // Actual upload of the file:
527 uploadFileData.uploadFile(getOutputStream(), thisChunkSize);
528
529 // Caution : this is for debug only. In production mode,
530 // sendResumableError should always be 'false'
531 if (chunkPart == 2 && sendResumableError) {
532 sendResumableError = false;
533 throw new JUploadException(new SocketException(
534 "This is a debug error. Should not happen in production."));
535 }
536
537 // If we are not in chunk mode, or if it was the last chunk,
538 // upload should be finished.
539 if (bLastChunk && uploadFileData.getRemainingLength() > 0) {
540 throw new JUploadExceptionUploadFailed("Files has not be entirely uploaded. The remaining size is "
541 + uploadFileData.getRemainingLength() + " bytes. File size was: "
542 + uploadFileData.getUploadLength() + " bytes.");
543
544 }
545 // Let's add any file-specific header.
546 afterFile(uploadFileData);
547
548 // Let's finish the request, and wait for the server Output, if
549 // any (not applicable in FTP)
550 int status = finishRequest();
551
552 if (bLastChunk) {
553 // We are finished with this one. Let's display it.
554 this.fileUploadManagerThread.setUploadStatus(packet, uploadFileData,
555 FileUploadManagerThread.UPLOAD_STATUS_FILE_UPLOADED_WAITING_FOR_RESPONSE);
556 } else {
557 // We are finished with the current chunk, but not with the
558 // file. Let's display it.
559 this.fileUploadManagerThread.setUploadStatus(packet, uploadFileData,
560 FileUploadManagerThread.UPLOAD_STATUS_CHUNK_UPLOADED_WAITING_FOR_RESPONSE);
561 }
562
563 // We now ask to the uploadPolicy, if it was a success.
564 // If not, the isUploadSuccessful should raise an exception.
565 this.uploadPolicy.checkUploadSuccess(status, getResponseMsg(), getResponseBody());
566 }// try
567 finally {
568 cleanRequest();
569 }
570 }// while
571 // Let's tell our manager that we've done the job!
572 this.fileUploadManagerThread.anotherFileHasBeenSent(packet, uploadFileData);
573
574 }// doChunkedUpload
575
576 /**
577 * Execution of an upload, in standard mode. This method uploads all files in the given packet.
578 *
579 * @param packet The files to upload in the current request to the server
580 * @param totalContentLength The total size of the upload, including any protocol-specific header or footer.
581 * @param totalFileLength The sum of each file length.
582 * @throws JUploadException When any error occurs
583 * @throws JUploadInterrupted Thrown when an interruption of the thread is detected.
584 */
585 void doNonChunkedUpload(UploadFilePacket packet, final long totalContentLength, final long totalFileLength)
586 throws JUploadException, JUploadInterrupted {
587
588 // First step is to prepare all files.
589 startRequest(totalContentLength, false, 0, true);
590
591 try {
592
593 // Then, upload each file.
594 for (UploadFileData uploadFileData : packet) {
595 if (this.fileUploadManagerThread.isUploadFinished()) {
596 // Upload is finished (by the user or because of an error,
597 // or instance)
598 break;
599 }
600 // We are about to start a new upload.
601 this.fileUploadManagerThread.setUploadStatus(packet, uploadFileData,
602 FileUploadManagerThread.UPLOAD_STATUS_UPLOADING);
603
604 // Let's add any file-specific header.
605 beforeFile(packet, uploadFileData);
606
607 // Actual upload of the file:
608 if (!this.fileUploadManagerThread.isUploadFinished()) {
609 uploadFileData.uploadFile(getOutputStream(), uploadFileData.getUploadLength());
610 }
611
612 // Let's add any file-specific header.
613 if (!this.fileUploadManagerThread.isUploadFinished()) {
614 afterFile(uploadFileData);
615
616 // Let's tell our manager that we've done the job!
617 // Ok, maybe the server will refuse it, but we won't say
618 // that
619 // now!
620 this.fileUploadManagerThread.anotherFileHasBeenSent(packet, uploadFileData);
621 }
622 }
623
624 // We are finished with this one. Let's display it.
625 if (!this.fileUploadManagerThread.isUploadFinished()) {
626 this.fileUploadManagerThread.setUploadStatus(packet, packet.get(packet.size() - 1),
627 FileUploadManagerThread.UPLOAD_STATUS_FILE_UPLOADED_WAITING_FOR_RESPONSE);
628
629 // Let's finish the request, and wait for the server Output, if
630 // any (not applicable in FTP)
631 int status = finishRequest();
632
633 // We now ask to the uploadPolicy, if it was a success.
634 // If not, the isUploadSuccessful should raise an exception.
635 this.uploadPolicy.checkUploadSuccess(status, getResponseMsg(), getResponseBody());
636 }
637 }// try
638 finally {
639 cleanRequest();
640 }
641
642 }// doNonChunkedUpload
643
644 /**
645 * Clean any resource of the last attempt for this packet, which would be in inconsistent step, in order to retry
646 * the upload of the current packet.
647 */
648 private void beforeRetry(UploadFilePacket packet) throws JUploadException {
649 if (packet != null) {
650 for (UploadFileData uploadFileData : packet) {
651 if (uploadFileData.isPreparedForUpload()) {
652 uploadFileData.beforeRetry();
653 }
654 }
655 }
656 }
657
658 /** @see FileUploadThread#close() */
659 public void close() {
660 try {
661 cleanAll();
662 } catch (JUploadException e) {
663 this.uploadPolicy.displayErr(e);
664 }
665 }
666
667 /**
668 * Replace single \r and \n by uniform end of line characters (CRLF). This makes it easier, to search for string
669 * within the body.
670 *
671 * @param s The original string
672 * @return The string with single \r and \n modified changed to CRLF (\r\n).
673 */
674 public final String normalizeCRLF(String s) {
675 Pattern p = Pattern.compile("\\r\\n|\\r|\\n", Pattern.MULTILINE);
676 String[] lines = p.split(s);
677 // Worst case: the s string contains only \n or \r characters: we then
678 // need to triple the string length. Let's say double is enough.
679 StringBuffer sb = new StringBuffer(s.length() * 2);
680 for (int i = 0; i < lines.length; i += 1) {
681 sb.append(lines[i]).append("\r\n");
682 }
683
684 return sb.toString();
685 }
686
687 /**
688 * Replace \r and \n by correctly displayed end of line characters. Used to display debug output. It also replace
689 * any single \r or \n by \r\n, to make it easier, to search for string within the body.
690 *
691 * @param s The original string
692 * @return The string with \r and \n modified, to be correctly displayed.
693 */
694 public final String quoteCRLF(String s) {
695 return s.replaceAll("\r\n", "\\\\r\\\\n\n");
696 }
697
698 /**
699 * {@inheritDoc}
700 */
701 public void setFileUploadThreadManager(FileUploadManagerThread fileUploadManagerThread) throws JUploadException {
702 if (this.fileUploadManagerThread != null) {
703 throw new JUploadException(
704 "Can not override fileUploadManagerThread (in DefaultFileUpload.setFileUploadThreadManager()");
705 }
706 this.fileUploadManagerThread = fileUploadManagerThread;
707 }
708 }