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}