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