PageRenderTime 69ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/source/java/ee/webmedia/alfresco/imap/service/ImapServiceExtImpl.java

https://bitbucket.org/smitdevel/delta
Java | 1341 lines | 1175 code | 127 blank | 39 comment | 265 complexity | 9275807134aa8e0108e0cc80d9f1fa4d MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. package ee.webmedia.alfresco.imap.service;
  2. import java.io.*;
  3. import java.nio.charset.Charset;
  4. import java.nio.charset.StandardCharsets;
  5. import java.security.acl.LastOwnerException;
  6. import java.util.*;
  7. import java.util.regex.Matcher;
  8. import java.util.regex.Pattern;
  9. import javax.mail.Address;
  10. import javax.mail.MessagingException;
  11. import javax.mail.Multipart;
  12. import javax.mail.Part;
  13. import javax.mail.internet.ContentType;
  14. import javax.mail.internet.InternetAddress;
  15. import javax.mail.internet.MimeMessage;
  16. import javax.mail.internet.ParseException;
  17. import javax.swing.text.BadLocationException;
  18. import javax.swing.text.rtf.RTFEditorKit;
  19. import org.alfresco.i18n.I18NUtil;
  20. import org.alfresco.model.ContentModel;
  21. import org.alfresco.repo.content.MimetypeMap;
  22. import org.alfresco.repo.imap.AlfrescoImapConst;
  23. import org.alfresco.repo.imap.AlfrescoImapFolder;
  24. import org.alfresco.repo.imap.AlfrescoImapUser;
  25. import org.alfresco.repo.imap.ImapService;
  26. import org.alfresco.repo.security.authentication.AuthenticationUtil;
  27. import org.alfresco.service.cmr.model.FileFolderService;
  28. import org.alfresco.service.cmr.model.FileInfo;
  29. import org.alfresco.service.cmr.repository.ChildAssociationRef;
  30. import org.alfresco.service.cmr.repository.ContentData;
  31. import org.alfresco.service.cmr.repository.ContentService;
  32. import org.alfresco.service.cmr.repository.ContentWriter;
  33. import org.alfresco.service.cmr.repository.MimetypeService;
  34. import org.alfresco.service.cmr.repository.NodeRef;
  35. import org.alfresco.service.cmr.repository.NodeService;
  36. import org.alfresco.service.namespace.QName;
  37. import org.alfresco.util.GUID;
  38. import org.alfresco.util.Pair;
  39. import org.alfresco.util.TempFileProvider;
  40. import org.apache.commons.collections.CollectionUtils;
  41. import org.apache.commons.collections.Predicate;
  42. import org.apache.commons.io.IOUtils;
  43. import org.apache.commons.lang.StringUtils;
  44. import org.apache.commons.lang.time.FastDateFormat;
  45. import org.apache.commons.logging.Log;
  46. import org.apache.commons.logging.LogFactory;
  47. import org.apache.poi.poifs.filesystem.DirectoryNode;
  48. import org.apache.poi.poifs.filesystem.DocumentInputStream;
  49. import org.apache.poi.poifs.filesystem.DocumentNode;
  50. import org.apache.poi.poifs.filesystem.Entry;
  51. import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
  52. import org.apache.xml.security.transforms.TransformationException;
  53. import org.jsoup.Jsoup;
  54. import org.jsoup.nodes.Document;
  55. import org.springframework.beans.factory.InitializingBean;
  56. import org.springframework.util.FileCopyUtils;
  57. import com.icegreen.greenmail.store.FolderException;
  58. import com.icegreen.greenmail.store.MailFolder;
  59. import com.icegreen.greenmail.util.GreenMailUtil;
  60. import ee.webmedia.alfresco.classificator.enums.DocumentStatus;
  61. import ee.webmedia.alfresco.classificator.enums.StorageType;
  62. import ee.webmedia.alfresco.classificator.enums.TransmittalMode;
  63. import ee.webmedia.alfresco.common.service.ApplicationConstantsBean;
  64. import ee.webmedia.alfresco.common.service.BulkLoadNodeService;
  65. import ee.webmedia.alfresco.common.service.GeneralService;
  66. import ee.webmedia.alfresco.common.web.BeanHelper;
  67. import ee.webmedia.alfresco.docconfig.bootstrap.SystematicDocumentType;
  68. import ee.webmedia.alfresco.docdynamic.service.DocumentDynamicService;
  69. import ee.webmedia.alfresco.document.einvoice.service.EInvoiceService;
  70. import ee.webmedia.alfresco.document.file.model.FileModel;
  71. import ee.webmedia.alfresco.document.file.service.FileService;
  72. import ee.webmedia.alfresco.document.file.web.Subfolder;
  73. import ee.webmedia.alfresco.document.log.service.DocumentLogService;
  74. import ee.webmedia.alfresco.document.model.DocumentCommonModel;
  75. import ee.webmedia.alfresco.document.model.DocumentSpecificModel;
  76. import ee.webmedia.alfresco.document.model.DocumentSubtypeModel;
  77. import ee.webmedia.alfresco.imap.AppendBehaviour;
  78. import ee.webmedia.alfresco.imap.AttachmentsFolderAppendBehaviour;
  79. import ee.webmedia.alfresco.imap.ImmutableFolder;
  80. import ee.webmedia.alfresco.imap.IncomingEInvoiceCreateBehaviour;
  81. import ee.webmedia.alfresco.imap.IncomingFolderAppendBehaviour;
  82. import ee.webmedia.alfresco.imap.PermissionDeniedAppendBehaviour;
  83. import ee.webmedia.alfresco.imap.SendFailureAppendBehaviour;
  84. import ee.webmedia.alfresco.imap.SentFolderAppendBehaviour;
  85. import ee.webmedia.alfresco.imap.model.ImapModel;
  86. import ee.webmedia.alfresco.privilege.model.Privilege;
  87. import ee.webmedia.alfresco.user.service.UserService;
  88. import ee.webmedia.alfresco.utils.FilenameUtil;
  89. import ee.webmedia.alfresco.utils.MessageUtil;
  90. import net.freeutils.tnef.Attachment;
  91. import net.freeutils.tnef.CompressedRTFInputStream;
  92. import net.freeutils.tnef.MAPIProp;
  93. import net.freeutils.tnef.MAPIProps;
  94. import net.freeutils.tnef.Message;
  95. import net.freeutils.tnef.RawInputStream;
  96. import net.freeutils.tnef.TNEFInputStream;
  97. import net.freeutils.tnef.TNEFUtils;
  98. /**
  99. * SimDhs specific IMAP logic.
  100. */
  101. public class ImapServiceExtImpl implements ImapServiceExt, InitializingBean {
  102. private static final Log log = LogFactory.getLog(ImapServiceExtImpl.class);
  103. private static final FastDateFormat dateTimeFormat = FastDateFormat.getInstance("dd.MM.yyyy HH:mm");
  104. private ImapService imapService;
  105. private FileFolderService fileFolderService;
  106. private DocumentLogService documentLogService;
  107. private NodeService nodeService;
  108. private ContentService contentService;
  109. private GeneralService generalService;
  110. private FileService fileService;
  111. private MimetypeService mimetypeService;
  112. private DocumentDynamicService documentDynamicService;
  113. private EInvoiceService einvoiceService;
  114. public UserService userService;
  115. private String messageCopyFolder;
  116. private boolean saveOriginalToRepo;
  117. private String incomingLetterSubfolderType;
  118. private String attachmentsSubfolderType;
  119. private String outgoingLettersSubfolderType;
  120. private String sendFailureNoticesSubfolderType;
  121. private Map<NodeRef, String> imapFolderTypes = null;
  122. private Map<NodeRef, Set<String>> imapFolderFixedSubfolders = null;
  123. private ApplicationConstantsBean applicationConstantsBean;
  124. private BulkLoadNodeService bulkLoadNodeService;
  125. // todo: make this configurable with spring
  126. private Set<String> allowedFolders = null;
  127. @Override
  128. public void afterPropertiesSet() throws Exception {
  129. GreenMailUtil.setMessageCopyFolder(messageCopyFolder);
  130. GreenMailUtil.setSaveOriginalToRepo(saveOriginalToRepo);
  131. }
  132. @Override
  133. public MailFolder getFolder(AlfrescoImapUser user, String folderName) {
  134. return addBehaviour(imapService.getFolder(user, folderName));
  135. }
  136. @Override
  137. public long saveEmailToSubfolder(NodeRef folderNodeRef, MimeMessage mimeMessage, String behaviour, boolean incomingEmail) throws FolderException {
  138. NodeRef parentNodeRef = findOrCreateFolder(folderNodeRef, behaviour);
  139. return saveEmail(parentNodeRef, mimeMessage, incomingEmail);
  140. }
  141. @Override
  142. public void saveFailureNoticeToSubfolder(NodeRef folderNodeRef, MimeMessage mimeMessage, String behaviour) throws FolderException {
  143. NodeRef parentNodeRef = findOrCreateFolder(folderNodeRef, behaviour);
  144. saveFailureNotice(parentNodeRef, mimeMessage);
  145. }
  146. private void saveFailureNotice(NodeRef parentNodeRef, MimeMessage mimeMessage) throws FolderException {
  147. try {
  148. createBody(parentNodeRef, mimeMessage, mimeMessage.getSubject(), null);
  149. } catch (Exception e) {
  150. log.warn("Cannot save email, folderNodeRef=" + parentNodeRef, e);
  151. throw new FolderException("Cannot save email: " + e.getMessage());
  152. }
  153. }
  154. private NodeRef findOrCreateFolder(NodeRef folderNodeRef, String behaviour) throws FolderException {
  155. NodeRef parentNodeRef = folderNodeRef;
  156. if (isUserBasedFolder(parentNodeRef)) {
  157. String username = AuthenticationUtil.getRunAsUser();
  158. if (userService.getPerson(username) == null) {
  159. throw new FolderException("User " + username + " doesn't exist, cannot create imap folder!");
  160. }
  161. parentNodeRef = fileService.findSubfolderWithName(parentNodeRef, username, ImapModel.Types.IMAP_FOLDER);
  162. if (parentNodeRef == null) {
  163. parentNodeRef = createImapSubfolder(folderNodeRef, behaviour, userService.getUserFullName(username), username);
  164. }
  165. }
  166. return parentNodeRef;
  167. }
  168. @Override
  169. public long saveEmail(NodeRef folderNodeRef, MimeMessage mimeMessage, boolean incomingEmail) throws FolderException { // todo: ex handling
  170. try {
  171. String docTypeId;
  172. if (incomingEmail) {
  173. docTypeId = SystematicDocumentType.INCOMING_LETTER.getId();
  174. } else {
  175. docTypeId = SystematicDocumentType.OUTGOING_LETTER.getId();
  176. }
  177. NodeRef docRef = documentDynamicService.createNewDocument(docTypeId, folderNodeRef).getFirst().getNodeRef();
  178. Map<QName, Serializable> properties = new HashMap<QName, Serializable>();
  179. String subject = mimeMessage.getSubject();
  180. if (StringUtils.isBlank(subject)) {
  181. subject = I18NUtil.getMessage("imap.letter_subject_missing");
  182. }
  183. properties.put(DocumentCommonModel.Props.DOC_NAME, subject);
  184. if (incomingEmail) {
  185. if (mimeMessage.getFrom() != null && mimeMessage.getFrom().length > 0) {
  186. InternetAddress sender = (InternetAddress) mimeMessage.getFrom()[0];
  187. properties.put(DocumentSpecificModel.Props.SENDER_DETAILS_NAME, sender.getPersonal());
  188. properties.put(DocumentSpecificModel.Props.SENDER_DETAILS_EMAIL, sender.getAddress());
  189. }
  190. properties.put(DocumentSpecificModel.Props.TRANSMITTAL_MODE, TransmittalMode.EMAIL.getValueName());
  191. } else {
  192. if (mimeMessage.getAllRecipients() != null) {
  193. Address[] allRecipients = mimeMessage.getAllRecipients();
  194. List<String> names = new ArrayList<String>(allRecipients.length);
  195. List<String> emails = new ArrayList<String>(allRecipients.length);
  196. for (Address recient : allRecipients) {
  197. names.add(((InternetAddress) recient).getPersonal());
  198. emails.add(((InternetAddress) recient).getAddress());
  199. }
  200. properties.put(DocumentCommonModel.Props.RECIPIENT_NAME, (Serializable) names);
  201. properties.put(DocumentCommonModel.Props.RECIPIENT_EMAIL, (Serializable) emails);
  202. }
  203. }
  204. properties.put(DocumentCommonModel.Props.STORAGE_TYPE, StorageType.DIGITAL.getValueName());
  205. nodeService.addProperties(docRef, properties);
  206. Map<QName, Serializable> emailAspectProps = new HashMap<QName, Serializable>();
  207. emailAspectProps.put(DocumentCommonModel.Props.EMAIL_DATE_TIME, mimeMessage.getSentDate());
  208. nodeService.addAspect(docRef, DocumentCommonModel.Aspects.EMAIL_DATE_TIME, emailAspectProps);
  209. documentLogService.addDocumentLog(docRef, I18NUtil.getMessage("document_log_status_imported", I18NUtil.getMessage("document_log_creator_imap")) //
  210. , I18NUtil.getMessage("document_log_creator_imap"));
  211. saveOriginalEmlFile(mimeMessage, docRef);
  212. saveAttachments(docRef, mimeMessage, true);
  213. fileService.reorderFiles(docRef);
  214. return (Long) nodeService.getProperty(docRef, ContentModel.PROP_NODE_DBID);
  215. } catch (Exception e) { // todo: improve exception handling
  216. log.warn("Cannot save email, folderNodeRef=" + folderNodeRef, e);
  217. throw new FolderException("Cannot save email: " + e.getMessage());
  218. }
  219. }
  220. private void saveOriginalEmlFile(MimeMessage mimeMessage, NodeRef docRef) throws MessagingException {
  221. String[] contentDataHeader = mimeMessage.getHeader(GreenMailUtil.SAVE_ORIGINAL_TO_REPO_CONTENT_DATA_HEADER_NAME);
  222. if (contentDataHeader == null || contentDataHeader.length <= 0 || StringUtils.isBlank(contentDataHeader[0])) {
  223. return;
  224. }
  225. ContentData contentData = ContentData.createContentProperty(contentDataHeader[0]);
  226. String filename = I18NUtil.getMessage("imap.letter_body_filename") + "." + mimetypeService.getExtension(contentData.getMimetype());
  227. NodeRef fileRef = fileFolderService.create(
  228. docRef,
  229. generalService.getUniqueFileName(docRef, filename),
  230. ContentModel.TYPE_CONTENT).getNodeRef();
  231. HashMap<QName, Serializable> props = new HashMap<QName, Serializable>();
  232. props.put(ContentModel.PROP_CONTENT, contentData);
  233. props.put(FileModel.Props.DISPLAY_NAME, fileService.getUniqueFileDisplayName(docRef, filename));
  234. props.put(FileModel.Props.ACTIVE, Boolean.FALSE);
  235. nodeService.addProperties(fileRef, props);
  236. }
  237. @Override
  238. public void saveIncomingEInvoice(NodeRef folderNodeRef, MimeMessage mimeMessage) throws FolderException {
  239. try {
  240. Object content = mimeMessage.getContent();
  241. List<NodeRef> newInvoices = new ArrayList<NodeRef>();
  242. Map<NodeRef, Integer> invoiceRefToAttachment = new HashMap<NodeRef, Integer>();
  243. if (content instanceof Multipart) {
  244. Multipart multipart = (Multipart) content;
  245. // Handle only top-level attachment files, not files included inside message body (usually pictures)
  246. // TODO detect invoices also from Rich Text (winmail.dat) messages
  247. for (int i = 0, n = multipart.getCount(); i < n; i++) {
  248. Part part = multipart.getBodyPart(i);
  249. if (isAttachment(part)) {
  250. List<NodeRef> newDocRefs = createInvoice(folderNodeRef, part);
  251. newInvoices.addAll(newDocRefs);
  252. for (NodeRef newDocRef : newDocRefs) {
  253. invoiceRefToAttachment.put(newDocRef, i);
  254. }
  255. }
  256. }
  257. }
  258. if (newInvoices.size() == 0) {
  259. String name = AlfrescoImapConst.MESSAGE_PREFIX + GUID.generate();
  260. FileInfo docInfo = fileFolderService.create(folderNodeRef, name, DocumentSubtypeModel.Types.INVOICE);
  261. Map<QName, Serializable> properties = new HashMap<QName, Serializable>();
  262. String subject = mimeMessage.getSubject();
  263. if (StringUtils.isBlank(subject)) {
  264. subject = I18NUtil.getMessage("imap.letter_subject_missing");
  265. }
  266. properties.put(DocumentCommonModel.Props.DOC_NAME, subject);
  267. if (mimeMessage.getFrom() != null) {
  268. InternetAddress sender = (InternetAddress) mimeMessage.getFrom()[0];
  269. properties.put(DocumentSpecificModel.Props.SELLER_PARTY_CONTACT_NAME, sender.getPersonal());
  270. properties.put(DocumentSpecificModel.Props.SELLER_PARTY_CONTACT_EMAIL_ADDRESS, sender.getAddress());
  271. }
  272. properties.put(DocumentSpecificModel.Props.TRANSMITTAL_MODE, TransmittalMode.EMAIL.getValueName());
  273. properties.put(DocumentCommonModel.Props.DOC_STATUS, DocumentStatus.WORKING.getValueName());
  274. properties.put(DocumentCommonModel.Props.STORAGE_TYPE, StorageType.XML.getValueName());
  275. NodeRef nodeRef = docInfo.getNodeRef();
  276. nodeService.addProperties(nodeRef, properties);
  277. newInvoices.add(nodeRef);
  278. }
  279. for (NodeRef docRef : newInvoices) {
  280. // TODO: optimize?
  281. saveAttachments(docRef, mimeMessage, false, invoiceRefToAttachment);
  282. documentLogService.addDocumentLog(docRef, I18NUtil.getMessage("document_log_status_imported", I18NUtil.getMessage("document_log_creator_imap")) //
  283. , I18NUtil.getMessage("document_log_creator_imap"));
  284. }
  285. if (!newInvoices.isEmpty()) {
  286. fileService.reorderFiles(newInvoices);
  287. }
  288. } catch (Exception e) { // TODO: improve exception handling
  289. log.warn("Cannot save email, folderNodeRef=" + folderNodeRef, e);
  290. throw new FolderException("Cannot save email: " + e.getMessage());
  291. }
  292. }
  293. private List<NodeRef> createInvoice(NodeRef folderNodeRef, Part part) throws MessagingException, IOException {
  294. String mimetype = getMimetype(part, null);
  295. if (MimetypeMap.MIMETYPE_XML.equalsIgnoreCase(mimetype)) {
  296. log.info("MIMETYPE IS XML... " + mimetype);
  297. InputStream inputStream = part.getInputStream();
  298. try {
  299. log.info("IMPORT INVOCE FROM XML....");
  300. return einvoiceService.importInvoiceFromXml(folderNodeRef, inputStream, TransmittalMode.EMAIL);
  301. } finally {
  302. inputStream.close();
  303. }
  304. }
  305. return new ArrayList<NodeRef>(0);
  306. }
  307. @Override
  308. public Collection<MailFolder> createAndListFolders(AlfrescoImapUser user, String mailboxPattern) {
  309. try {
  310. return addBehaviour(filter(imapService.listSubscribedMailboxes(user, mailboxPattern)));
  311. } catch (Exception e) {
  312. throw new RuntimeException(e);
  313. }
  314. }
  315. @Override
  316. public NodeRef createImapSubfolder(NodeRef parentFolderNodeRef, String behaviour, String subfolderName, String folderAssocName) {
  317. QName assocName = QName.createQName(ImapModel.URI, folderAssocName);
  318. Map<QName, Serializable> props = new HashMap<QName, Serializable>();
  319. props.put(ContentModel.PROP_NAME, subfolderName);
  320. if (StringUtils.isNotBlank(behaviour)) {
  321. props.put(ImapModel.Properties.APPEND_BEHAVIOUR, behaviour);
  322. }
  323. NodeRef folderRef = nodeService.createNode(parentFolderNodeRef, ContentModel.ASSOC_CONTAINS, assocName, ImapModel.Types.IMAP_FOLDER, props).getChildRef();
  324. BeanHelper.getPrivilegeService().setPermissions(folderRef, UserService.AUTH_DOCUMENT_MANAGERS_GROUP, Privilege.EDIT_DOCUMENT);
  325. return folderRef;
  326. }
  327. @Override
  328. public void saveAttachmentsToSubfolder(NodeRef document, MimeMessage originalMessage, boolean saveBody) throws IOException, MessagingException, TransformationException,
  329. FolderException, BadLocationException {
  330. NodeRef parentNodeRef = findOrCreateFolder(document, AttachmentsFolderAppendBehaviour.BEHAVIOUR_NAME);
  331. saveAttachments(parentNodeRef, originalMessage, saveBody);
  332. }
  333. @Override
  334. public void saveAttachments(NodeRef document, MimeMessage originalMessage, boolean saveBody)
  335. throws IOException, MessagingException, BadLocationException {
  336. saveAttachments(document, originalMessage, saveBody, null);
  337. }
  338. private void saveAttachments(NodeRef document, MimeMessage originalMessage, boolean saveBody, Map<NodeRef, Integer> invoiceRefToAttachment) throws IOException,
  339. MessagingException, BadLocationException {
  340. Object content = originalMessage.getContent();
  341. //if (content instanceof String) {
  342. // content = fixPlainTextContentEncoding((String)content);
  343. //}
  344. Part tnefPart = getTnefPart(content);
  345. Message tnefMessage = null;
  346. InputStream tnefInputStream = null;
  347. try {
  348. List<Part> attachments = new ArrayList<>();
  349. saveAttachments(document, content, attachments, invoiceRefToAttachment, tnefPart);
  350. String metadata = generateEmailMetadata(originalMessage, attachments);
  351. if (tnefPart != null) {
  352. tnefInputStream = tnefPart.getInputStream();
  353. tnefMessage = new Message(new TNEFInputStream(tnefInputStream));
  354. saveBody = saveTnefBodyAndAttachments(document, tnefMessage, metadata, saveBody);
  355. }
  356. Part bodyPart = null;
  357. if (saveBody) {
  358. bodyPart = createBody(document, originalMessage, metadata);
  359. }
  360. log.info("MimeMessage:" + getPartDebugInfo(originalMessage, bodyPart, tnefPart, attachments, tnefMessage));
  361. } finally {
  362. if (tnefMessage != null) {
  363. tnefMessage.close();
  364. }
  365. if (tnefInputStream != null) {
  366. tnefInputStream.close();
  367. }
  368. }
  369. }
  370. public List<String> getAttachmentNames(List<Part> attachments) throws MessagingException {
  371. List<String> attachmentNames = new ArrayList<>();
  372. for (Part attachment : attachments) {
  373. if (StringUtils.isNotBlank(attachment.getFileName())) {
  374. attachmentNames.add(attachment.getFileName());
  375. }
  376. }
  377. return attachmentNames;
  378. }
  379. private String generateEmailMetadata(MimeMessage message, List<Part> attachments) throws MessagingException {
  380. StringBuilder metadata = new StringBuilder();
  381. boolean isText = isPlainText(message);
  382. String lineBreak = isText ? "\n" : "<br />";
  383. if (message.getFrom() != null && message.getFrom().length > 0) {
  384. InternetAddress sender = (InternetAddress) message.getFrom()[0];
  385. metadata.append(getFieldName("email_from_field")).append(getNameEmailString(sender, isText)).append(lineBreak);
  386. }
  387. if (message.getSentDate() != null) {
  388. metadata.append(getFieldName("email_date_field")).append(dateTimeFormat.format(message.getSentDate())).append(lineBreak);
  389. }
  390. List<String> recipientsTo = getNameEmailList((InternetAddress[]) message.getRecipients(javax.mail.Message.RecipientType.TO), isText);
  391. if (CollectionUtils.isNotEmpty(recipientsTo)) {
  392. metadata.append(getFieldName("email_to_field")).append(StringUtils.join(recipientsTo, "; ")).append(lineBreak);
  393. }
  394. List<String> recipientsCc = getNameEmailList((InternetAddress[]) message.getRecipients(javax.mail.Message.RecipientType.CC), isText);
  395. if (CollectionUtils.isNotEmpty(recipientsCc)) {
  396. metadata.append(getFieldName("email_cc_field")).append(StringUtils.join(recipientsCc, "; ")).append(lineBreak);
  397. }
  398. if (StringUtils.isNotBlank(message.getSubject())) {
  399. metadata.append(getFieldName("email_subject_field")).append(message.getSubject()).append(lineBreak);
  400. }
  401. String priority = getPriorotyValue(message);
  402. if (StringUtils.isNotBlank(priority)) {
  403. metadata.append(getFieldName("email_priority_field")).append(priority).append(lineBreak);
  404. }
  405. List<String> attachmentNames = getAttachmentNames(attachments);
  406. if (CollectionUtils.isNotEmpty(attachmentNames)) {
  407. metadata.append(getFieldName("email_attachment_field")).append(StringUtils.join(attachmentNames, "; ")).append(lineBreak);
  408. }
  409. if (metadata.length() > 0) {
  410. metadata.append(lineBreak);
  411. metadata.append(lineBreak);
  412. }
  413. return metadata.toString();
  414. }
  415. private boolean isPlainText(MimeMessage message) {
  416. try {
  417. Part p = getText(message);
  418. if (p.isMimeType(MimetypeMap.MIMETYPE_HTML)) {
  419. return false;
  420. }
  421. } catch (Exception e) {
  422. return true;
  423. }
  424. return true;
  425. }
  426. private String getFieldName(String code){
  427. return MessageUtil.getMessage(code) + ": ";
  428. }
  429. private String getPriorotyValue(MimeMessage message) throws MessagingException {
  430. String priority = message.getHeader("X-Priority", "(");
  431. String code = null;
  432. if (StringUtils.isNotBlank(priority)) {
  433. Integer priorityValue = Integer.valueOf(priority.substring(0, 1));
  434. if (priorityValue == 1) {
  435. code = "email_priority_highest";
  436. } else if (priorityValue == 2) {
  437. code = "email_priority_high";
  438. } else if (priorityValue == 3) {
  439. code = "email_priority_normal";
  440. } else if (priorityValue == 4) {
  441. code = "email_priority_low";
  442. } else if (priorityValue == 5) {
  443. code = "email_priority_lowest";
  444. }
  445. }
  446. return StringUtils.isNotBlank(code) ? MessageUtil.getMessage(code) : null;
  447. }
  448. private List<String> getNameEmailList(InternetAddress[] addresses, boolean isText) {
  449. List<String> result = new ArrayList<>();
  450. if (addresses != null && addresses.length > 0) {
  451. for (InternetAddress address : addresses) {
  452. String nameEmailString = getNameEmailString(address, isText);
  453. if (StringUtils.isNotBlank(nameEmailString)) {
  454. result.add(nameEmailString);
  455. }
  456. }
  457. }
  458. return result;
  459. }
  460. private String getNameEmailString(InternetAddress address, boolean isText) {
  461. if (address != null) {
  462. String result = StringUtils.EMPTY;
  463. if (StringUtils.isNotBlank(address.getPersonal())) {
  464. result += address.getPersonal() + " ";
  465. }
  466. if (StringUtils.isNotBlank(address.getAddress())) {
  467. result += isText ? "<" : "&lt;";
  468. result += address.getAddress();
  469. result += isText ? ">" : "&gt;";
  470. }
  471. return result;
  472. }
  473. return null;
  474. }
  475. private String fixPlainTextContentEncoding(String content) {
  476. byte[] tempBytes = Charset.forName("ISO-8859-1").encode(content).array();
  477. String tempContent = new String(tempBytes);
  478. content = new String(Charset.forName("UTF-8").encode(tempContent).array());
  479. return content;
  480. }
  481. private boolean saveTnefBodyAndAttachments(NodeRef document, Message tnefMessage, String metadata, boolean saveBody) throws IOException, UnsupportedEncodingException, BadLocationException {
  482. if (saveBody) {
  483. String bodyFilename = I18NUtil.getMessage("imap.letter_body_filename");
  484. MAPIProps props = tnefMessage.getMAPIProps();
  485. if (props != null) {
  486. // compressed RTF body
  487. RawInputStream ris = (RawInputStream) props.getPropValue(MAPIProp.PR_RTF_COMPRESSED);
  488. if (ris != null) {
  489. InputStream bodyStream = new CompressedRTFInputStream(ris);
  490. try {
  491. createBody(document, "application/rtf", StandardCharsets.UTF_8.name(), metadata, bodyStream, bodyFilename);
  492. } finally {
  493. bodyStream.close();
  494. }
  495. saveBody = false;
  496. }
  497. if (saveBody) {
  498. // HTML body (either PR_HTML or PR_BODY_HTML - both have the same ID, but one is a string and one is a byte array)
  499. Object html = props.getPropValue(MAPIProp.PR_HTML);
  500. if (html != null) {
  501. if (html instanceof RawInputStream) {
  502. ris = (RawInputStream) html;
  503. File file = TempFileProvider.createTempFile("tnefHtmlBody", null);
  504. try {
  505. OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
  506. FileCopyUtils.copy(ris, out);
  507. InputStream in = new BufferedInputStream(new FileInputStream(file));
  508. Pair<String, String> mimetypeAndEncoding;
  509. try {
  510. mimetypeAndEncoding = generalService.getMimetypeAndEncoding(in, "test.html", MimetypeMap.MIMETYPE_HTML);
  511. } finally {
  512. in.close();
  513. }
  514. in = new BufferedInputStream(new FileInputStream(file));
  515. try {
  516. createBody(document, mimetypeAndEncoding.getFirst(), mimetypeAndEncoding.getSecond(), metadata, ris, bodyFilename);
  517. } finally {
  518. in.close();
  519. }
  520. saveBody = false;
  521. } finally {
  522. file.delete();
  523. }
  524. } else {
  525. String text = (String) html;
  526. if (StringUtils.isNotEmpty(text)) {
  527. InputStream bodyStream = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
  528. try {
  529. createBody(document, MimetypeMap.MIMETYPE_HTML, StandardCharsets.UTF_8.name(), metadata, bodyStream, bodyFilename);
  530. } finally {
  531. bodyStream.close();
  532. }
  533. saveBody = false;
  534. }
  535. }
  536. }
  537. // If RTF and HTML body is not available from inside TNEF, then skip getting TEXT body from inside
  538. // HTML (message.getAttribute(Attr.attBody)), because it should be available as plain MIME part
  539. }
  540. }
  541. }
  542. @SuppressWarnings("unchecked")
  543. List<Attachment> attachments = tnefMessage.getAttachments();
  544. for (Attachment attachment : attachments) {
  545. if (attachment.getNestedMessage() != null) { // nested message
  546. saveBody = saveTnefBodyAndAttachments(document, attachment.getNestedMessage(), metadata, saveBody);
  547. } else { // regular attachment
  548. // XXX attachment.getRawData() returns always the same InputStream, so we cannot use it multiple times (because we usually close it, not reset it to beginning);
  549. // therefore we have to write attachment contents to a temp file, to be able to use it multiple times (for encoding detection; for reading contents into Alfresco
  550. // repo)
  551. File file = TempFileProvider.createTempFile("tnefAttachment", null);
  552. try {
  553. OutputStream out = new BufferedOutputStream(new FileOutputStream(file));
  554. try {
  555. attachment.writeTo(out);
  556. } finally {
  557. out.close();
  558. }
  559. boolean foundAttachmentsFromOle = saveAttachmentsFromOle(document, file, attachment);
  560. if (!foundAttachmentsFromOle) {
  561. saveAttachment(document, file, attachment);
  562. }
  563. } finally {
  564. file.delete();
  565. }
  566. }
  567. }
  568. return saveBody;
  569. }
  570. private void saveAttachment(NodeRef document, File tnefFile, Attachment tnefAttachment) throws FileNotFoundException, IOException {
  571. InputStream in = new BufferedInputStream(new FileInputStream(tnefFile));
  572. Pair<String, String> mimetypeAndEncoding;
  573. try {
  574. mimetypeAndEncoding = generalService.getMimetypeAndEncoding(in, tnefAttachment.getFilename(), null);
  575. } finally {
  576. in.close();
  577. }
  578. in = new BufferedInputStream(new FileInputStream(tnefFile));
  579. try {
  580. createAttachment(document, tnefAttachment.getFilename(), mimetypeAndEncoding.getFirst(), mimetypeAndEncoding.getSecond(), in, null);
  581. } finally {
  582. in.close();
  583. }
  584. }
  585. private boolean saveAttachmentsFromOle(NodeRef document, File tnefFile, Attachment tnefAttachment) throws IOException {
  586. boolean foundAttachments = false;
  587. // In Rich Text e-mails inline pictures (added in Outlook as "Insert Picture", not as "Insert Attachment") are in "OLE 2 Compound Document" format
  588. if (tnefAttachment.getMAPIProps() == null) {
  589. return foundAttachments;
  590. }
  591. Object attachMethod = tnefAttachment.getMAPIProps().getPropValue(MAPIProp.PR_ATTACH_METHOD);
  592. // According to http://stackoverflow.com/questions/4657684/nicely-reading-outlook-mailitem-properties
  593. // PR_ATTACH_METHOD value ATTACH_OLE should be 6
  594. if (!new Integer(6).equals(attachMethod)) {
  595. return foundAttachments;
  596. }
  597. Object displayName = tnefAttachment.getMAPIProps().getPropValue(MAPIProp.PR_DISPLAY_NAME);
  598. String fileName;
  599. if ("Picture (Device Independent Bitmap)".equals(displayName)) {
  600. fileName = tnefAttachment.getFilename() + ".bmp";
  601. } else if (displayName instanceof String && StringUtils.isNotBlank((String) displayName)) {
  602. fileName = tnefAttachment.getFilename() + " " + ((String) displayName) + ".bin";
  603. } else {
  604. fileName = tnefAttachment.getFilename() + ".bin";
  605. }
  606. String mimeType = mimetypeService.guessMimetype(fileName);
  607. NPOIFSFileSystem fs = new NPOIFSFileSystem(tnefFile);
  608. try {
  609. DirectoryNode root = fs.getRoot();
  610. for (Entry entry : root) {
  611. if ("CONTENTS".equals(entry.getName()) && entry instanceof DocumentNode) {
  612. DocumentInputStream inputStream = root.createDocumentInputStream(entry);
  613. try {
  614. createAttachment(document, fileName, mimeType, "UTF-8", inputStream, null);
  615. } finally {
  616. inputStream.close();
  617. }
  618. foundAttachments = true;
  619. }
  620. }
  621. } finally {
  622. fs.close();
  623. }
  624. return foundAttachments;
  625. }
  626. private Part getTnefPart(Object content) throws MessagingException, IOException {
  627. if (content instanceof Multipart) {
  628. Multipart multipart = (Multipart) content;
  629. for (int i = 0, n = multipart.getCount(); i < n; i++) {
  630. Part part = multipart.getBodyPart(i);
  631. if (TNEFUtils.isTNEFMimeType(part.getContentType())) {
  632. return part;
  633. }
  634. Part tnefPart = getTnefPart(part.getContent());
  635. if (tnefPart != null) {
  636. return tnefPart;
  637. }
  638. }
  639. }
  640. return null;
  641. }
  642. private void saveAttachments(NodeRef document, Object content, List<Part> attachments, Map<NodeRef, Integer> invoiceRefToAttachment, Part tnefPart) throws MessagingException,
  643. IOException {
  644. if (content instanceof Multipart) {
  645. Multipart multipart = (Multipart) content;
  646. for (int i = 0, n = multipart.getCount(); i < n; i++) {
  647. Part part = multipart.getBodyPart(i);
  648. if (part != tnefPart &&
  649. (invoiceRefToAttachment == null || !invoiceRefToAttachment.containsValue(i) || invoiceRefToAttachment.get(document).equals(i))) {
  650. if (isAttachment(part)) {
  651. createAttachment(document, part, null);
  652. attachments.add(part);
  653. } else {
  654. saveAttachments(document, part.getContent(), attachments, null, tnefPart); // invoice map values represent only top level attachments
  655. }
  656. }
  657. }
  658. }
  659. }
  660. private boolean isAttachment(Part part) throws MessagingException {
  661. return Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition()) || StringUtils.isNotBlank(part.getFileName());
  662. }
  663. @Override
  664. public boolean isFixedFolder(NodeRef folderRef) {
  665. return StringUtils.equals(FOLDER_TYPE_PREFIX_FIXED, getImapFolderTypes().get(folderRef));
  666. }
  667. @Override
  668. public boolean isUserBasedFolder(NodeRef folderRef) {
  669. return StringUtils.equals(FOLDER_TYPE_PREFIX_USER_BASED, getImapFolderTypes().get(folderRef));
  670. }
  671. @Override
  672. public Set<String> getFixedSubfolderNames(NodeRef parentFolderRef) {
  673. Set<String> subfolderNames = getImapFolderFixedSubfolders().get(parentFolderRef);
  674. if (subfolderNames == null) {
  675. return new HashSet<String>();
  676. }
  677. return subfolderNames;
  678. }
  679. private void createAttachment(NodeRef document, Part part, String overrideFilename) throws MessagingException, IOException {
  680. log.info("createAttachment()...");
  681. String partFileName = part.getFileName();
  682. log.info("PART Filename: " + partFileName);
  683. String mimeType = getMimetype(part, overrideFilename);
  684. log.info("MIMETYPE: " + mimeType);
  685. String encoding = getEncoding(part);
  686. log.info("ENCODING: " + encoding);
  687. if (StringUtils.isBlank(encoding)) {
  688. log.info("ENCODING IS BLANK! Try to get by part data...");
  689. InputStream inputStream = part.getInputStream();
  690. try {
  691. Pair<String, String> mimetypeAndEncoding = generalService.getMimetypeAndEncoding(inputStream, partFileName, mimeType);
  692. log.info("MIMETYPE: " + mimetypeAndEncoding.getFirst());
  693. encoding = mimetypeAndEncoding.getSecond();
  694. log.info("ENCODING: " + mimetypeAndEncoding.getSecond());
  695. // mimeType was already guessed the same way in getMimetype()
  696. } finally {
  697. inputStream.close();
  698. }
  699. }
  700. InputStream inputStream = part.getInputStream();
  701. try {
  702. createAttachment(document, partFileName, mimeType, encoding, inputStream, overrideFilename);
  703. } finally {
  704. inputStream.close();
  705. }
  706. }
  707. private void createAttachment(NodeRef document, String partFileName, String mimeType, String encoding, InputStream inputStream, String overrideFilename) throws IOException {
  708. String filename;
  709. if (overrideFilename == null) {
  710. filename = partFileName;
  711. } else {
  712. filename = overrideFilename + "." + mimetypeService.getExtension(mimeType);
  713. }
  714. if (StringUtils.isBlank(filename)) {
  715. filename = I18NUtil.getMessage("imap.letter_attachment_filename") + "." + mimetypeService.getExtension(mimeType);
  716. }
  717. FileInfo createdFile = fileFolderService.create(
  718. document,
  719. generalService.getUniqueFileName(document, FilenameUtil.makeSafeFilename(filename)),
  720. ContentModel.TYPE_CONTENT);
  721. nodeService.setProperty(createdFile.getNodeRef(), FileModel.Props.DISPLAY_NAME, filename);
  722. ContentWriter writer = fileFolderService.getWriter(createdFile.getNodeRef());
  723. writer.setMimetype(mimeType);
  724. writer.setEncoding(encoding);
  725. OutputStream os = writer.getContentOutputStream();
  726. FileCopyUtils.copy(inputStream, os);
  727. }
  728. private String getMimetype(Part part, String overrideFilename) throws MessagingException {
  729. String mimeType = null;
  730. String contentTypeString = part.getContentType();
  731. log.info("PART content type: " + contentTypeString);
  732. try {
  733. ContentType contentType = new ContentType(contentTypeString);
  734. mimeType = StringUtils.lowerCase(contentType.getBaseType());
  735. log.info("MIMETYPE: " + mimeType);
  736. } catch (ParseException e) {
  737. log.warn("Error parsing contentType '" + contentTypeString + "'", e);
  738. }
  739. if (overrideFilename == null && part.getFileName() != null) {
  740. // Always ignore user-provided mime-type
  741. String oldMimetype = mimeType;
  742. log.info("OLD MIMETYPE: " + oldMimetype);
  743. log.info("GUESS MIMETYPE By FILENAME...." + part.getFileName());
  744. mimeType = mimetypeService.guessMimetype(part.getFileName());
  745. log.info("MIMETYPE: " + mimeType);
  746. if (log.isDebugEnabled() && !StringUtils.equals(oldMimetype, mimeType)) {
  747. log.debug("Original mimetype '" + oldMimetype + "', but we are guessing mimetype based on filename '" + part.getFileName() + "' => '"
  748. + mimeType + "'");
  749. }
  750. } else if (StringUtils.isBlank(mimeType)) {
  751. log.info("MIME TYPE IS BLANK! USE BINARY MIME TYPE...");
  752. // If mime-type parsing from contentType failed and overrideFilename is used, then use binary mime type
  753. mimeType = MimetypeMap.MIMETYPE_BINARY;
  754. log.info("MIMETYPE: " + mimeType);
  755. }
  756. return mimeType;
  757. }
  758. private Part createBody(NodeRef document, MimeMessage originalMessage, String metadata) throws MessagingException, IOException, BadLocationException {
  759. String filename = I18NUtil.getMessage("imap.letter_body_filename");
  760. return createBody(document, originalMessage, filename, metadata);
  761. }
  762. private Part createBody(NodeRef document, MimeMessage originalMessage, String filename, String metadata) throws MessagingException, IOException, BadLocationException {
  763. Part p = getText(originalMessage);
  764. if (p == null) {
  765. log.debug("No body part found from message, skipping body PDF creation");
  766. return p;
  767. }
  768. String mimeType;
  769. if (p.isMimeType(MimetypeMap.MIMETYPE_HTML)) {
  770. mimeType = MimetypeMap.MIMETYPE_HTML;
  771. } else if (p.isMimeType(MimetypeMap.MIMETYPE_TEXT_PLAIN)) {
  772. mimeType = MimetypeMap.MIMETYPE_TEXT_PLAIN;
  773. } else {
  774. log.info("Found body part from message, but don't know how to handle it, skipping body PDF creation, contentType=" + p.getContentType());
  775. return p;
  776. }
  777. // We assume that content-type header also contains charset; so far there haven't been different cases
  778. // If content-type header doesn't contain charset, we use UTF-8 as default
  779. String encoding = getEncoding(p);
  780. log.info("Found body part from message, parsed mimeType=" + mimeType + " and encoding=" + encoding + " from contentType=" + p.getContentType());
  781. createBody(document, mimeType, encoding, metadata, p.getInputStream(), filename);
  782. return p;
  783. }
  784. private void createBody(NodeRef document, String mimeType, String encoding, String metadata, InputStream contentStream, String filename) throws IOException, BadLocationException {
  785. ContentWriter tempWriter = contentService.getTempWriter();
  786. tempWriter.setMimetype(mimeType);
  787. tempWriter.setEncoding(encoding);
  788. tempWriter.putContent(createContentString(metadata, contentStream, mimeType, encoding));
  789. String safeFileName = FilenameUtil.makeSafeFilename(filename);
  790. FileInfo createdFile = fileService.transformToPdf(document, null, tempWriter.getReader(), safeFileName, filename, null);
  791. if (createdFile == null) {
  792. // Don't re-use previous reader (channel already opened)
  793. InputStream contentInputStream = tempWriter.getReader().getContentInputStream();
  794. try {
  795. createAttachment(document, null, mimeType, encoding, contentInputStream, filename);
  796. } finally {
  797. contentInputStream.close();
  798. }
  799. }
  800. }
  801. private InputStream createContentString(String metadata, InputStream contentStream, String mimeType, String encoding) throws IOException, BadLocationException {
  802. String resultString = StringUtils.EMPTY;
  803. if (StringUtils.isNotBlank(metadata)) {
  804. if (MimetypeMap.MIMETYPE_HTML.equals(mimeType)) {
  805. Document html = Jsoup.parse(IOUtils.toString(contentStream, encoding));
  806. html.body().before(metadata);
  807. resultString = html.toString();
  808. } else if ("application/rtf".equals(mimeType)) {
  809. RTFEditorKit rtfParser = new RTFEditorKit();
  810. javax.swing.text.Document document = rtfParser.createDefaultDocument();
  811. rtfParser.read(contentStream, document, 0);
  812. String rtfContent = document.getText(0, document.getLength());
  813. resultString += metadata;
  814. resultString += rtfContent;
  815. } else {
  816. resultString += metadata;
  817. resultString += IOUtils.toString(contentStream, encoding);
  818. }
  819. return new ByteArrayInputStream(resultString.getBytes(encoding));
  820. }
  821. return contentStream;
  822. }
  823. // Workaround for getting encoding from content type
  824. // contentType=text/plain; charset="iso-8859-1"
  825. private static String getEncoding(Part p) throws MessagingException {
  826. String encoding = StandardCharsets.UTF_8.name(); // default encoding is UTF-8
  827. log.info("DEFAULT ENCODING: " + encoding);
  828. String regExp = "(.*charset=\"([^\"]+)\".*)";
  829. Matcher matcher = Pattern.compile(regExp).matcher(p.getContentType().replace('\n', ' ').replace('\r', ' '));
  830. if (matcher.matches()) {
  831. log.info("CHARSET META TAGA FOUND!" + matcher.group(1));
  832. if (matcher.groupCount() == 2) {
  833. encoding = matcher.group(2);
  834. } else {
  835. log.info("CHARSET INFO NOT FOUND....");
  836. }
  837. } else {
  838. regExp = "/<meta[^>]+charset=['\"]?(.*?)['\"]?[\\/\\s>]/i";
  839. matcher = Pattern.compile(regExp).matcher(p.getContentType().replace('\n', ' ').replace('\r', ' '));
  840. if (matcher.matches()) {
  841. log.info("CHARSET META TAGA FOUND!");
  842. encoding = matcher.group(1);
  843. } else {
  844. log.info("CHARSET INFO NOT FOUND....");
  845. }
  846. }
  847. // What is the point?
  848. // Why we should change encoding?
  849. if ("windows-1257".equals(encoding)) {
  850. // AARE: REMOVING active encondig change.
  851. //return StandardCharsets.UTF_8.name();
  852. }
  853. return encoding;
  854. }
  855. /**
  856. * Return the primary text content of the message.
  857. */
  858. // taken from http://java.sun.com/products/javamail/FAQ.html
  859. private Part getText(Part p) throws MessagingException, IOException {
  860. if (p.isMimeType("text/*")) {
  861. return p;
  862. }
  863. Object content = p.getContent();
  864. Multipart mp;
  865. if (content instanceof Multipart) {
  866. mp = (Multipart) content;
  867. } else {
  868. return null;
  869. }
  870. if (p.isMimeType("multipart/alternative")) {
  871. // prefer html text over plain text
  872. Part text = null;
  873. for (int i = 0; i < mp.getCount(); i++) {
  874. Part bp = mp.getBodyPart(i);
  875. if (bp.isMimeType("text/plain")) {
  876. if (text == null) {
  877. text = getText(bp);
  878. }
  879. continue;
  880. } else if (bp.isMimeType("text/html")) {
  881. Part s = getText(bp);
  882. if (s != null) {
  883. return s;
  884. }
  885. } else {
  886. return getText(bp);
  887. }
  888. }
  889. return text;
  890. } else if (p.isMimeType("multipart/*")) {
  891. for (int i = 0; i < mp.getCount(); i++) {
  892. Part s = getText(mp.getBodyPart(i));
  893. if (s != null) {
  894. return s;
  895. }
  896. }
  897. }
  898. return null;
  899. }
  900. private String getPartDebugInfo(Part p, Part bodyPart, Part tnefPart, List<Part> attachments, Message tnefMessage) throws MessagingException, IOException {
  901. String debugInfo = "\n¤Part:";
  902. // Compare by reference
  903. if (p == bodyPart) {
  904. debugInfo += " BODY";
  905. }
  906. if (p == tnefPart) {
  907. debugInfo += " TNEF";
  908. }
  909. for (Part attachment : attachments) {
  910. if (p == attachment) {
  911. debugInfo += " ATTACHMENT";
  912. }
  913. }
  914. debugInfo += " disposition=" + p.getDisposition();
  915. debugInfo += " contentType=" + p.getContentType();
  916. debugInfo += " fileName=" + p.getFileName();
  917. debugInfo += " size=" + p.getSize();
  918. if (p.isMimeType("text/plain")) {
  919. debugInfo += " isMimeType('text/plain')";
  920. } else if (p.isMimeType("text/html")) {
  921. debugInfo += " isMimeType('text/html')";
  922. }
  923. Object content = p.getContent();
  924. if (p.isMimeType("text/*")) {
  925. debugInfo += " isMimeType('text/*')";
  926. } else {
  927. if (content instanceof Multipart) {
  928. debugInfo += " isInstanceOfMultipart";
  929. if (p.isMimeType("multipart/alternative")) {

Large files files are truncated, but you can click here to view the full file