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