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