001/*
002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Thomas Roger
016 */
017
018package org.nuxeo.ecm.webapp.action;
019
020import static org.apache.commons.logging.LogFactory.getLog;
021import static org.jboss.seam.ScopeType.CONVERSATION;
022import static org.jboss.seam.annotations.Install.FRAMEWORK;
023import static org.nuxeo.ecm.webapp.helpers.EventNames.USER_ALL_DOCUMENT_TYPES_SELECTION_CHANGED;
024
025import java.io.IOException;
026import java.io.Serializable;
027import java.util.ArrayList;
028import java.util.Collection;
029import java.util.Collections;
030import java.util.Date;
031import java.util.HashMap;
032import java.util.List;
033import java.util.Map;
034import java.util.Random;
035
036import javax.faces.application.FacesMessage;
037import javax.faces.context.FacesContext;
038import javax.servlet.http.HttpServletRequest;
039
040import org.apache.commons.logging.Log;
041import org.jboss.seam.annotations.In;
042import org.jboss.seam.annotations.Install;
043import org.jboss.seam.annotations.Name;
044import org.jboss.seam.annotations.Observer;
045import org.jboss.seam.annotations.Scope;
046import org.jboss.seam.annotations.web.RequestParameter;
047import org.jboss.seam.core.Events;
048import org.jboss.seam.faces.FacesMessages;
049import org.jboss.seam.international.Messages;
050import org.jboss.seam.international.StatusMessage;
051import org.nuxeo.ecm.automation.AutomationService;
052import org.nuxeo.ecm.automation.OperationChain;
053import org.nuxeo.ecm.automation.OperationContext;
054import org.nuxeo.ecm.automation.OperationException;
055import org.nuxeo.ecm.automation.OperationParameters;
056import org.nuxeo.ecm.automation.core.util.BlobList;
057import org.nuxeo.ecm.automation.core.util.DataModelProperties;
058import org.nuxeo.ecm.automation.server.jaxrs.batch.BatchManager;
059import org.nuxeo.ecm.core.api.Blob;
060import org.nuxeo.ecm.core.api.CoreSession;
061import org.nuxeo.ecm.core.api.DataModel;
062import org.nuxeo.ecm.core.api.DocumentModel;
063import org.nuxeo.ecm.core.api.IdRef;
064import org.nuxeo.ecm.core.api.NuxeoException;
065import org.nuxeo.ecm.core.api.impl.SimpleDocumentModel;
066import org.nuxeo.ecm.core.api.security.SecurityConstants;
067import org.nuxeo.ecm.platform.actions.Action;
068import org.nuxeo.ecm.platform.ui.web.api.NavigationContext;
069import org.nuxeo.ecm.platform.ui.web.api.WebActions;
070import org.nuxeo.ecm.platform.ui.web.rest.FancyNavigationHandler;
071import org.nuxeo.ecm.platform.ui.web.util.files.FileUtils;
072import org.nuxeo.ecm.platform.web.common.exceptionhandling.ExceptionHelper;
073import org.nuxeo.ecm.webapp.dnd.DndConfigurationHelper;
074import org.nuxeo.ecm.webapp.filemanager.FileManageActionsBean;
075import org.nuxeo.ecm.webapp.filemanager.NxUploadedFile;
076import org.nuxeo.runtime.api.Framework;
077import org.richfaces.event.FileUploadEvent;
078
079/**
080 * @since 6.0
081 */
082@Name("importActions")
083@Scope(CONVERSATION)
084@Install(precedence = FRAMEWORK)
085public class ImportActions implements Serializable {
086
087    private static final long serialVersionUID = 1L;
088
089    private static final Log log = getLog(ImportActions.class);
090
091    public static final String LABEL_IMPORT_PROBLEM = "label.bulk.import.documents.error";
092
093    public static final String LABEL_IMPORT_CANNOT_CREATE_ERROR = "label.bulk.import.documents.cannotCreateError";
094
095    public static final String DOCUMENTS_IMPORTED = "documentImported";
096
097    protected static Random random = new Random();
098
099    @In(create = true, required = false)
100    protected transient CoreSession documentManager;
101
102    @In(create = true)
103    protected NavigationContext navigationContext;
104
105    @In(create = true)
106    protected transient WebActions webActions;
107
108    @In(create = true)
109    protected transient DndConfigurationHelper dndConfigHelper;
110
111    @In(create = true, required = false)
112    protected FacesMessages facesMessages;
113
114    @In(create = true)
115    protected Map<String, String> messages;
116
117    @RequestParameter
118    protected String fancyboxFormId;
119
120    protected DocumentModel importDocumentModel;
121
122    protected Action selectedImportOption;
123
124    protected List<Action> importOptions;
125
126    protected String selectedImportFolderId;
127
128    protected String currentBatchId;
129
130    protected Collection<NxUploadedFile> uploadedFiles = null;
131
132    public DocumentModel getImportDocumentModel() {
133        if (importDocumentModel == null) {
134            importDocumentModel = new SimpleDocumentModel();
135        }
136        return importDocumentModel;
137    }
138
139    public String getSelectedImportOptionId() {
140        if (selectedImportOption == null) {
141            selectedImportOption = importOptions != null && importOptions.size() > 0 ? importOptions.get(0) : null;
142        }
143        return selectedImportOption != null ? selectedImportOption.getId() : null;
144    }
145
146    public void setSelectedImportOptionId(String id) {
147        for (Action importOption : importOptions) {
148            if (importOption.getId().equals(id)) {
149                selectedImportOption = importOption;
150                break;
151            }
152        }
153    }
154
155    public Action getSelectedImportOption() {
156        if (selectedImportOption == null) {
157            selectedImportOption = importOptions != null && importOptions.size() > 0 ? importOptions.get(0) : null;
158        }
159        return selectedImportOption;
160    }
161
162    public List<Action> getImportOptions(String dropContext) {
163        if (importOptions == null) {
164            importOptions = new ArrayList<>();
165            importOptions.addAll(webActions.getActionsList(dropContext));
166        }
167        return importOptions;
168    }
169
170    public String getSelectedImportFolderId() {
171        if (selectedImportFolderId == null) {
172            DocumentModel currentDocument = navigationContext.getCurrentDocument();
173            if (currentDocument == null) {
174                return null;
175            }
176            if (currentDocument.isFolder() && !"/".equals(currentDocument.getPathAsString())
177                    && documentManager.hasPermission(currentDocument.getRef(), SecurityConstants.ADD_CHILDREN)) {
178                selectedImportFolderId = currentDocument.getId();
179            } else {
180                // try to find the first folderish parent
181                List<DocumentModel> parents = documentManager.getParentDocuments(currentDocument.getRef());
182                Collections.reverse(parents);
183
184                for (DocumentModel parent : parents) {
185                    if (parent.isFolder()
186                            && documentManager.hasPermission(parent.getRef(), SecurityConstants.ADD_CHILDREN)) {
187                        selectedImportFolderId = parent.getId();
188                        break;
189                    }
190                }
191            }
192        }
193        return selectedImportFolderId;
194    }
195
196    public void setSelectedImportFolderId(String selectedImportFolderId) {
197        this.selectedImportFolderId = selectedImportFolderId;
198    }
199
200    /*
201     * ----- Document bulk import -----
202     */
203
204    public String generateBatchId() {
205        if (currentBatchId == null) {
206            BatchManager batchManager = Framework.getService(BatchManager.class);
207            currentBatchId = batchManager.initBatch();
208        }
209        return currentBatchId;
210    }
211
212    public boolean hasUploadedFiles() {
213        if (currentBatchId != null) {
214            BatchManager batchManager = Framework.getLocalService(BatchManager.class);
215            return batchManager.hasBatch(currentBatchId);
216        }
217        return false;
218    }
219
220    public String importDocuments() {
221        Map<String, Serializable> importOptionProperties = selectedImportOption.getProperties();
222        String chainOrOperationId = null;
223        if (importOptionProperties.containsKey("chainId")) {
224            chainOrOperationId = (String) importOptionProperties.get("chainId");
225        } else if (importOptionProperties.containsKey("operationId")) {
226            chainOrOperationId = (String) importOptionProperties.get("operationId");
227        } else {
228            // fallback on action id
229            chainOrOperationId = selectedImportOption.getId();
230        }
231
232        List<DataModel> dms = new ArrayList<>();
233        for (String schema : importDocumentModel.getSchemas()) {
234            dms.add(importDocumentModel.getDataModel(schema));
235        }
236        DataModelProperties properties = new DataModelProperties(dms, true);
237
238        Map<String, Object> contextParams = new HashMap<>();
239        contextParams.put("docMetaData", properties);
240        contextParams.put("currentDocument", selectedImportFolderId);
241
242        boolean resetBatchState = true;
243        try {
244            List<DocumentModel> importedDocuments;
245            if (dndConfigHelper.useHtml5DragAndDrop()) {
246                importedDocuments = importDocumentsThroughBatchManager(chainOrOperationId, contextParams);
247
248            } else {
249                importedDocuments = importDocumentsThroughUploadItems(chainOrOperationId, contextParams);
250            }
251
252            Events.instance().raiseEvent(DOCUMENTS_IMPORTED, importedDocuments, importDocumentModel);
253
254            if (selectedImportFolderId != null) {
255                return navigationContext.navigateToRef(new IdRef(selectedImportFolderId));
256            }
257        } catch (NuxeoException e) {
258            log.debug(e, e);
259            Throwable t = ExceptionHelper.unwrapException(e);
260            // FIXME NXP-XXXXX
261            if (t.getMessage().contains("Cannot create")) {
262                // do not reset state
263                resetBatchState = false;
264                FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, Messages.instance().get(
265                        LABEL_IMPORT_CANNOT_CREATE_ERROR), null);
266                FacesContext faces = FacesContext.getCurrentInstance();
267                if (fancyboxFormId != null && fancyboxFormId.startsWith(":")) {
268                    faces.addMessage(fancyboxFormId.substring(1), message);
269                } else {
270                    faces.addMessage(fancyboxFormId, message);
271                }
272                HttpServletRequest httpRequest = (HttpServletRequest) faces.getExternalContext().getRequest();
273                // avoid redirect for message to be displayed (and fancybox to
274                // be reopened)
275                httpRequest.setAttribute(FancyNavigationHandler.DISABLE_REDIRECT_FOR_URL_REWRITE, Boolean.TRUE);
276            } else {
277                facesMessages.addFromResourceBundle(StatusMessage.Severity.ERROR,
278                        Messages.instance().get(LABEL_IMPORT_PROBLEM));
279            }
280        } finally {
281            if (resetBatchState) {
282                // reset batch state
283                cancel();
284            }
285        }
286
287        return null;
288    }
289
290    @SuppressWarnings("unchecked")
291    protected List<DocumentModel> importDocumentsThroughBatchManager(String chainOrOperationId,
292            Map<String, Object> contextParams) {
293        BatchManager bm = Framework.getLocalService(BatchManager.class);
294        return (List<DocumentModel>) bm.execute(currentBatchId, chainOrOperationId, documentManager, contextParams,
295                null);
296    }
297
298    @SuppressWarnings("unchecked")
299    protected List<DocumentModel> importDocumentsThroughUploadItems(String chainOrOperationId,
300            Map<String, Object> contextParams) {
301        if (uploadedFiles == null) {
302            return Collections.emptyList();
303        }
304
305        try {
306            List<Blob> blobs = new ArrayList<>();
307            for (NxUploadedFile uploadItem : uploadedFiles) {
308                Blob blob = uploadItem.getBlob();
309                FileUtils.configureFileBlob(blob);
310                blobs.add(blob);
311            }
312
313            OperationContext ctx = new OperationContext(documentManager);
314            ctx.setInput(new BlobList(blobs));
315            ctx.putAll(contextParams);
316
317            AutomationService as = Framework.getLocalService(AutomationService.class);
318            if (chainOrOperationId.startsWith("Chain.")) {
319                return (List<DocumentModel>) as.run(ctx, chainOrOperationId.substring(6));
320            } else {
321                OperationChain chain = new OperationChain("operation");
322                OperationParameters params = new OperationParameters(chainOrOperationId, new HashMap<String, Object>());
323                chain.add(params);
324                return (List<DocumentModel>) as.run(ctx, chain);
325            }
326        } catch (OperationException e) {
327            log.error("Error while executing automation batch ", e);
328            throw new NuxeoException(e);
329        } finally {
330            for (NxUploadedFile uploadItem : getUploadedFiles()) {
331                // FIXME: check if a temp file needs to be tracked for
332                // deletion
333                // File tempFile = uploadItem.getFile();
334                // if (tempFile != null && tempFile.exists()) {
335                // Framework.trackFile(tempFile, tempFile);
336                // }
337            }
338            uploadedFiles = null;
339        }
340    }
341
342    public void cancel() {
343        if (currentBatchId != null) {
344            BatchManager bm = Framework.getLocalService(BatchManager.class);
345            bm.clean(currentBatchId);
346        }
347        importDocumentModel = null;
348        selectedImportFolderId = null;
349        uploadedFiles = null;
350        currentBatchId = null;
351    }
352
353    public Collection<NxUploadedFile> getUploadedFiles() {
354        if (uploadedFiles == null) {
355            uploadedFiles = new ArrayList<>();
356        }
357        return uploadedFiles;
358    }
359
360    public void setUploadedFiles(Collection<NxUploadedFile> uploadedFiles) {
361        this.uploadedFiles = uploadedFiles;
362    }
363
364    @Observer(value = USER_ALL_DOCUMENT_TYPES_SELECTION_CHANGED)
365    public void invalidateSelectedImportFolder() {
366        selectedImportFolderId = null;
367    }
368
369    /**
370     * @since 6.0
371     */
372    public void processUpload(FileUploadEvent uploadEvent) {
373        try {
374            if (uploadedFiles == null) {
375                uploadedFiles = new ArrayList<NxUploadedFile>();
376            }
377            Blob blob = FileManageActionsBean.getBlob(uploadEvent);
378            uploadedFiles.add(new NxUploadedFile(blob));
379        } catch (IOException e) {
380            log.error(e, e);
381        }
382    }
383
384}