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