001/* 002 * (C) Copyright 2012-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 * Florent Guillaume 019 * Julien Carsique 020 */ 021package org.nuxeo.ecm.csv; 022 023import static org.nuxeo.ecm.csv.CSVImportLog.Status.ERROR; 024import static org.nuxeo.ecm.csv.Constants.CSV_NAME_COL; 025import static org.nuxeo.ecm.csv.Constants.CSV_TYPE_COL; 026 027import java.io.BufferedReader; 028import java.io.File; 029import java.io.FileInputStream; 030import java.io.FileNotFoundException; 031import java.io.IOException; 032import java.io.InputStream; 033import java.io.InputStreamReader; 034import java.io.Reader; 035import java.io.Serializable; 036import java.text.DateFormat; 037import java.text.ParseException; 038import java.text.SimpleDateFormat; 039import java.util.ArrayList; 040import java.util.Arrays; 041import java.util.Collections; 042import java.util.Date; 043import java.util.HashMap; 044import java.util.List; 045import java.util.Map; 046 047import org.apache.commons.csv.CSVFormat; 048import org.apache.commons.csv.CSVParser; 049import org.apache.commons.csv.CSVRecord; 050import org.apache.commons.io.Charsets; 051import org.apache.commons.io.FilenameUtils; 052import org.apache.commons.io.IOUtils; 053import org.apache.commons.io.input.BOMInputStream; 054import org.apache.commons.lang.StringUtils; 055import org.apache.commons.logging.Log; 056import org.apache.commons.logging.LogFactory; 057import org.nuxeo.common.utils.ExceptionUtils; 058import org.nuxeo.common.utils.Path; 059import org.nuxeo.ecm.automation.AutomationService; 060import org.nuxeo.ecm.automation.OperationChain; 061import org.nuxeo.ecm.automation.OperationContext; 062import org.nuxeo.ecm.automation.core.operations.notification.MailTemplateHelper; 063import org.nuxeo.ecm.automation.core.operations.notification.SendMail; 064import org.nuxeo.ecm.automation.core.scripting.Expression; 065import org.nuxeo.ecm.automation.core.scripting.Scripting; 066import org.nuxeo.ecm.automation.core.util.ComplexTypeJSONDecoder; 067import org.nuxeo.ecm.automation.core.util.StringList; 068import org.nuxeo.ecm.core.api.Blobs; 069import org.nuxeo.ecm.core.api.DocumentModel; 070import org.nuxeo.ecm.core.api.DocumentRef; 071import org.nuxeo.ecm.core.api.NuxeoException; 072import org.nuxeo.ecm.core.api.NuxeoPrincipal; 073import org.nuxeo.ecm.core.api.PathRef; 074import org.nuxeo.ecm.core.query.sql.NXQL; 075import org.nuxeo.ecm.core.schema.DocumentType; 076import org.nuxeo.ecm.core.schema.SchemaManager; 077import org.nuxeo.ecm.core.schema.types.ComplexType; 078import org.nuxeo.ecm.core.schema.types.CompositeType; 079import org.nuxeo.ecm.core.schema.types.Field; 080import org.nuxeo.ecm.core.schema.types.ListType; 081import org.nuxeo.ecm.core.schema.types.SimpleTypeImpl; 082import org.nuxeo.ecm.core.schema.types.Type; 083import org.nuxeo.ecm.core.schema.types.primitives.BooleanType; 084import org.nuxeo.ecm.core.schema.types.primitives.DateType; 085import org.nuxeo.ecm.core.schema.types.primitives.DoubleType; 086import org.nuxeo.ecm.core.schema.types.primitives.IntegerType; 087import org.nuxeo.ecm.core.schema.types.primitives.LongType; 088import org.nuxeo.ecm.core.schema.types.primitives.StringType; 089import org.nuxeo.ecm.core.transientstore.api.TransientStore; 090import org.nuxeo.ecm.core.transientstore.work.TransientStoreWork; 091import org.nuxeo.ecm.core.work.api.WorkManager; 092import org.nuxeo.ecm.csv.CSVImportLog.Status; 093import org.nuxeo.ecm.platform.ec.notification.service.NotificationService; 094import org.nuxeo.ecm.platform.ec.notification.service.NotificationServiceHelper; 095import org.nuxeo.ecm.platform.types.TypeManager; 096import org.nuxeo.ecm.platform.ui.web.rest.api.URLPolicyService; 097import org.nuxeo.ecm.platform.url.DocumentViewImpl; 098import org.nuxeo.ecm.platform.url.api.DocumentView; 099import org.nuxeo.ecm.platform.usermanager.UserManager; 100import org.nuxeo.runtime.api.Framework; 101 102/** 103 * Work task to import form a CSV file. Because the file is read from the local filesystem, this must be executed in a 104 * local queue. Since NXP-15252 the CSV reader manages "records", not "lines". 105 * 106 * @since 5.7 107 */ 108public class CSVImporterWork extends TransientStoreWork { 109 110 public static final String NUXEO_CSV_MAIL_TO = "nuxeo.csv.mail.to"; 111 112 public static final String LABEL_CSV_IMPORTER_NOT_EXISTING_FIELD = "label.csv.importer.notExistingField"; 113 114 public static final String LABEL_CSV_IMPORTER_CANNOT_CONVERT_FIELD_VALUE = "label.csv.importer.cannotConvertFieldValue"; 115 116 public static final String LABEL_CSV_IMPORTER_NOT_EXISTING_FILE = "label.csv.importer.notExistingFile"; 117 118 public static final String NUXEO_CSV_BLOBS_FOLDER = "nuxeo.csv.blobs.folder"; 119 120 public static final String LABEL_CSV_IMPORTER_DOCUMENT_ALREADY_EXISTS = "label.csv.importer.documentAlreadyExists"; 121 122 public static final String LABEL_CSV_IMPORTER_UNABLE_TO_UPDATE = "label.csv.importer.unableToUpdate"; 123 124 public static final String LABEL_CSV_IMPORTER_DOCUMENT_UPDATED = "label.csv.importer.documentUpdated"; 125 126 public static final String LABEL_CSV_IMPORTER_UNABLE_TO_CREATE = "label.csv.importer.unableToCreate"; 127 128 public static final String LABEL_CSV_IMPORTER_PARENT_DOES_NOT_EXIST = "label.csv.importer.parentDoesNotExist"; 129 130 public static final String LABEL_CSV_IMPORTER_DOCUMENT_CREATED = "label.csv.importer.documentCreated"; 131 132 public static final String LABEL_CSV_IMPORTER_NOT_ALLOWED_SUB_TYPE = "label.csv.importer.notAllowedSubType"; 133 134 public static final String LABEL_CSV_IMPORTER_UNABLE_TO_SAVE = "label.csv.importer.unableToSave"; 135 136 public static final String LABEL_CSV_IMPORTER_ERROR_IMPORTING_LINE = "label.csv.importer.errorImportingLine"; 137 138 public static final String LABEL_CSV_IMPORTER_NOT_EXISTING_TYPE = "label.csv.importer.notExistingType"; 139 140 public static final String LABEL_CSV_IMPORTER_MISSING_TYPE_VALUE = "label.csv.importer.missingTypeValue"; 141 142 public static final String LABEL_CSV_IMPORTER_MISSING_NAME_VALUE = "label.csv.importer.missingNameValue"; 143 144 public static final String LABEL_CSV_IMPORTER_MISSING_NAME_COLUMN = "label.csv.importer.missingNameColumn"; 145 146 public static final String LABEL_CSV_IMPORTER_EMPTY_FILE = "label.csv.importer.emptyFile"; 147 148 public static final String LABEL_CSV_IMPORTER_ERROR_DURING_IMPORT = "label.csv.importer.errorDuringImport"; 149 150 public static final String LABEL_CSV_IMPORTER_EMPTY_LINE = "label.csv.importer.emptyLine"; 151 152 private static final long serialVersionUID = 1L; 153 154 private static final Log log = LogFactory.getLog(CSVImporterWork.class); 155 156 private static final String TEMPLATE_IMPORT_RESULT = "templates/csvImportResult.ftl"; 157 158 public static final String CATEGORY_CSV_IMPORTER = "csvImporter"; 159 160 public static final String CONTENT_FILED_TYPE_NAME = "content"; 161 162 /** 163 * CSV headers that won't be checked if the field exists on the document type. 164 * 165 * @since 7.3 166 */ 167 public static List<String> AUTHORIZED_HEADERS = Arrays.asList(NXQL.ECM_LIFECYCLESTATE); 168 169 protected String parentPath; 170 171 protected String username; 172 173 protected File csvFile; 174 175 protected String csvFileName; 176 177 protected CSVImporterOptions options; 178 179 protected transient DateFormat dateformat; 180 181 protected boolean hasTypeColumn; 182 183 protected Date startDate; 184 185 protected ArrayList<CSVImportLog> importLogs = new ArrayList<>(); 186 187 public CSVImporterWork(String id) { 188 super(id); 189 } 190 191 public CSVImporterWork(String repositoryName, String parentPath, String username, File csvFile, String csvFileName, 192 CSVImporterOptions options) { 193 super(CSVImportId.create(repositoryName, parentPath, csvFile)); 194 setDocument(repositoryName, null); 195 setOriginatingUsername(username); 196 this.parentPath = parentPath; 197 this.username = username; 198 this.csvFile = csvFile; 199 this.csvFileName = csvFileName; 200 this.options = options; 201 startDate = new Date(); 202 } 203 204 @Override 205 public String getCategory() { 206 return CATEGORY_CSV_IMPORTER; 207 } 208 209 @Override 210 public String getTitle() { 211 return String.format("CSV import in '%s'", parentPath); 212 } 213 214 public List<CSVImportLog> getImportLogs() { 215 return new ArrayList<>(importLogs); 216 } 217 218 @Override 219 public void work() { 220 TransientStore store = getStore(); 221 store.putParameter(id, "status", new CSVImportStatus(CSVImportStatus.State.RUNNING)); 222 setStatus("Importing"); 223 openUserSession(); 224 CSVFormat csvFormat = CSVFormat.DEFAULT.withHeader() 225 .withEscape(options.getEscapeCharacter()).withCommentMarker(options.getCommentMarker()); 226 try (Reader in = newReader(csvFile); CSVParser parser = csvFormat.parse(in)) { 227 doImport(parser); 228 } catch (IOException e) { 229 logError(0, "Error while doing the import: %s", LABEL_CSV_IMPORTER_ERROR_DURING_IMPORT, e.getMessage()); 230 log.debug(e, e); 231 } 232 store.putParameter(id, "logs", importLogs); 233 if (options.sendEmail()) { 234 setStatus("Sending email"); 235 sendMail(); 236 } 237 setStatus(null); 238 } 239 240 @Override 241 public void cleanUp(boolean ok, Exception e) { 242 try { 243 super.cleanUp(ok, e); 244 } finally { 245 getStore().putParameter(id, "status", new CSVImportStatus(CSVImportStatus.State.COMPLETED)); 246 } 247 } 248 249 static final Serializable EMPTY_LOGS =new ArrayList<CSVImportLog>(); 250 251 String launch() { 252 WorkManager works = Framework.getLocalService(WorkManager.class); 253 String queueId = works.getCategoryQueueId(CATEGORY_CSV_IMPORTER); 254 255 TransientStore store = getStore(); 256 store.putParameter(id, "logs", EMPTY_LOGS); 257 store.putParameter(id, "status", new CSVImportStatus(CSVImportStatus.State.SCHEDULED, 0, works.getMetrics(queueId).scheduled.intValue())); 258 works.schedule(this, WorkManager.Scheduling.IF_NOT_RUNNING_OR_SCHEDULED); 259 return id; 260 } 261 262 static CSVImportStatus getStatus(String id) { 263 TransientStore store = getStore(); 264 if (!store.exists(id)) { 265 return null; 266 } 267 return (CSVImportStatus) store.getParameter(id, "status"); 268 } 269 270 @SuppressWarnings("unchecked") 271 static List<CSVImportLog> getLastImportLogs(String id) { 272 TransientStore store = getStore(); 273 if (!store.exists(id)) { 274 return Collections.emptyList(); 275 } 276 return (ArrayList<CSVImportLog>)store.getParameter(id, "logs"); 277 } 278 279 /** 280 * @since 7.3 281 */ 282 protected BufferedReader newReader(File file) throws FileNotFoundException { 283 return new BufferedReader(new InputStreamReader(new BOMInputStream(new FileInputStream(file)))); 284 } 285 286 protected void doImport(CSVParser parser) { 287 log.info(String.format("Importing CSV file: %s", csvFileName)); 288 Map<String, Integer> header = parser.getHeaderMap(); 289 if (header == null) { 290 logError(0, "No header line, empty file?", LABEL_CSV_IMPORTER_EMPTY_FILE); 291 return; 292 } 293 if (!header.containsKey(CSV_NAME_COL)) { 294 logError(0, "Missing 'name' column", LABEL_CSV_IMPORTER_MISSING_NAME_COLUMN); 295 return; 296 } 297 hasTypeColumn = header.containsKey(CSV_TYPE_COL); 298 299 try { 300 int batchSize = options.getBatchSize(); 301 long docsCreatedCount = 0; 302 for (CSVRecord record : parser) { 303 if (record.size() == 0) { 304 // empty record 305 importLogs.add(new CSVImportLog(record.getRecordNumber(), Status.SKIPPED, "Empty record", 306 LABEL_CSV_IMPORTER_EMPTY_LINE)); 307 continue; 308 } 309 try { 310 if (importRecord(record, header)) { 311 docsCreatedCount++; 312 if (docsCreatedCount % batchSize == 0) { 313 commitOrRollbackTransaction(); 314 startTransaction(); 315 } 316 } 317 } catch (NuxeoException e) { 318 // try next line 319 Throwable unwrappedException = unwrapException(e); 320 logError(parser.getRecordNumber(), "Error while importing line: %s", 321 LABEL_CSV_IMPORTER_ERROR_IMPORTING_LINE, unwrappedException.getMessage()); 322 log.debug(unwrappedException, unwrappedException); 323 } 324 } 325 326 try { 327 session.save(); 328 } catch (NuxeoException e) { 329 Throwable ue = unwrapException(e); 330 logError(parser.getRecordNumber(), "Unable to save: %s", LABEL_CSV_IMPORTER_UNABLE_TO_SAVE, 331 ue.getMessage()); 332 log.debug(ue, ue); 333 } 334 } finally { 335 commitOrRollbackTransaction(); 336 startTransaction(); 337 } 338 log.info(String.format("Done importing CSV file: %s", csvFileName)); 339 } 340 341 /** 342 * Import a line from the CSV file. 343 * 344 * @return {@code true} if a document has been created or updated, {@code false} otherwise. 345 * @since 6.0 346 */ 347 protected boolean importRecord(CSVRecord record, Map<String, Integer> header) { 348 String name = record.get(CSV_NAME_COL); 349 if (StringUtils.isBlank(name)) { 350 log.debug("record.isSet=" + record.isSet(CSV_NAME_COL)); 351 logError(record.getRecordNumber(), "Missing 'name' value", LABEL_CSV_IMPORTER_MISSING_NAME_VALUE); 352 return false; 353 } 354 355 Path targetPath = new Path(parentPath).append(name); 356 name = targetPath.lastSegment(); 357 String newParentPath = targetPath.removeLastSegments(1).toString(); 358 boolean exists = options.getCSVImporterDocumentFactory().exists(session, newParentPath, name, null); 359 360 DocumentRef docRef = null; 361 String type = null; 362 if (exists) { 363 docRef = new PathRef(targetPath.toString()); 364 type = session.getDocument(docRef).getType(); 365 } else { 366 if (hasTypeColumn) { 367 type = record.get(CSV_TYPE_COL); 368 } 369 if (StringUtils.isBlank(type)) { 370 log.debug("record.isSet=" + record.isSet(CSV_TYPE_COL)); 371 logError(record.getRecordNumber(), "Missing 'type' value", LABEL_CSV_IMPORTER_MISSING_TYPE_VALUE); 372 return false; 373 } 374 } 375 376 DocumentType docType = Framework.getLocalService(SchemaManager.class).getDocumentType(type); 377 if (docType == null) { 378 logError(record.getRecordNumber(), "The type '%s' does not exist", LABEL_CSV_IMPORTER_NOT_EXISTING_TYPE, 379 type); 380 return false; 381 } 382 Map<String, Serializable> properties = computePropertiesMap(record, docType, header); 383 if (properties == null) { 384 // skip this line 385 return false; 386 } 387 388 long lineNumber = record.getRecordNumber(); 389 if (exists) { 390 return updateDocument(lineNumber, docRef, properties); 391 } else { 392 return createDocument(lineNumber, newParentPath, name, type, properties); 393 } 394 } 395 396 /** 397 * @since 6.0 398 */ 399 protected Map<String, Serializable> computePropertiesMap(CSVRecord record, CompositeType compositeType, 400 Map<String, Integer> header) { 401 Map<String, Serializable> values = new HashMap<>(); 402 for (String headerValue : header.keySet()) { 403 String lineValue = record.get(headerValue); 404 lineValue = lineValue.trim(); 405 String fieldName = headerValue; 406 if (!CSV_NAME_COL.equals(headerValue) && !CSV_TYPE_COL.equals(headerValue)) { 407 if (AUTHORIZED_HEADERS.contains(headerValue) && !StringUtils.isBlank(lineValue)) { 408 values.put(headerValue, lineValue); 409 } else { 410 if (!compositeType.hasField(fieldName)) { 411 fieldName = fieldName.split(":")[1]; 412 } 413 if (compositeType.hasField(fieldName) && !StringUtils.isBlank(lineValue)) { 414 Serializable convertedValue = convertValue(compositeType, fieldName, headerValue, lineValue, 415 record.getRecordNumber()); 416 if (convertedValue == null) { 417 return null; 418 } 419 values.put(headerValue, convertedValue); 420 } 421 } 422 } 423 } 424 return values; 425 } 426 427 protected Serializable convertValue(CompositeType compositeType, String fieldName, String headerValue, 428 String stringValue, long lineNumber) { 429 if (compositeType.hasField(fieldName)) { 430 Field field = compositeType.getField(fieldName); 431 if (field != null) { 432 try { 433 Serializable fieldValue = null; 434 Type fieldType = field.getType(); 435 if (fieldType.isComplexType()) { 436 if (fieldType.getName().equals(CONTENT_FILED_TYPE_NAME)) { 437 String blobsFolderPath = Framework.getProperty(NUXEO_CSV_BLOBS_FOLDER); 438 String path = FilenameUtils.normalize(blobsFolderPath + "/" + stringValue); 439 File file = new File(path); 440 if (file.exists()) { 441 fieldValue = (Serializable) Blobs.createBlob(file); 442 } else { 443 logError(lineNumber, "The file '%s' does not exist", 444 LABEL_CSV_IMPORTER_NOT_EXISTING_FILE, stringValue); 445 return null; 446 } 447 } else { 448 fieldValue = (Serializable) ComplexTypeJSONDecoder.decode((ComplexType) fieldType, 449 stringValue); 450 } 451 } else { 452 if (fieldType.isListType()) { 453 Type listFieldType = ((ListType) fieldType).getFieldType(); 454 if (listFieldType.isSimpleType()) { 455 /* 456 * Array. 457 */ 458 fieldValue = stringValue.split(options.getListSeparatorRegex()); 459 } else { 460 /* 461 * Complex list. 462 */ 463 fieldValue = (Serializable) ComplexTypeJSONDecoder.decodeList((ListType) fieldType, 464 stringValue); 465 } 466 } else { 467 /* 468 * Primitive type. 469 */ 470 Type type = field.getType(); 471 if (type instanceof SimpleTypeImpl) { 472 type = type.getSuperType(); 473 } 474 if (type.isSimpleType()) { 475 if (type instanceof StringType) { 476 fieldValue = stringValue; 477 } else if (type instanceof IntegerType) { 478 fieldValue = Integer.valueOf(stringValue); 479 } else if (type instanceof LongType) { 480 fieldValue = Long.valueOf(stringValue); 481 } else if (type instanceof DoubleType) { 482 fieldValue = Double.valueOf(stringValue); 483 } else if (type instanceof BooleanType) { 484 fieldValue = Boolean.valueOf(stringValue); 485 } else if (type instanceof DateType) { 486 fieldValue = getDateFormat().parse(stringValue); 487 } 488 } 489 } 490 } 491 return fieldValue; 492 } catch (ParseException | NumberFormatException | IOException e) { 493 logError(lineNumber, "Unable to convert field '%s' with value '%s'", 494 LABEL_CSV_IMPORTER_CANNOT_CONVERT_FIELD_VALUE, headerValue, stringValue); 495 log.debug(e, e); 496 } 497 } 498 } else { 499 logError(lineNumber, "Field '%s' does not exist on type '%s'", LABEL_CSV_IMPORTER_NOT_EXISTING_FIELD, 500 headerValue, compositeType.getName()); 501 } 502 return null; 503 } 504 505 protected DateFormat getDateFormat() { 506 // transient field so may become null 507 if (dateformat == null) { 508 dateformat = new SimpleDateFormat(options.getDateFormat()); 509 } 510 return dateformat; 511 } 512 513 protected boolean createDocument(long lineNumber, String newParentPath, String name, String type, 514 Map<String, Serializable> properties) { 515 try { 516 DocumentRef parentRef = new PathRef(newParentPath); 517 if (session.exists(parentRef)) { 518 DocumentModel parent = session.getDocument(parentRef); 519 520 TypeManager typeManager = Framework.getLocalService(TypeManager.class); 521 if (options.checkAllowedSubTypes() && !typeManager.isAllowedSubType(type, parent.getType())) { 522 logError(lineNumber, "'%s' type is not allowed in '%s'", LABEL_CSV_IMPORTER_NOT_ALLOWED_SUB_TYPE, 523 type, parent.getType()); 524 } else { 525 options.getCSVImporterDocumentFactory().createDocument(session, newParentPath, name, type, 526 properties); 527 importLogs.add(new CSVImportLog(lineNumber, Status.SUCCESS, "Document created", 528 LABEL_CSV_IMPORTER_DOCUMENT_CREATED)); 529 return true; 530 } 531 } else { 532 logError(lineNumber, "Parent document '%s' does not exist", LABEL_CSV_IMPORTER_PARENT_DOES_NOT_EXIST, 533 newParentPath); 534 } 535 } catch (RuntimeException e) { 536 Throwable unwrappedException = unwrapException(e); 537 logError(lineNumber, "Unable to create document: %s", LABEL_CSV_IMPORTER_UNABLE_TO_CREATE, 538 unwrappedException.getMessage()); 539 log.debug(unwrappedException, unwrappedException); 540 } 541 return false; 542 } 543 544 protected boolean updateDocument(long lineNumber, DocumentRef docRef, Map<String, Serializable> properties) { 545 if (options.updateExisting()) { 546 try { 547 options.getCSVImporterDocumentFactory().updateDocument(session, docRef, properties); 548 importLogs.add(new CSVImportLog(lineNumber, Status.SUCCESS, "Document updated", 549 LABEL_CSV_IMPORTER_DOCUMENT_UPDATED)); 550 return true; 551 } catch (RuntimeException e) { 552 Throwable unwrappedException = unwrapException(e); 553 logError(lineNumber, "Unable to update document: %s", LABEL_CSV_IMPORTER_UNABLE_TO_UPDATE, 554 unwrappedException.getMessage()); 555 log.debug(unwrappedException, unwrappedException); 556 } 557 } else { 558 importLogs.add(new CSVImportLog(lineNumber, Status.SKIPPED, "Document already exists", 559 LABEL_CSV_IMPORTER_DOCUMENT_ALREADY_EXISTS)); 560 } 561 return false; 562 } 563 564 protected void logError(long lineNumber, String message, String localizedMessage, String... params) { 565 importLogs.add(new CSVImportLog(lineNumber, ERROR, String.format(message, (Object[]) params), localizedMessage, 566 params)); 567 String lineMessage = String.format("Line %d", lineNumber); 568 String errorMessage = String.format(message, (Object[]) params); 569 log.error(String.format("%s: %s", lineMessage, errorMessage)); 570 } 571 572 protected void sendMail() { 573 UserManager userManager = Framework.getLocalService(UserManager.class); 574 NuxeoPrincipal principal = userManager.getPrincipal(username); 575 String email = principal.getEmail(); 576 if (email == null) { 577 log.info(String.format("Not sending import result email to '%s', no email configured", username)); 578 return; 579 } 580 581 OperationContext ctx = new OperationContext(session); 582 ctx.setInput(session.getRootDocument()); 583 584 CSVImporter csvImporter = Framework.getLocalService(CSVImporter.class); 585 List<CSVImportLog> importerLogs = csvImporter.getImportLogs(getId()); 586 CSVImportResult importResult = CSVImportResult.fromImportLogs(importerLogs); 587 List<CSVImportLog> skippedAndErrorImportLogs = csvImporter.getImportLogs(getId(), Status.SKIPPED, Status.ERROR); 588 ctx.put("importResult", importResult); 589 ctx.put("skippedAndErrorImportLogs", skippedAndErrorImportLogs); 590 ctx.put("csvFilename", csvFileName); 591 ctx.put("startDate", DateFormat.getInstance().format(startDate)); 592 ctx.put("username", username); 593 594 DocumentModel importFolder = session.getDocument(new PathRef(parentPath)); 595 String importFolderUrl = getDocumentUrl(importFolder); 596 ctx.put("importFolderTitle", importFolder.getTitle()); 597 ctx.put("importFolderUrl", importFolderUrl); 598 ctx.put("userUrl", getUserUrl()); 599 600 StringList to = buildRecipientsList(email); 601 Expression from = Scripting.newExpression("Env[\"mail.from\"]"); 602 String subject = "CSV Import result of " + csvFileName; 603 String message = loadTemplate(TEMPLATE_IMPORT_RESULT); 604 605 try { 606 OperationChain chain = new OperationChain("SendMail"); 607 chain.add(SendMail.ID) 608 .set("from", from) 609 .set("to", to) 610 .set("HTML", true) 611 .set("subject", subject) 612 .set("message", message); 613 Framework.getLocalService(AutomationService.class).run(ctx, chain); 614 } catch (Exception e) { 615 ExceptionUtils.checkInterrupt(e); 616 log.error(String.format("Unable to notify user '%s' for import result of '%s': %s", username, csvFileName, 617 e.getMessage())); 618 log.debug(e, e); 619 throw ExceptionUtils.runtimeException(e); 620 } 621 } 622 623 protected String getDocumentUrl(DocumentModel doc) { 624 return MailTemplateHelper.getDocumentUrl(doc, null); 625 } 626 627 protected String getUserUrl() { 628 NotificationService notificationService = NotificationServiceHelper.getNotificationService(); 629 Map<String, String> params = new HashMap<>(); 630 params.put("username", username); 631 DocumentView docView = new DocumentViewImpl(null, null, params); 632 URLPolicyService urlPolicyService = Framework.getLocalService(URLPolicyService.class); 633 return urlPolicyService.getUrlFromDocumentView("user", docView, notificationService.getServerUrlPrefix()); 634 } 635 636 protected StringList buildRecipientsList(String userEmail) { 637 String csvMailTo = Framework.getProperty(NUXEO_CSV_MAIL_TO); 638 if (StringUtils.isBlank(csvMailTo)) { 639 return new StringList(new String[] { userEmail }); 640 } else { 641 return new StringList(new String[] { userEmail, csvMailTo }); 642 } 643 } 644 645 private static String loadTemplate(String key) { 646 InputStream io = CSVImporterWork.class.getClassLoader().getResourceAsStream(key); 647 if (io != null) { 648 try { 649 return IOUtils.toString(io, Charsets.UTF_8); 650 } catch (IOException e) { 651 // cannot happen 652 throw new NuxeoException(e); 653 } finally { 654 try { 655 io.close(); 656 } catch (IOException e) { 657 // nothing to do 658 } 659 } 660 } 661 return null; 662 } 663 664 public static Throwable unwrapException(Throwable t) { 665 Throwable cause = null; 666 if (t != null) { 667 cause = t.getCause(); 668 } 669 if (cause == null) { 670 return t; 671 } else { 672 return unwrapException(cause); 673 } 674 } 675 676}