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}