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