1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.ui.form.field.upload;
35
36 import info.magnolia.cms.beans.config.MIMEMapping;
37
38 import java.io.OutputStream;
39 import java.util.Locale;
40
41 import org.apache.commons.lang3.StringEscapeUtils;
42 import org.apache.commons.lang3.StringUtils;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 import com.vaadin.event.dd.DragAndDropEvent;
47 import com.vaadin.event.dd.DropHandler;
48 import com.vaadin.event.dd.acceptcriteria.AcceptAll;
49 import com.vaadin.event.dd.acceptcriteria.AcceptCriterion;
50 import com.vaadin.server.StreamVariable;
51 import com.vaadin.server.UploadException;
52 import com.vaadin.ui.Component;
53 import com.vaadin.ui.DragAndDropWrapper;
54 import com.vaadin.ui.DragAndDropWrapper.WrapperTransferable;
55 import com.vaadin.ui.HasComponents;
56 import com.vaadin.ui.Html5File;
57 import com.vaadin.ui.UI;
58 import com.vaadin.v7.data.Property;
59 import com.vaadin.v7.ui.CustomField;
60 import com.vaadin.v7.ui.Upload;
61 import com.vaadin.v7.ui.Upload.FailedEvent;
62 import com.vaadin.v7.ui.Upload.FailedListener;
63 import com.vaadin.v7.ui.Upload.FinishedEvent;
64 import com.vaadin.v7.ui.Upload.FinishedListener;
65 import com.vaadin.v7.ui.Upload.ProgressListener;
66 import com.vaadin.v7.ui.Upload.StartedEvent;
67 import com.vaadin.v7.ui.Upload.StartedListener;
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90 public abstract class AbstractUploadField<T extends UploadReceiver> extends CustomField<T> implements StartedListener, FinishedListener, ProgressListener, FailedListener, DropHandler, UploadField {
91
92 private static final Logger log = LoggerFactory.getLogger(AbstractUploadField.class);
93
94 private long maxUploadSize = Long.MAX_VALUE;
95
96 private String allowedMimeTypePattern = ".*";
97
98
99
100 private boolean interruptedDragAndDropUpload = false;
101
102 private Upload upload;
103
104 private DragAndDropWrapper dropZone;
105
106 private HasComponents root;
107
108 private UploadStatus uploadStatus = UploadStatus.READY;
109
110
111
112
113
114 protected abstract void buildEmptyLayout();
115
116
117
118
119
120
121
122 protected abstract void buildInProgressLayout(String uploadedFileMimeType);
123
124
125
126
127 protected abstract void refreshInProgressLayout(long readBytes, long contentLength, String fileName);
128
129
130
131
132
133 protected abstract void buildCompletedLayout();
134
135 protected abstract void displayUploadInterruptNote(InterruptionReason reason);
136
137 protected abstract void displayUploadFinishedNote(String fileName);
138
139 protected abstract void displayUploadFailedNote(String fileName);
140
141 @Override
142 public void setLocale(Locale locale) {
143 if (root != null) {
144 updateDisplay();
145 }
146 }
147
148 @Override
149 public void setPropertyDataSource(Property newDataSource) {
150 super.setPropertyDataSource(newDataSource);
151 createUpload();
152 }
153
154
155
156
157
158
159
160
161 protected void updateDisplay() {
162 if (getValue() != null) {
163 if (this.getValue().isEmpty()) {
164 buildEmptyLayout();
165 } else {
166 buildCompletedLayout();
167 }
168 markAsDirty();
169 }
170 }
171
172
173
174
175
176 protected void interruptUpload(InterruptionReason reason) {
177 displayUploadInterruptNote(reason);
178 if (upload.isUploading()) {
179 upload.interruptUpload();
180 } else {
181 setDragAndDropUploadInterrupted(true);
182 }
183
184 uploadStatus = UploadStatus.READY;
185 }
186
187 private void setDragAndDropUploadInterrupted(boolean isInterrupted) {
188 interruptedDragAndDropUpload = isInterrupted;
189 }
190
191 private boolean isDragAndDropUploadInterrupted() {
192 return interruptedDragAndDropUpload;
193 }
194
195
196
197
198 protected enum InterruptionReason {
199 USER, FILE_NOT_ALLOWED, FILE_SIZE;
200 }
201
202
203
204
205 protected enum UploadStatus {
206 READY, UPLOADING, FINISHED
207 }
208
209
210
211
212
213 protected boolean isValidFile(StartedEvent event) {
214 log.debug("Evaluate following regExp: {} against {}", allowedMimeTypePattern, event.getMIMEType());
215 return event.getMIMEType().matches(allowedMimeTypePattern);
216 }
217
218
219
220
221 private boolean postFileValidation() {
222
223
224 return true;
225 }
226
227
228
229
230 private void createUpload() {
231 this.upload = new Upload(null, getValue());
232 this.upload.addStartedListener(this);
233 this.upload.addFinishedListener(this);
234 this.upload.addProgressListener(this);
235 this.upload.setImmediate(true);
236 }
237
238
239
240
241 protected Upload getUpload() {
242 return this.upload;
243 }
244
245
246
247
248 protected DragAndDropWrapper getDropZone() {
249 return this.dropZone;
250 }
251
252
253
254
255 protected DragAndDropWrapper createDropZone(Component c) {
256 dropZone = new DragAndDropWrapper(c);
257 dropZone.setDropHandler(this);
258 return this.dropZone;
259 }
260
261
262
263
264 @Override
265 public void drop(DragAndDropEvent event) {
266
267
268 if (!UploadStatus.READY.equals(uploadStatus)) {
269 log.debug("Not ready to upload new file, skipping, is there any other file is uploading?");
270 return;
271 }
272
273
274 setDragAndDropUploadInterrupted(false);
275
276 final DragAndDropWrapper.WrapperTransferable transferable = (WrapperTransferable) event.getTransferable();
277 final Html5File[] files = transferable.getFiles();
278 if (files == null) {
279 return;
280 }
281
282
283 startPolling();
284
285 for (final Html5File html5File : files) {
286 html5File.setStreamVariable(new StreamVariable() {
287
288 private String name;
289 private String mime;
290
291 @Override
292 public OutputStream getOutputStream() {
293 return getValue().receiveUpload(name, mime);
294 }
295
296 @Override
297 public boolean listenProgress() {
298 return true;
299 }
300
301 @Override
302 public void onProgress(StreamingProgressEvent event) {
303 updateProgress(event.getBytesReceived(), event.getContentLength());
304 }
305
306 @Override
307 public void streamingStarted(StreamingStartEvent event) {
308 name = event.getFileName();
309 mime = event.getMimeType();
310 if (StringUtils.isEmpty(mime)) {
311 String extension = StringUtils.substringAfterLast(name, ".");
312 final String mimeType = MIMEMapping.getMIMEType(extension);
313 mime = mimeType == null ? "" : mimeType;
314 if(StringUtils.isEmpty(mime)) {
315 log.warn("Couldn't find mimeType in MIMEMappings for file extension: {}", extension);
316 }
317 }
318 StartedEvent startEvent = new StartedEvent(upload, name, mime, event.getContentLength());
319 uploadStarted(startEvent);
320 }
321
322 @Override
323 public void streamingFinished(StreamingEndEvent event) {
324 FinishedEvent uploadEvent = new FinishedEvent(upload, event.getFileName(), event.getMimeType(), event.getContentLength());
325 uploadFinished(uploadEvent);
326 }
327
328 @Override
329 public void streamingFailed(StreamingErrorEvent event) {
330 FailedEvent failedEvent = new FailedEvent(upload, event.getFileName(), event.getMimeType(), event.getContentLength());
331 uploadFailed(failedEvent);
332 }
333
334 @Override
335 public synchronized boolean isInterrupted() {
336 return isDragAndDropUploadInterrupted();
337 }
338
339
340 });
341 }
342 }
343
344
345
346
347 @Override
348 public AcceptCriterion getAcceptCriterion() {
349 return AcceptAll.get();
350 }
351
352
353
354
355
356 @Override
357 public void uploadStarted(StartedEvent event) {
358 if (isValidFile(event)) {
359 buildInProgressLayout(event.getMIMEType());
360
361
362 startPolling();
363 } else {
364 interruptUpload(InterruptionReason.FILE_NOT_ALLOWED);
365 }
366 uploadStatus = UploadStatus.UPLOADING;
367 }
368
369
370
371
372
373 @Override
374 public void updateProgress(long readBytes, long contentLength) {
375 if (readBytes > this.maxUploadSize || contentLength > this.maxUploadSize) {
376 this.interruptUpload(InterruptionReason.FILE_SIZE);
377 return;
378 }
379 refreshInProgressLayout(readBytes, contentLength, getValue().getLastFileName());
380 }
381
382
383
384
385
386
387
388
389
390 @Override
391 public void uploadFinished(FinishedEvent event) {
392
393 stopPolling();
394
395 if (event instanceof FailedEvent) {
396 uploadFailed((FailedEvent) event);
397 return;
398 }
399
400 if (!postFileValidation()) {
401 FailedEvent newEvent = new FailedEvent(upload, getValue().getFileName(), getValue().getMimeType(), getValue().getFileSize());
402 uploadFailed(newEvent);
403 return;
404 }
405 displayUploadFinishedNote(StringEscapeUtils.escapeHtml4(event.getFilename()));
406 this.getPropertyDataSource().setValue(event.getUpload().getReceiver());
407 buildCompletedLayout();
408 fireValueChange(false);
409 uploadStatus = UploadStatus.READY;
410 }
411
412 @Override
413 public void uploadFailed(FailedEvent event) {
414
415 stopPolling();
416
417 if (event.getReason() instanceof UploadException) {
418 displayUploadFailedNote(event.getFilename());
419 }
420 resetDataSource();
421 updateDisplay();
422 log.warn("Upload failed for file {} ", event.getFilename());
423 uploadStatus = UploadStatus.READY;
424 }
425
426 @Override
427 public Class getType() {
428 return UploadReceiver.class;
429 }
430
431 protected void resetDataSource() {
432 getValue().setValue(null);
433 getPropertyDataSource().setValue(getValue());
434 }
435
436 protected HasComponents getRootLayout() {
437 return this.root;
438 }
439
440 protected void setRootLayout(HasComponents root) {
441 this.root = root;
442 }
443
444 @Override
445 public void setAllowedMimeTypePattern(String allowedMimeTypePattern) {
446 this.allowedMimeTypePattern = allowedMimeTypePattern;
447
448 }
449
450 @Override
451 public void setMaxUploadSize(long maxUploadSize) {
452 this.maxUploadSize = maxUploadSize;
453 }
454
455 @Override
456 public void detach() {
457 if (upload.isUploading()) {
458 upload.interruptUpload();
459 } else {
460 setDragAndDropUploadInterrupted(true);
461 }
462 super.detach();
463 }
464
465 private void startPolling() {
466 UI ui = UI.getCurrent();
467 if (ui != null) {
468 ui.access(() -> UI.getCurrent().setPollInterval(200));
469 }
470 }
471
472 private void stopPolling() {
473 UI ui = UI.getCurrent();
474 if (ui != null) {
475 ui.access(() -> UI.getCurrent().setPollInterval(-1));
476 }
477 }
478
479 }