001/* 002 * (C) Copyright 2006-2007 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Nuxeo - initial API and implementation 018 * 019 * $Id$ 020 */ 021 022package org.nuxeo.ecm.webapp.clipboard; 023 024import java.io.File; 025import java.io.IOException; 026import java.io.Serializable; 027import java.util.ArrayList; 028import java.util.Arrays; 029import java.util.HashMap; 030import java.util.HashSet; 031import java.util.LinkedList; 032import java.util.List; 033import java.util.Map; 034import java.util.Set; 035 036import javax.faces.context.FacesContext; 037import javax.servlet.http.HttpServletRequest; 038 039import org.apache.commons.logging.Log; 040import org.apache.commons.logging.LogFactory; 041import org.jboss.seam.annotations.Factory; 042import org.jboss.seam.annotations.In; 043import org.jboss.seam.annotations.Name; 044import org.jboss.seam.annotations.Scope; 045import org.jboss.seam.annotations.remoting.WebRemote; 046import org.jboss.seam.annotations.web.RequestParameter; 047import org.jboss.seam.core.Events; 048import org.jboss.seam.faces.FacesMessages; 049import org.jboss.seam.international.LocaleSelector; 050import org.jboss.seam.international.StatusMessage; 051import org.nuxeo.ecm.core.api.CoreSession; 052import org.nuxeo.ecm.core.api.DocumentModel; 053import org.nuxeo.ecm.core.api.DocumentModelList; 054import org.nuxeo.ecm.core.api.DocumentRef; 055import org.nuxeo.ecm.core.api.IdRef; 056import org.nuxeo.ecm.core.api.LifeCycleConstants; 057import org.nuxeo.ecm.core.api.NuxeoException; 058import org.nuxeo.ecm.core.api.security.SecurityConstants; 059import org.nuxeo.ecm.core.io.download.DownloadService; 060import org.nuxeo.ecm.core.schema.FacetNames; 061import org.nuxeo.ecm.core.schema.SchemaManager; 062import org.nuxeo.ecm.platform.actions.Action; 063import org.nuxeo.ecm.platform.types.TypeManager; 064import org.nuxeo.ecm.platform.ui.web.api.NavigationContext; 065import org.nuxeo.ecm.platform.ui.web.api.WebActions; 066import org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants; 067import org.nuxeo.ecm.platform.ui.web.cache.SeamCacheHelper; 068import org.nuxeo.ecm.platform.ui.web.tag.fn.Functions; 069import org.nuxeo.ecm.platform.ui.web.util.BaseURL; 070import org.nuxeo.ecm.platform.ui.web.util.ComponentUtils; 071import org.nuxeo.ecm.webapp.documentsLists.DocumentsListDescriptor; 072import org.nuxeo.ecm.webapp.documentsLists.DocumentsListsManager; 073import org.nuxeo.ecm.webapp.helpers.EventManager; 074import org.nuxeo.ecm.webapp.helpers.EventNames; 075import org.nuxeo.runtime.api.Framework; 076 077import static org.jboss.seam.ScopeType.EVENT; 078import static org.jboss.seam.ScopeType.SESSION; 079 080/** 081 * This is the action listener behind the copy/paste template that knows how to copy/paste the selected user data to the 082 * target action listener, and also create/remove the corresponding objects into the backend. 083 * 084 * @author <a href="mailto:rcaraghin@nuxeo.com">Razvan Caraghin</a> 085 */ 086@Name("clipboardActions") 087@Scope(SESSION) 088public class ClipboardActionsBean implements ClipboardActions, Serializable { 089 090 private static final long serialVersionUID = -2407222456116573225L; 091 092 private static final Log log = LogFactory.getLog(ClipboardActionsBean.class); 093 094 @In(create = true, required = false) 095 protected FacesMessages facesMessages; 096 097 @In(create = true) 098 protected Map<String, String> messages; 099 100 @In(create = true, required = false) 101 protected transient CoreSession documentManager; 102 103 @In(create = true) 104 protected transient DocumentsListsManager documentsListsManager; 105 106 @In(create = true) 107 protected TypeManager typeManager; 108 109 @In(create = true) 110 protected NavigationContext navigationContext; 111 112 @In(create = true) 113 protected transient WebActions webActions; // it is serializable 114 115 @In(create = true) 116 protected transient LocaleSelector localeSelector; 117 118 @RequestParameter() 119 protected String workListDocId; 120 121 private String currentSelectedList; 122 123 private String previouslySelectedList; 124 125 private transient List<String> availableLists; 126 127 private transient List<DocumentsListDescriptor> descriptorsForAvailableLists; 128 129 private Boolean canEditSelectedDocs; 130 131 private transient Map<String, List<Action>> actionCache; 132 133 public void releaseClipboardableDocuments() { 134 } 135 136 public boolean isInitialized() { 137 return documentManager != null; 138 } 139 140 public void putSelectionInWorkList(Boolean forceAppend) { 141 canEditSelectedDocs = null; 142 if (!documentsListsManager.isWorkingListEmpty(DocumentsListsManager.CURRENT_DOCUMENT_SELECTION)) { 143 putSelectionInWorkList( 144 documentsListsManager.getWorkingList(DocumentsListsManager.CURRENT_DOCUMENT_SELECTION), 145 forceAppend); 146 autoSelectCurrentList(DocumentsListsManager.DEFAULT_WORKING_LIST); 147 } else { 148 log.debug("No selectable Documents in context to process copy on..."); 149 } 150 log.debug("add to worklist processed..."); 151 } 152 153 public void putSelectionInWorkList() { 154 putSelectionInWorkList(false); 155 } 156 157 public void putSelectionInDefaultWorkList() { 158 canEditSelectedDocs = null; 159 if (!documentsListsManager.isWorkingListEmpty(DocumentsListsManager.CURRENT_DOCUMENT_SELECTION)) { 160 List<DocumentModel> docsList = documentsListsManager.getWorkingList(DocumentsListsManager.CURRENT_DOCUMENT_SELECTION); 161 Object[] params = { docsList.size() }; 162 facesMessages.add(StatusMessage.Severity.INFO, "#0 " + messages.get("n_copied_docs"), params); 163 documentsListsManager.addToWorkingList(DocumentsListsManager.DEFAULT_WORKING_LIST, docsList); 164 165 // auto select clipboard 166 autoSelectCurrentList(DocumentsListsManager.DEFAULT_WORKING_LIST); 167 168 } else { 169 log.debug("No selectable Documents in context to process copy on..."); 170 } 171 log.debug("add to worklist processed..."); 172 } 173 174 @WebRemote 175 public void putInClipboard(String docId) { 176 DocumentModel doc = documentManager.getDocument(new IdRef(docId)); 177 documentsListsManager.addToWorkingList(DocumentsListsManager.CLIPBOARD, doc); 178 Object[] params = { 1 }; 179 facesMessages.add(StatusMessage.Severity.INFO, "#0 " + messages.get("n_copied_docs"), params); 180 181 autoSelectCurrentList(DocumentsListsManager.CLIPBOARD); 182 } 183 184 public void putSelectionInClipboard() { 185 canEditSelectedDocs = null; 186 if (!documentsListsManager.isWorkingListEmpty(DocumentsListsManager.CURRENT_DOCUMENT_SELECTION)) { 187 List<DocumentModel> docsList = documentsListsManager.getWorkingList(DocumentsListsManager.CURRENT_DOCUMENT_SELECTION); 188 Object[] params = { docsList.size() }; 189 facesMessages.add(StatusMessage.Severity.INFO, "#0 " + messages.get("n_copied_docs"), params); 190 191 documentsListsManager.addToWorkingList(DocumentsListsManager.CLIPBOARD, docsList); 192 193 // auto select clipboard 194 autoSelectCurrentList(DocumentsListsManager.CLIPBOARD); 195 196 } else { 197 log.debug("No selectable Documents in context to process copy on..."); 198 } 199 log.debug("add to worklist processed..."); 200 } 201 202 public void putSelectionInWorkList(List<DocumentModel> docsList) { 203 putSelectionInWorkList(docsList, false); 204 } 205 206 public void putSelectionInWorkList(List<DocumentModel> docsList, Boolean forceAppend) { 207 canEditSelectedDocs = null; 208 if (null != docsList) { 209 Object[] params = { docsList.size() }; 210 facesMessages.add(StatusMessage.Severity.INFO, "#0 " + messages.get("n_added_to_worklist_docs"), params); 211 212 // Add to the default working list 213 documentsListsManager.addToWorkingList(getCurrentSelectedListName(), docsList, forceAppend); 214 log.debug("Elements copied to clipboard..."); 215 216 } else { 217 log.debug("No copiedDocs to process copy on..."); 218 } 219 220 log.debug("add to worklist processed..."); 221 } 222 223 @Deprecated 224 public void copySelection(List<DocumentModel> copiedDocs) { 225 if (null != copiedDocs) { 226 Object[] params = { copiedDocs.size() }; 227 facesMessages.add(StatusMessage.Severity.INFO, "#0 " + messages.get("n_copied_docs"), params); 228 229 // clipboard.copy(copiedDocs); 230 231 // Reset + Add to clipboard list 232 documentsListsManager.resetWorkingList(DocumentsListsManager.CLIPBOARD); 233 documentsListsManager.addToWorkingList(DocumentsListsManager.CLIPBOARD, copiedDocs); 234 235 // Add to the default working list 236 documentsListsManager.addToWorkingList(copiedDocs); 237 log.debug("Elements copied to clipboard..."); 238 239 } else { 240 log.debug("No copiedDocs to process copy on..."); 241 } 242 243 log.debug("Copy processed..."); 244 } 245 246 public boolean exists(DocumentRef ref) { 247 return ref != null && documentManager.exists(ref); 248 } 249 250 public String removeWorkListItem(DocumentRef ref) { 251 DocumentModel doc = null; 252 if (exists(ref)) { 253 doc = documentManager.getDocument(ref); 254 } else { // document was permanently deleted so let's use the one in the work list 255 List<DocumentModel> workingListDocs = documentsListsManager.getWorkingList(getCurrentSelectedListName()); 256 for (DocumentModel wDoc : workingListDocs) { 257 if (wDoc.getRef().equals(ref)) { 258 doc = wDoc; 259 } 260 } 261 } 262 documentsListsManager.removeFromWorkingList(getCurrentSelectedListName(), doc); 263 return null; 264 } 265 266 public String clearWorkingList() { 267 documentsListsManager.resetWorkingList(getCurrentSelectedListName()); 268 return null; 269 } 270 271 public String pasteDocumentList(String listName) { 272 return pasteDocumentList(documentsListsManager.getWorkingList(listName)); 273 } 274 275 public String pasteDocumentListInside(String listName, String docId) { 276 return pasteDocumentListInside(documentsListsManager.getWorkingList(listName), docId); 277 } 278 279 public String pasteDocumentList(List<DocumentModel> docPaste) { 280 DocumentModel currentDocument = navigationContext.getCurrentDocument(); 281 if (null != docPaste) { 282 List<DocumentModel> newDocs = recreateDocumentsWithNewParent(getParent(currentDocument), docPaste); 283 284 Object[] params = { newDocs.size() }; 285 facesMessages.add(StatusMessage.Severity.INFO, "#0 " + messages.get("n_pasted_docs"), params); 286 287 EventManager.raiseEventsOnDocumentSelected(currentDocument); 288 Events.instance().raiseEvent(EventNames.DOCUMENT_CHILDREN_CHANGED, currentDocument); 289 290 log.debug("Elements pasted and created into the backend..."); 291 } else { 292 log.debug("No docPaste to process paste on..."); 293 } 294 295 return null; 296 } 297 298 public String pasteDocumentListInside(List<DocumentModel> docPaste, String docId) { 299 DocumentModel targetDoc = documentManager.getDocument(new IdRef(docId)); 300 if (null != docPaste) { 301 List<DocumentModel> newDocs = recreateDocumentsWithNewParent(targetDoc, docPaste); 302 303 Object[] params = { newDocs.size() }; 304 facesMessages.add(StatusMessage.Severity.INFO, "#0 " + messages.get("n_pasted_docs"), params); 305 306 EventManager.raiseEventsOnDocumentSelected(targetDoc); 307 Events.instance().raiseEvent(EventNames.DOCUMENT_CHILDREN_CHANGED, targetDoc); 308 309 log.debug("Elements pasted and created into the backend..."); 310 } else { 311 log.debug("No docPaste to process paste on..."); 312 } 313 314 return null; 315 } 316 317 public List<DocumentModel> moveDocumentsToNewParent(DocumentModel destFolder, List<DocumentModel> docs) 318 { 319 DocumentRef destFolderRef = destFolder.getRef(); 320 boolean destinationIsDeleted = LifeCycleConstants.DELETED_STATE.equals(destFolder.getCurrentLifeCycleState()); 321 List<DocumentModel> newDocs = new ArrayList<DocumentModel>(); 322 StringBuilder sb = new StringBuilder(); 323 for (DocumentModel docModel : docs) { 324 DocumentRef sourceFolderRef = docModel.getParentRef(); 325 326 String sourceType = docModel.getType(); 327 boolean canRemoveDoc = documentManager.hasPermission(sourceFolderRef, SecurityConstants.REMOVE_CHILDREN); 328 boolean canPasteInCurrentFolder = typeManager.isAllowedSubType(sourceType, destFolder.getType(), 329 navigationContext.getCurrentDocument()); 330 boolean sameFolder = sourceFolderRef.equals(destFolderRef); 331 if (canRemoveDoc && canPasteInCurrentFolder && !sameFolder) { 332 if (destinationIsDeleted) { 333 if (checkDeletedState(docModel)) { 334 DocumentModel newDoc = documentManager.move(docModel.getRef(), destFolderRef, null); 335 setDeleteState(newDoc); 336 newDocs.add(newDoc); 337 } else { 338 addWarnMessage(sb, docModel); 339 } 340 } else { 341 DocumentModel newDoc = documentManager.move(docModel.getRef(), destFolderRef, null); 342 newDocs.add(newDoc); 343 } 344 } 345 } 346 documentManager.save(); 347 348 if (sb.length() > 0) { 349 facesMessages.add(StatusMessage.Severity.WARN, sb.toString()); 350 } 351 return newDocs; 352 } 353 354 public String moveDocumentList(String listName, String docId) { 355 List<DocumentModel> docs = documentsListsManager.getWorkingList(listName); 356 DocumentModel targetDoc = documentManager.getDocument(new IdRef(docId)); 357 // Get all parent folders 358 Set<DocumentRef> parentRefs = new HashSet<DocumentRef>(); 359 for (DocumentModel doc : docs) { 360 parentRefs.add(doc.getParentRef()); 361 } 362 363 List<DocumentModel> newDocs = moveDocumentsToNewParent(targetDoc, docs); 364 365 documentsListsManager.resetWorkingList(listName); 366 367 Object[] params = { newDocs.size() }; 368 facesMessages.add(StatusMessage.Severity.INFO, "#0 " + messages.get("n_moved_docs"), params); 369 370 EventManager.raiseEventsOnDocumentSelected(targetDoc); 371 Events.instance().raiseEvent(EventNames.DOCUMENT_CHILDREN_CHANGED, targetDoc); 372 373 // Send event to all initial parents 374 for (DocumentRef docRef : parentRefs) { 375 Events.instance().raiseEvent(EventNames.DOCUMENT_CHILDREN_CHANGED, documentManager.getDocument(docRef)); 376 } 377 378 log.debug("Elements moved and created into the backend..."); 379 380 return null; 381 } 382 383 public String moveDocumentList(String listName) { 384 DocumentModel currentDocument = navigationContext.getCurrentDocument(); 385 return moveDocumentList(listName, currentDocument.getId()); 386 } 387 388 public String moveWorkingList() { 389 try { 390 moveDocumentList(getCurrentSelectedListName()); 391 } catch (NuxeoException e) { 392 log.error("moveWorkingList failed" + e.getMessage(), e); 393 facesMessages.add(StatusMessage.Severity.WARN, messages.get("invalid_operation")); 394 } 395 return null; 396 } 397 398 public String pasteWorkingList() { 399 try { 400 pasteDocumentList(getCurrentSelectedList()); 401 } catch (NuxeoException e) { 402 log.error("pasteWorkingList failed" + e.getMessage(), e); 403 facesMessages.add(StatusMessage.Severity.WARN, messages.get("invalid_operation")); 404 } 405 return null; 406 } 407 408 public String pasteClipboard() { 409 try { 410 pasteDocumentList(DocumentsListsManager.CLIPBOARD); 411 returnToPreviouslySelectedList(); 412 } catch (NuxeoException e) { 413 log.error("pasteClipboard failed" + e.getMessage(), e); 414 facesMessages.add(StatusMessage.Severity.WARN, messages.get("invalid_operation")); 415 416 } 417 return null; 418 } 419 420 @WebRemote 421 public String pasteClipboardInside(String docId) { 422 pasteDocumentListInside(DocumentsListsManager.CLIPBOARD, docId); 423 return null; 424 } 425 426 @WebRemote 427 public String moveClipboardInside(String docId) { 428 moveDocumentList(DocumentsListsManager.CLIPBOARD, docId); 429 return null; 430 } 431 432 /** 433 * Creates the documents in the backend under the target parent. 434 */ 435 protected List<DocumentModel> recreateDocumentsWithNewParent(DocumentModel parent, List<DocumentModel> documents) 436 { 437 438 List<DocumentModel> newDocuments = new ArrayList<DocumentModel>(); 439 440 if (null == parent || null == documents) { 441 log.error("Null params received, returning..."); 442 return newDocuments; 443 } 444 445 List<DocumentModel> documentsToPast = new LinkedList<DocumentModel>(); 446 447 // filter list on content type 448 for (DocumentModel doc : documents) { 449 if (typeManager.isAllowedSubType(doc.getType(), parent.getType(), navigationContext.getCurrentDocument())) { 450 documentsToPast.add(doc); 451 } 452 } 453 454 // copying proxy or document 455 boolean isPublishSpace = isPublishSpace(parent); 456 boolean destinationIsDeleted = LifeCycleConstants.DELETED_STATE.equals(parent.getCurrentLifeCycleState()); 457 List<DocumentRef> docRefs = new ArrayList<DocumentRef>(); 458 List<DocumentRef> proxyRefs = new ArrayList<DocumentRef>(); 459 StringBuilder sb = new StringBuilder(); 460 for (DocumentModel doc : documentsToPast) { 461 if (destinationIsDeleted && !checkDeletedState(doc)) { 462 addWarnMessage(sb, doc); 463 } else if (doc.isProxy() && !isPublishSpace) { 464 // in a non-publish space, we want to expand proxies into 465 // normal docs 466 proxyRefs.add(doc.getRef()); 467 } else { 468 // copy as is 469 docRefs.add(doc.getRef()); 470 } 471 } 472 if (!proxyRefs.isEmpty()) { 473 newDocuments.addAll(documentManager.copyProxyAsDocument(proxyRefs, parent.getRef(), true)); 474 } 475 if (!docRefs.isEmpty()) { 476 newDocuments.addAll(documentManager.copy(docRefs, parent.getRef(), true)); 477 } 478 if (destinationIsDeleted) { 479 for (DocumentModel d : newDocuments) { 480 setDeleteState(d); 481 } 482 } 483 documentManager.save(); 484 if (sb.length() > 0) { 485 facesMessages.add(StatusMessage.Severity.WARN, sb.toString()); 486 } 487 return newDocuments; 488 } 489 490 protected boolean checkDeletedState(DocumentModel doc) { 491 if (LifeCycleConstants.DELETED_STATE.equals(doc.getCurrentLifeCycleState())) { 492 return true; 493 } 494 if (doc.getAllowedStateTransitions().contains(LifeCycleConstants.DELETE_TRANSITION)) { 495 return true; 496 } 497 return false; 498 } 499 500 protected void setDeleteState(DocumentModel doc) { 501 if (doc.getAllowedStateTransitions().contains(LifeCycleConstants.DELETE_TRANSITION)) { 502 doc.followTransition(LifeCycleConstants.DELETE_TRANSITION); 503 } 504 } 505 506 protected void addWarnMessage(StringBuilder sb, DocumentModel doc) { 507 if (sb.length() == 0) { 508 sb.append(messages.get("document_no_deleted_state")); 509 sb.append("'").append(doc.getTitle()).append("'"); 510 } else { 511 sb.append(", '").append(doc.getTitle()).append("'"); 512 } 513 } 514 515 /** 516 * Check if the container is a publish space. If this is not the case, a proxy copied to it will be recreated as a 517 * new document. 518 */ 519 protected boolean isPublishSpace(DocumentModel container) { 520 SchemaManager schemaManager = Framework.getService(SchemaManager.class); 521 Set<String> publishSpaces = schemaManager.getDocumentTypeNamesForFacet(FacetNames.PUBLISH_SPACE); 522 if (publishSpaces == null || publishSpaces.isEmpty()) { 523 publishSpaces = new HashSet<String>(); 524 } 525 return publishSpaces.contains(container.getType()); 526 } 527 528 /** 529 * Gets the parent document under the paste should be performed. 530 * <p> 531 * Rules: 532 * <p> 533 * In general the currentDocument is the parent. Exceptions to this rule: when the currentDocument is a domain or 534 * null. If Domain then content root is the parent. If null is passed, then the JCR root is taken as parent. 535 */ 536 protected DocumentModel getParent(DocumentModel currentDocument) { 537 538 if (currentDocument.isFolder()) { 539 return currentDocument; 540 } 541 542 DocumentModelList parents = navigationContext.getCurrentPath(); 543 for (int i = parents.size() - 1; i >= 0; i--) { 544 DocumentModel parent = parents.get(i); 545 if (parent.isFolder()) { 546 return parent; 547 } 548 } 549 550 return null; 551 } 552 553 @Factory(value = "isCurrentWorkListEmpty", scope = EVENT) 554 public boolean factoryForIsCurrentWorkListEmpty() { 555 return isWorkListEmpty(); 556 } 557 558 public boolean isWorkListEmpty() { 559 return documentsListsManager.isWorkingListEmpty(getCurrentSelectedListName()); 560 } 561 562 public String exportWorklistAsZip() { 563 return exportWorklistAsZip(documentsListsManager.getWorkingList(getCurrentSelectedListName())); 564 } 565 566 public String exportAllBlobsFromWorkingListAsZip() { 567 return exportWorklistAsZip(); 568 } 569 570 public String exportMainBlobFromWorkingListAsZip() { 571 return exportWorklistAsZip(); 572 } 573 574 public String exportWorklistAsZip(List<DocumentModel> documents) { 575 return exportWorklistAsZip(documents, true); 576 } 577 578 public String exportWorklistAsZip(DocumentModel document) { 579 return exportWorklistAsZip(Arrays.asList(new DocumentModel[] { document }), true); 580 } 581 582 /** 583 * Checks if copy action is available in the context of the current Document. 584 * <p> 585 * Condition: the list of selected documents is not empty. 586 */ 587 public boolean getCanCopy() { 588 if (navigationContext.getCurrentDocument() == null) { 589 return false; 590 } 591 return !documentsListsManager.isWorkingListEmpty(DocumentsListsManager.CURRENT_DOCUMENT_SELECTION); 592 } 593 594 /** 595 * Checks if the Paste action is available in the context of the current Document. Conditions: 596 * <p> 597 * <ul> 598 * <li>list is not empty 599 * <li>user has the needed permissions on the current document 600 * <li>the content of the list can be added as children of the current document 601 * </ul> 602 */ 603 public boolean getCanPaste(String listName) { 604 605 DocumentModel currentDocument = navigationContext.getCurrentDocument(); 606 607 if (documentsListsManager.isWorkingListEmpty(listName) || currentDocument == null) { 608 return false; 609 } 610 611 DocumentModel pasteTarget = getParent(navigationContext.getCurrentDocument()); 612 if (pasteTarget == null) { 613 // parent may be unreachable (right inheritance blocked) 614 return false; 615 } 616 if (!documentManager.hasPermission(pasteTarget.getRef(), SecurityConstants.ADD_CHILDREN)) { 617 return false; 618 } else { 619 // filter on allowed content types 620 // see if at least one doc can be pasted 621 // String pasteTypeName = clipboard.getClipboardDocumentType(); 622 List<String> pasteTypesName = documentsListsManager.getWorkingListTypes(listName); 623 for (String pasteTypeName : pasteTypesName) { 624 if (typeManager.isAllowedSubType(pasteTypeName, pasteTarget.getType(), 625 navigationContext.getCurrentDocument())) { 626 return true; 627 } 628 } 629 return false; 630 } 631 } 632 633 public boolean getCanPasteInside(String listName, DocumentModel document) { 634 if (documentsListsManager.isWorkingListEmpty(listName) || document == null) { 635 return false; 636 } 637 638 if (!documentManager.hasPermission(document.getRef(), SecurityConstants.ADD_CHILDREN)) { 639 return false; 640 } else { 641 // filter on allowed content types 642 // see if at least one doc can be pasted 643 // String pasteTypeName = clipboard.getClipboardDocumentType(); 644 List<String> pasteTypesName = documentsListsManager.getWorkingListTypes(listName); 645 for (String pasteTypeName : pasteTypesName) { 646 if (typeManager.isAllowedSubType(pasteTypeName, document.getType(), 647 navigationContext.getCurrentDocument())) { 648 return true; 649 } 650 } 651 return false; 652 } 653 } 654 655 /** 656 * Checks if the Move action is available in the context of the document document. Conditions: 657 * <p> 658 * <ul> 659 * <li>list is not empty 660 * <li>user has the needed permissions on the document 661 * <li>an element in the list can be removed from its folder and added as child of the current document 662 * </ul> 663 */ 664 public boolean getCanMoveInside(String listName, DocumentModel document) { 665 if (documentsListsManager.isWorkingListEmpty(listName) || document == null) { 666 return false; 667 } 668 DocumentRef destFolderRef = document.getRef(); 669 DocumentModel destFolder = document; 670 if (!documentManager.hasPermission(destFolderRef, SecurityConstants.ADD_CHILDREN)) { 671 return false; 672 } else { 673 // filter on allowed content types 674 // see if at least one doc can be removed and pasted 675 for (DocumentModel docModel : documentsListsManager.getWorkingList(listName)) { 676 // skip deleted documents 677 if (!exists(docModel.getRef())) { 678 continue; 679 } 680 DocumentRef sourceFolderRef = docModel.getParentRef(); 681 String sourceType = docModel.getType(); 682 boolean canRemoveDoc = documentManager.hasPermission(sourceFolderRef, 683 SecurityConstants.REMOVE_CHILDREN); 684 boolean canPasteInCurrentFolder = typeManager.isAllowedSubType(sourceType, destFolder.getType(), 685 navigationContext.getCurrentDocument()); 686 boolean sameFolder = sourceFolderRef.equals(destFolderRef); 687 if (canRemoveDoc && canPasteInCurrentFolder && !sameFolder) { 688 return true; 689 } 690 } 691 return false; 692 } 693 } 694 695 /** 696 * Checks if the Move action is available in the context of the current Document. Conditions: 697 * <p> 698 * <ul> 699 * <li>list is not empty 700 * <li>user has the needed permissions on the current document 701 * <li>an element in the list can be removed from its folder and added as child of the current document 702 * </ul> 703 */ 704 public boolean getCanMove(String listName) { 705 DocumentModel currentDocument = navigationContext.getCurrentDocument(); 706 return getCanMoveInside(listName, currentDocument); 707 } 708 709 public boolean getCanPasteWorkList() { 710 return getCanPaste(getCurrentSelectedListName()); 711 } 712 713 public boolean getCanMoveWorkingList() { 714 return getCanMove(getCurrentSelectedListName()); 715 } 716 717 public boolean getCanPasteFromClipboard() { 718 return getCanPaste(DocumentsListsManager.CLIPBOARD); 719 } 720 721 public boolean getCanPasteFromClipboardInside(DocumentModel document) { 722 return getCanPasteInside(DocumentsListsManager.CLIPBOARD, document); 723 } 724 725 public boolean getCanMoveFromClipboardInside(DocumentModel document) { 726 return getCanMoveInside(DocumentsListsManager.CLIPBOARD, document); 727 } 728 729 public void setCurrentSelectedList(String listId) { 730 if (listId != null && !listId.equals(currentSelectedList)) { 731 currentSelectedList = listId; 732 canEditSelectedDocs = null; 733 } 734 } 735 736 @RequestParameter() 737 String listIdToSelect; 738 739 public void selectList() { 740 if (listIdToSelect != null) { 741 setCurrentSelectedList(listIdToSelect); 742 } 743 } 744 745 public List<DocumentModel> getCurrentSelectedList() { 746 return documentsListsManager.getWorkingList(getCurrentSelectedListName()); 747 } 748 749 public String getCurrentSelectedListName() { 750 if (currentSelectedList == null) { 751 if (!getAvailableLists().isEmpty()) { 752 setCurrentSelectedList(availableLists.get(0)); 753 } 754 } 755 return currentSelectedList; 756 } 757 758 public String getCurrentSelectedListTitle() { 759 String title = null; 760 String listName = getCurrentSelectedListName(); 761 if (listName != null) { 762 DocumentsListDescriptor desc = documentsListsManager.getWorkingListDescriptor(listName); 763 if (desc != null) { 764 title = desc.getTitle(); 765 } 766 } 767 return title; 768 } 769 770 public List<String> getAvailableLists() { 771 if (availableLists == null) { 772 availableLists = documentsListsManager.getWorkingListNamesForCategory("CLIPBOARD"); 773 } 774 return availableLists; 775 } 776 777 public List<DocumentsListDescriptor> getDescriptorsForAvailableLists() { 778 if (descriptorsForAvailableLists == null) { 779 List<String> availableLists = getAvailableLists(); 780 descriptorsForAvailableLists = new ArrayList<DocumentsListDescriptor>(); 781 for (String lName : availableLists) { 782 descriptorsForAvailableLists.add(documentsListsManager.getWorkingListDescriptor(lName)); 783 } 784 } 785 return descriptorsForAvailableLists; 786 } 787 788 public List<Action> getActionsForCurrentList() { 789 String lstName = getCurrentSelectedListName(); 790 if (isWorkListEmpty()) { 791 // we use cache here since this is a very common case ... 792 if (actionCache == null) { 793 actionCache = new HashMap<String, List<Action>>(); 794 } 795 if (!actionCache.containsKey(lstName)) { 796 actionCache.put(lstName, webActions.getActionsList(lstName + "_LIST")); 797 } 798 return actionCache.get(lstName); 799 } else { 800 return webActions.getActionsList(lstName + "_LIST"); 801 } 802 } 803 804 public List<Action> getActionsForSelection() { 805 return webActions.getActionsList(DocumentsListsManager.CURRENT_DOCUMENT_SELECTION + "_LIST", false); 806 } 807 808 private void autoSelectCurrentList(String listName) { 809 previouslySelectedList = getCurrentSelectedListName(); 810 setCurrentSelectedList(listName); 811 } 812 813 private void returnToPreviouslySelectedList() { 814 setCurrentSelectedList(previouslySelectedList); 815 } 816 817 public boolean getCanEditSelectedDocs() { 818 if (canEditSelectedDocs == null) { 819 if (getCurrentSelectedList().isEmpty()) { 820 canEditSelectedDocs = false; 821 } else { 822 final List<DocumentModel> selectedDocs = getCurrentSelectedList(); 823 824 // check selected docs 825 canEditSelectedDocs = checkWritePerm(selectedDocs); 826 } 827 } 828 return canEditSelectedDocs; 829 } 830 831 @Deprecated 832 // no longer used by the user_clipboard.xhtml template 833 public boolean getCanEditListDocs(String listName) { 834 final List<DocumentModel> docs = documentsListsManager.getWorkingList(listName); 835 836 final boolean canEdit; 837 if (docs.isEmpty()) { 838 canEdit = false; 839 } else { 840 // check selected docs 841 canEdit = checkWritePerm(docs); 842 } 843 return canEdit; 844 } 845 846 private boolean checkWritePerm(List<DocumentModel> selectedDocs) { 847 for (DocumentModel documentModel : selectedDocs) { 848 boolean canWrite = documentManager.hasPermission(documentModel.getRef(), 849 SecurityConstants.WRITE_PROPERTIES); 850 if (!canWrite) { 851 return false; 852 } 853 } 854 return true; 855 } 856 857 public boolean isCacheEnabled() { 858 if (!SeamCacheHelper.canUseSeamCache()) { 859 return false; 860 } 861 return isWorkListEmpty(); 862 } 863 864 public String getCacheKey() { 865 return getCurrentSelectedListName() + "::" + localeSelector.getLocaleString(); 866 } 867 868 public boolean isCacheEnabledForSelection() { 869 if (!SeamCacheHelper.canUseSeamCache()) { 870 return false; 871 } 872 return documentsListsManager.isWorkingListEmpty(DocumentsListsManager.CURRENT_DOCUMENT_SELECTION); 873 } 874 875 @Override 876 public String exportWorklistAsZip(List<DocumentModel> documents, boolean exportAllBlobs) { 877 try { 878 FacesContext context = FacesContext.getCurrentInstance(); 879 DocumentListZipExporter zipExporter = new DocumentListZipExporter(); 880 File tmpFile = zipExporter.exportWorklistAsZip(documents, documentManager, exportAllBlobs); 881 if (tmpFile == null) { 882 // empty zip file, do nothing 883 facesMessages.add(StatusMessage.Severity.INFO, messages.get("label.clipboard.emptyDocuments")); 884 return null; 885 } else { 886 if (tmpFile.length() > Functions.getBigFileSizeLimit()) { 887 HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest(); 888 request.setAttribute(NXAuthConstants.DISABLE_REDIRECT_REQUEST_KEY, true); 889 String zipDownloadURL = BaseURL.getBaseURL(request); 890 zipDownloadURL += DownloadService.NXBIGZIPFILE + "/"; 891 zipDownloadURL += tmpFile.getName(); 892 try { 893 context.getExternalContext().redirect(zipDownloadURL); 894 } catch (IOException e) { 895 log.error("Error while redirecting for big file downloader", e); 896 } 897 } else { 898 ComponentUtils.downloadFile(tmpFile, "clipboard.zip", "clipboardZip"); 899 tmpFile.delete(); 900 } 901 902 return ""; 903 } 904 } catch (IOException io) { 905 throw new NuxeoException(io); 906 } 907 } 908}