1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.exoplatform.wcm.connector;
18
19 import java.io.BufferedInputStream;
20 import java.io.File;
21 import java.io.FileInputStream;
22 import java.text.DateFormat;
23 import java.text.SimpleDateFormat;
24 import java.util.Date;
25 import java.util.GregorianCalendar;
26 import java.util.HashSet;
27 import java.util.Hashtable;
28 import java.util.Locale;
29 import java.util.Map;
30 import java.util.ResourceBundle;
31 import java.util.Set;
32
33 import javax.jcr.ItemExistsException;
34 import javax.jcr.Node;
35 import javax.servlet.http.HttpServletRequest;
36 import javax.ws.rs.core.CacheControl;
37 import javax.ws.rs.core.MediaType;
38 import javax.ws.rs.core.Response;
39 import javax.xml.parsers.DocumentBuilder;
40 import javax.xml.parsers.DocumentBuilderFactory;
41 import javax.xml.transform.dom.DOMSource;
42
43 import com.ibm.icu.text.Transliterator;
44 import org.apache.commons.lang.StringUtils;
45 import org.exoplatform.common.http.HTTPStatus;
46 import org.exoplatform.ecm.connector.fckeditor.FCKMessage;
47 import org.exoplatform.ecm.connector.fckeditor.FCKUtils;
48 import org.exoplatform.ecm.utils.lock.LockUtil;
49 import org.exoplatform.ecm.utils.text.Text;
50 import org.exoplatform.services.cms.documents.AutoVersionService;
51 import org.exoplatform.services.cms.impl.Utils;
52 import org.exoplatform.services.cms.jcrext.activity.ActivityCommonService;
53 import org.exoplatform.services.cms.mimetype.DMSMimeTypeResolver;
54 import org.exoplatform.services.cms.templates.TemplateService;
55 import org.exoplatform.services.listener.ListenerService;
56 import org.exoplatform.services.log.ExoLogger;
57 import org.exoplatform.services.log.Log;
58 import org.exoplatform.services.resources.ResourceBundleService;
59 import org.exoplatform.services.wcm.core.NodetypeConstant;
60 import org.exoplatform.services.wcm.publication.WCMPublicationService;
61 import org.exoplatform.services.wcm.utils.WCMCoreUtils;
62 import org.exoplatform.upload.UploadResource;
63 import org.exoplatform.upload.UploadService;
64 import org.exoplatform.upload.UploadService.UploadLimit;
65 import org.json.JSONObject;
66 import org.w3c.dom.Document;
67 import org.w3c.dom.Element;
68
69
70
71
72
73
74
75 public class FileUploadHandler {
76
77
78 private static final Log LOG = ExoLogger.getLogger(FileUploadHandler.class.getName());
79
80
81 public final static String UPLOAD_ACTION = "upload";
82
83
84 public final static String PROGRESS_ACTION = "progress";
85
86
87 public final static String ABORT_ACTION = "abort";
88
89
90 public final static String DELETE_ACTION = "delete";
91
92
93 public final static String SAVE_ACTION = "save";
94
95
96 public final static String SAVE_NEW_VERSION_ACTION = "saveNewVersion";
97
98
99 public final static String CHECK_EXIST= "exist";
100
101
102 public final static String REPLACE= "replace";
103
104 public final static String CREATE_VERSION = "createVersion";
105
106
107 public final static String KEEP_BOTH= "keepBoth";
108
109
110 private static final String LAST_MODIFIED_PROPERTY = "Last-Modified";
111
112
113 private static final String IF_MODIFIED_SINCE_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss z";
114
115 public final static String POST_CREATE_CONTENT_EVENT = "CmsService.event.postCreate";
116
117 private final String CONNECTOR_BUNDLE_LOCATION = "locale.wcm.resources.WCMResourceBundleConnector";
118 private final String AUTOVERSION_ERROR_MIME_TYPE = "DocumentAutoVersion.msg.WrongMimeType";
119
120
121 private UploadService uploadService;
122
123
124 ListenerService listenerService;
125
126 private ActivityCommonService activityService;
127
128
129 private FCKMessage fckMessage;
130
131
132 private Map<String, Long> uploadIdTimeMap;
133
134
135 private long UPLOAD_LIFE_TIME;
136
137
138
139
140 public FileUploadHandler() {
141 uploadService = WCMCoreUtils.getService(UploadService.class);
142 listenerService = WCMCoreUtils.getService(ListenerService.class);
143 activityService = WCMCoreUtils.getService(ActivityCommonService.class);
144 fckMessage = new FCKMessage();
145 uploadIdTimeMap = new Hashtable<String, Long>();
146 UPLOAD_LIFE_TIME = System.getProperty("MULTI_UPLOAD_LIFE_TIME") == null ? 600 :
147 Long.parseLong(System.getProperty("MULTI_UPLOAD_LIFE_TIME"));
148 }
149
150
151
152
153
154
155
156
157
158 public Response upload(HttpServletRequest servletRequest, String uploadId, Integer limit) throws Exception{
159 uploadService.addUploadLimit(uploadId, limit);
160 uploadService.createUploadResource(servletRequest);
161 uploadIdTimeMap.put(uploadId, System.currentTimeMillis());
162 CacheControl cacheControl = new CacheControl();
163 cacheControl.setNoCache(true);
164 cacheControl.setNoStore(true);
165 DateFormat dateFormat = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);
166
167
168 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
169 DocumentBuilder builder = factory.newDocumentBuilder();
170 Document doc = builder.newDocument();
171 Element rootElement = doc.createElement("html");
172 Element head = doc.createElement("head");
173 Element body = doc.createElement("body");
174 rootElement.appendChild(head);
175 rootElement.appendChild(body);
176 doc.appendChild(rootElement);
177
178 return Response.ok(new DOMSource(doc), MediaType.TEXT_XML)
179 .cacheControl(cacheControl)
180 .header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date()))
181 .build();
182 }
183
184
185
186
187
188
189
190
191
192
193
194 public Response checkStatus(String uploadId, String language) throws Exception {
195 CacheControl cacheControl = new CacheControl();
196 cacheControl.setNoCache(true);
197 cacheControl.setNoStore(true);
198 DateFormat dateFormat = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);
199
200 if ((StringUtils.isEmpty(uploadId)) || (uploadService.getUploadResource(uploadId) == null)) return null;
201
202
203 if (UploadResource.FAILED_STATUS == uploadService.getUploadResource(uploadId).getStatus()) {
204
205
206 uploadService.removeUploadResource(uploadId);
207 uploadIdTimeMap.remove(uploadId);
208
209 String uploadLimit = String.valueOf(uploadService.getUploadLimits().get(uploadId).getLimit());
210 Document fileExceedLimit =
211 fckMessage.createMessage(FCKMessage.FILE_EXCEED_LIMIT,
212 FCKMessage.ERROR,
213 language,
214 new String[]{uploadLimit});
215
216 return Response.ok(new DOMSource(fileExceedLimit), MediaType.TEXT_XML)
217 .cacheControl(cacheControl)
218 .header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date()))
219 .build();
220 }
221
222 return null;
223 }
224
225
226
227
228
229
230
231
232
233
234
235 public Response control(String uploadId, String action) throws Exception {
236 CacheControl cacheControl = new CacheControl();
237 cacheControl.setNoCache(true);
238 cacheControl.setNoStore(true);
239 DateFormat dateFormat = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);
240
241 if (FileUploadHandler.PROGRESS_ACTION.equals(action)) {
242 Document currentProgress = getProgress(uploadId);
243 return Response.ok(new DOMSource(currentProgress), MediaType.TEXT_XML)
244 .cacheControl(cacheControl)
245 .header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date()))
246 .build();
247 } else if (FileUploadHandler.ABORT_ACTION.equals(action)) {
248 uploadService.removeUploadResource(uploadId);
249 uploadIdTimeMap.remove(uploadId);
250 return Response.ok(null, MediaType.TEXT_XML)
251 .cacheControl(cacheControl)
252 .header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date()))
253 .build();
254 } else if (FileUploadHandler.DELETE_ACTION.equals(action)) {
255 uploadService.removeUploadResource(uploadId);
256 uploadIdTimeMap.remove(uploadId);
257 return Response.ok(null, MediaType.TEXT_XML)
258 .cacheControl(cacheControl)
259 .header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date()))
260 .build();
261 }
262 return Response.status(HTTPStatus.BAD_REQUEST)
263 .cacheControl(cacheControl)
264 .header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date()))
265 .build();
266 }
267
268
269
270
271
272
273
274
275
276
277 public Response checkExistence(Node parent, String fileName) throws Exception {
278 DMSMimeTypeResolver resolver = DMSMimeTypeResolver.getInstance();
279 CacheControl cacheControl = new CacheControl();
280 cacheControl.setNoCache(true);
281 DateFormat dateFormat = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);
282
283
284 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
285 DocumentBuilder builder = factory.newDocumentBuilder();
286 Document fileExistence = builder.newDocument();
287 fileName = Utils.cleanNameWithAccents(fileName);
288 Element rootElement = fileExistence.createElement(
289 parent.hasNode(fileName) ? "Existed" : "NotExisted");
290 if(parent.hasNode(fileName)){
291 Node existNode = parent.getNode(fileName);
292 if(existNode.isNodeType(NodetypeConstant.MIX_VERSIONABLE)){
293 rootElement.appendChild(fileExistence.createElement("Versioned"));
294 }
295 }
296 if(parent.isNodeType(NodetypeConstant.NT_FILE) &&
297 resolver.getMimeType(parent.getName()).equals(resolver.getMimeType(fileName))){
298 rootElement.appendChild(fileExistence.createElement("CanVersioning"));
299 }
300 fileExistence.appendChild(rootElement);
301
302 return Response.ok(new DOMSource(fileExistence), MediaType.TEXT_XML)
303 .cacheControl(cacheControl)
304 .header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date()))
305 .build();
306 }
307
308
309
310
311
312
313
314 public Response cleanName(String fileName) throws Exception {
315 CacheControl cacheControl = new CacheControl();
316 cacheControl.setNoCache(true);
317 DateFormat dateFormat = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);
318
319 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
320 DocumentBuilder builder = factory.newDocumentBuilder();
321 Document cleanedFilename = builder.newDocument();
322 fileName = Utils.cleanNameWithAccents(fileName);
323 Element rootElement = cleanedFilename.createElement("name");
324 cleanedFilename.appendChild(rootElement);
325 rootElement.setTextContent(fileName);
326 return Response.ok(new DOMSource(cleanedFilename), MediaType.TEXT_XML)
327 .cacheControl(cacheControl)
328 .header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date()))
329 .build();
330 }
331
332
333
334
335
336
337
338
339
340
341
342
343
344 public Response saveAsNTFile(Node parent,
345 String uploadId,
346 String fileName,
347 String language,
348 String siteName,
349 String userId) throws Exception {
350 return saveAsNTFile(parent, uploadId, fileName, language, siteName, userId, KEEP_BOTH);
351 }
352
353
354
355
356
357
358
359
360
361
362
363
364
365 public Response saveAsNTFile(Node parent,
366 String uploadId,
367 String fileName,
368 String language,
369 String siteName,
370 String userId,
371 String existenceAction) throws Exception {
372 return saveAsNTFile(parent, uploadId, fileName, language, siteName, userId, existenceAction,false);
373 }
374
375
376
377
378
379
380
381
382
383
384
385
386 public Response saveAsNTFile(Node parent,
387 String uploadId,
388 String fileName,
389 String language,
390 String siteName,
391 String userId,
392 String existenceAction,
393 boolean isNewVersion) throws Exception {
394 try {
395 CacheControl cacheControl = new CacheControl();
396 cacheControl.setNoCache(true);
397 UploadResource resource = uploadService.getUploadResource(uploadId);
398 DateFormat dateFormat = new SimpleDateFormat(IF_MODIFIED_SINCE_DATE_FORMAT);
399 if (parent == null) {
400 Document fileNotUploaded = fckMessage.createMessage(FCKMessage.FILE_NOT_UPLOADED,
401 FCKMessage.ERROR,
402 language,
403 null);
404 return Response.ok(new DOMSource(fileNotUploaded), MediaType.TEXT_XML)
405 .cacheControl(cacheControl)
406 .header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date()))
407 .build();
408 }
409 if (!FCKUtils.hasAddNodePermission(parent)) {
410 Object[] args = { parent.getPath() };
411 Document message = fckMessage.createMessage(FCKMessage.FILE_UPLOAD_RESTRICTION,
412 FCKMessage.ERROR,
413 language,
414 args);
415 return Response.ok(new DOMSource(message), MediaType.TEXT_XML)
416 .cacheControl(cacheControl)
417 .header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date()))
418 .build();
419 }
420 if ((fileName == null) || (fileName.length() == 0)) {
421 fileName = resource.getFileName();
422 }
423
424 if(parent.isLocked()) {
425 parent.getSession().addLockToken(LockUtil.getLockToken(parent));
426 }
427 if (parent.hasNode(fileName)) {
428
429
430
431
432
433
434
435
436
437 if (REPLACE.equals(existenceAction)) {
438
439 ListenerService listenerService = WCMCoreUtils.getService(ListenerService.class);
440 listenerService.broadcast(ActivityCommonService.FILE_REMOVE_ACTIVITY, parent, parent.getNode(fileName));
441 parent.getNode(fileName).remove();
442 parent.save();
443 }
444 }
445 AutoVersionService autoVersionService = WCMCoreUtils.getService(AutoVersionService.class);
446 String location = resource.getStoreLocation();
447
448 Node file = null;
449 Node jcrContent=null;
450 boolean fileCreated = false;
451 String exoTitle = fileName;
452
453 fileName = Utils.cleanNameWithAccents(fileName);
454 DMSMimeTypeResolver mimeTypeResolver = DMSMimeTypeResolver.getInstance();
455 String mimetype = mimeTypeResolver.getMimeType(resource.getFileName());
456 String nodeName = fileName;
457 int count = 0;
458 if(!CREATE_VERSION.equals(existenceAction) ||
459 (!parent.hasNode(fileName) && !CREATE_VERSION.equals(existenceAction))) {
460 if(parent.isNodeType(NodetypeConstant.NT_FILE)){
461 String mimeTypeParent = mimeTypeResolver.getMimeType(parent.getName());
462 if(mimetype != mimeTypeParent){
463 ResourceBundleService resourceBundleService = WCMCoreUtils.getService(ResourceBundleService.class);
464 ResourceBundle resourceBundle = resourceBundleService.getResourceBundle(CONNECTOR_BUNDLE_LOCATION, new Locale(language));
465 String errorMsg = resourceBundle.getString(AUTOVERSION_ERROR_MIME_TYPE);
466 errorMsg = errorMsg.replace("{0}", StringUtils.escape("<span style='font-weight:bold;'>" + parent.getName() + "</span>"));
467 JSONObject jsonObject = new JSONObject();
468 jsonObject.put("error_type", "ERROR_MIMETYPE");
469 jsonObject.put("error_message", errorMsg);
470 return Response.serverError().entity(jsonObject.toString()).build();
471 }
472 parent = parent.getParent();
473 }
474 do {
475 try {
476 file = parent.addNode(nodeName, FCKUtils.NT_FILE);
477 fileCreated = true;
478 } catch (ItemExistsException e) {
479 nodeName = increaseName(fileName, ++count);
480 }
481 } while (!fileCreated);
482
483 if(!file.isNodeType(NodetypeConstant.MIX_REFERENCEABLE)) {
484 file.addMixin(NodetypeConstant.MIX_REFERENCEABLE);
485 }
486
487 if(!file.isNodeType(NodetypeConstant.MIX_COMMENTABLE))
488 file.addMixin(NodetypeConstant.MIX_COMMENTABLE);
489
490 if(!file.isNodeType(NodetypeConstant.MIX_VOTABLE))
491 file.addMixin(NodetypeConstant.MIX_VOTABLE);
492
493 if(!file.isNodeType(NodetypeConstant.MIX_I18N))
494 file.addMixin(NodetypeConstant.MIX_I18N);
495
496 if(!file.hasProperty(NodetypeConstant.EXO_TITLE)) {
497 file.setProperty(NodetypeConstant.EXO_TITLE, exoTitle);
498 }
499 jcrContent = file.addNode("jcr:content","nt:resource");
500 }else if(parent.hasNode(nodeName)){
501 file = parent.getNode(nodeName);
502 autoVersionService.autoVersion(file,isNewVersion);
503 jcrContent = file.hasNode("jcr:content")?file.getNode("jcr:content"):file.addNode("jcr:content","nt:resource");
504 }else if(parent.isNodeType(NodetypeConstant.NT_FILE)){
505 file = parent;
506 autoVersionService.autoVersion(file,isNewVersion);
507 jcrContent = file.hasNode("jcr:content")?file.getNode("jcr:content"):file.addNode("jcr:content","nt:resource");
508 }
509
510 jcrContent.setProperty("jcr:lastModified", new GregorianCalendar());
511 jcrContent.setProperty("jcr:data", new BufferedInputStream(new FileInputStream(new File(location))));
512 jcrContent.setProperty("jcr:mimeType", mimetype);
513 if(fileCreated) {
514 file.getParent().save();
515 autoVersionService.autoVersion(file,isNewVersion);
516 }
517
518
519 uploadService.removeUploadResource(uploadId);
520 uploadIdTimeMap.remove(uploadId);
521 WCMPublicationService wcmPublicationService = WCMCoreUtils.getService(WCMPublicationService.class);
522 wcmPublicationService.updateLifecyleOnChangeContent(file, siteName, userId);
523
524 if (activityService.isBroadcastNTFileEvents(file) && !CREATE_VERSION.equals(existenceAction)) {
525 listenerService.broadcast(ActivityCommonService.FILE_CREATED_ACTIVITY, null, file);
526 }
527 file.save();
528 return Response.ok(createDOMResponse("Result", mimetype), MediaType.TEXT_XML)
529 .cacheControl(cacheControl)
530 .header(LAST_MODIFIED_PROPERTY, dateFormat.format(new Date()))
531 .build();
532 } catch (Exception exc) {
533 LOG.error(exc.getMessage(), exc);
534 return Response.serverError().entity(exc.getMessage()).build();
535 }
536 }
537
538 public boolean isDocumentNodeType(Node node) throws Exception {
539 TemplateService templateService = WCMCoreUtils.getService(TemplateService.class);
540 return templateService.isManagedNodeType(node.getPrimaryNodeType().getName());
541 }
542
543
544
545
546
547
548
549 private String increaseName(String origin, int count) {
550 int index = origin.indexOf('.');
551 if (index == -1) return origin + count;
552 return origin.substring(0, index) + count + origin.substring(index);
553 }
554
555
556
557
558
559 public long getUploadingFileCount() {
560 removeDeadUploads();
561 return uploadIdTimeMap.size();
562 }
563
564
565
566
567 private void removeDeadUploads() {
568 Set<String> removedIds = new HashSet<String>();
569 for (String id : uploadIdTimeMap.keySet()) {
570 if ((System.currentTimeMillis() - uploadIdTimeMap.get(id)) > UPLOAD_LIFE_TIME * 1000) {
571 removedIds.add(id);
572 }
573 }
574 for (String id : removedIds) {
575 uploadIdTimeMap.remove(id);
576 }
577 }
578
579
580
581
582
583
584
585
586
587 private Document getProgress(String uploadId) throws Exception {
588 UploadResource resource = uploadService.getUploadResource(uploadId);
589 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
590 DocumentBuilder builder = factory.newDocumentBuilder();
591 Document doc = builder.newDocument();
592 Double percent = 0.0;
593 if(resource != null) {
594 if (resource.getStatus() == UploadResource.UPLOADING_STATUS) {
595 percent = (resource.getUploadedSize() * 100) / resource.getEstimatedSize();
596 } else {
597 percent = 100.0;
598 }
599 }
600 Element rootElement = doc.createElement("UploadProgress");
601 rootElement.setAttribute("uploadId", uploadId);
602 rootElement.setAttribute("fileName", resource == null ? "" : resource.getFileName());
603 rootElement.setAttribute("percent", percent.intValue() + "");
604 rootElement.setAttribute("uploadedSize", resource == null ? "0" : resource.getUploadedSize() + "");
605 rootElement.setAttribute("totalSize", resource == null ? "0" : resource.getEstimatedSize() + "");
606 rootElement.setAttribute("fileType", resource == null ? "null" : resource.getMimeType() + "");
607 UploadLimit limit = uploadService.getUploadLimits().get(uploadId);
608 if (limit != null) {
609 rootElement.setAttribute("limit", limit.getLimit() + "");
610 rootElement.setAttribute("unit", limit.getUnit() + "");
611 }
612 doc.appendChild(rootElement);
613 return doc;
614 }
615
616
617
618
619
620
621
622 private DOMSource createDOMResponse(String name, String mimeType) throws Exception {
623 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
624 DocumentBuilder builder = factory.newDocumentBuilder();
625 Document doc = builder.newDocument();
626 Element rootElement = doc.createElement(name);
627 rootElement.setAttribute("mimetype", mimeType);
628 doc.appendChild(rootElement);
629 return new DOMSource(doc);
630 }
631
632 }