PageRenderTime 38ms CodeModel.GetById 27ms RepoModel.GetById 1ms 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
  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")) {
  930. debugInfo += " isMimeType('multipart/alternative')";
  931. } else if (p.isMimeType("multipart/*")) {
  932. debugInfo += " isMimeType('multipart/*')";
  933. }
  934. Multipart mp = (Multipart) content;
  935. debugInfo += " multiPartContentType=" + mp.getContentType();
  936. debugInfo += " multiPartCount=" + mp.getCount();
  937. }
  938. }
  939. if (p == tnefPart && tnefMessage != null) {
  940. debugInfo += StringUtils.replace(getTnefMessageDebugInfo(tnefMessage), "\n¤", "\n¤ ");
  941. }
  942. if (content instanceof Multipart) {
  943. Multipart mp = (Multipart) content;
  944. for (int i = 0; i < mp.getCount(); i++) {
  945. Part bp = mp.getBodyPart(i);
  946. debugInfo += StringUtils.replace(getPartDebugInfo(bp, bodyPart, tnefPart, attachments, tnefMessage), "\n¤", "\n¤ ");
  947. }
  948. }
  949. return debugInfo;
  950. }
  951. private String getTnefMessageDebugInfo(Message tnefMessage) throws IOException {
  952. String debugInfo = "\n¤TNEF Message:";
  953. MAPIProps props = tnefMessage.getMAPIProps();
  954. if (props == null) {
  955. debugInfo += " mapiPropsIsNull";
  956. } else {
  957. debugInfo += " PR_RTF_COMPRESSED=" + getObjectDebugInfo(props.getPropValue(MAPIProp.PR_RTF_COMPRESSED));
  958. debugInfo += " PR_HTML=" + getObjectDebugInfo(props.getPropValue(MAPIProp.PR_HTML));
  959. }
  960. @SuppressWarnings("unchecked")
  961. List<Attachment> tnefAttachments = tnefMessage.getAttachments();
  962. for (Attachment attachment : tnefAttachments) {
  963. debugInfo += "\n¤ TNEF Attachment:";
  964. debugInfo += " nestedMessage=" + getObjectDebugInfo(attachment.getNestedMessage());
  965. debugInfo += " filename=" + attachment.getFilename();
  966. debugInfo += " rawData=" + getObjectDebugInfo(attachment.getRawData());
  967. props = attachment.getMAPIProps();
  968. if (props == null) {
  969. debugInfo += " mapiPropsIsNull";
  970. } else {
  971. debugInfo += " PR_ATTACH_METHOD=" + getObjectDebugInfo(props.getPropValue(MAPIProp.PR_ATTACH_METHOD));
  972. debugInfo += " PR_DISPLAY_NAME=" + getObjectDebugInfo(props.getPropValue(MAPIProp.PR_DISPLAY_NAME));
  973. }
  974. if (attachment.getNestedMessage() != null) { // nested message
  975. debugInfo += StringUtils.replace(getTnefMessageDebugInfo(attachment.getNestedMessage()), "\n¤", "\n¤ ");
  976. }
  977. }
  978. return debugInfo;
  979. }
  980. private static String getObjectDebugInfo(Object object) throws IOException {
  981. if (object == null) {
  982. return "null";
  983. }
  984. String info = object.getClass().getSimpleName();
  985. if (object instanceof InputStream) {
  986. info += "[available=" + ((InputStream) object).available() + "]";
  987. } else if (object instanceof Number) {
  988. info = object.toString();
  989. } else if (object instanceof String) {
  990. int length = ((String) object).length();
  991. if (length <= 64) {
  992. info = object.toString();
  993. } else {
  994. info += "[length=" + length + "]";
  995. }
  996. }
  997. return info;
  998. }
  999. private MailFolder addBehaviour(AlfrescoImapFolder folder) {
  1000. String appendBehaviour;
  1001. FileInfo folderInfo = folder.getFolderInfo();
  1002. if (folderInfo != null) { // todo: why folder info is null?
  1003. appendBehaviour = (String) folderInfo.getProperties().get(ImapModel.Properties.APPEND_BEHAVIOUR);
  1004. if (appendBehaviour == null) {
  1005. Object name = folderInfo.getProperties().get(ContentModel.PROP_NAME);
  1006. if (name != null && name.toString().equals("INBOX")) {
  1007. appendBehaviour = IncomingFolderAppendBehaviour.BEHAVIOUR_NAME;
  1008. } else {
  1009. appendBehaviour = PermissionDeniedAppendBehaviour.BEHAVIOUR_NAME;
  1010. }
  1011. } else {
  1012. Object name = folderInfo.getProperties().get(ContentModel.PROP_NAME);
  1013. if (name != null && name.toString().equals(folder.getMountPointName())) {
  1014. appendBehaviour = IncomingFolderAppendBehaviour.BEHAVIOUR_NAME;
  1015. }
  1016. }
  1017. } else {
  1018. appendBehaviour = PermissionDeniedAppendBehaviour.BEHAVIOUR_NAME;
  1019. }
  1020. return new ImmutableFolder(folder, getBehaviour(appendBehaviour));
  1021. }
  1022. public AppendBehaviour getBehaviour(String behaviour) {
  1023. if (PermissionDeniedAppendBehaviour.BEHAVIOUR_NAME.equals(behaviour)) {
  1024. return new PermissionDeniedAppendBehaviour();
  1025. } else if (IncomingFolderAppendBehaviour.BEHAVIOUR_NAME.equals(behaviour)) {
  1026. return new IncomingFolderAppendBehaviour(this);
  1027. } else if (IncomingEInvoiceCreateBehaviour.BEHAVIOUR_NAME.equals(behaviour)) {
  1028. return new IncomingEInvoiceCreateBehaviour(this);
  1029. } else if (AttachmentsFolderAppendBehaviour.BEHAVIOUR_NAME.equals(behaviour)) {
  1030. return new AttachmentsFolderAppendBehaviour(this);
  1031. } else if (SentFolderAppendBehaviour.BEHAVIOUR_NAME.equals(behaviour)) {
  1032. return new SentFolderAppendBehaviour(this);
  1033. } else if (SendFailureAppendBehaviour.BEHAVIOUR_NAME.equals(behaviour)) {
  1034. return new SendFailureAppendBehaviour(this);
  1035. } else {
  1036. throw new RuntimeException("Unknown behaviour: " + behaviour);
  1037. }
  1038. }
  1039. private Collection<AlfrescoImapFolder> filter(Collection<AlfrescoImapFolder> folders) {
  1040. CollectionUtils.filter(folders, new Predicate() {
  1041. @Override
  1042. public boolean evaluate(Object o) {
  1043. AlfrescoImapFolder folder = (AlfrescoImapFolder) o;
  1044. NodeRef parentRef = null;
  1045. ChildAssociationRef parentAssoc = nodeService.getPrimaryParent(folder.getFolderInfo().getNodeRef());
  1046. if (parentAssoc != null) {
  1047. parentRef = parentAssoc.getParentRef();
  1048. }
  1049. if (parentRef != null) {
  1050. if (isFixedFolder(parentRef)) {
  1051. if (getFixedSubfolderNames(parentRef).contains(folder.getName())) {
  1052. return true;
  1053. }
  1054. return false;
  1055. }
  1056. if (isUserBasedFolder(parentRef)) {
  1057. return false;
  1058. }
  1059. // subfolder type is not specified, user general logic for filtering
  1060. }
  1061. if (getAllowedFolders().contains(folder.getName())) {
  1062. if (folder.getName().equals(getIncomingInvoiceFolderName())) {
  1063. return applicationConstantsBean.isEinvoiceEnabled();
  1064. }
  1065. return true;
  1066. }
  1067. return false;
  1068. }
  1069. });
  1070. return folders;
  1071. }
  1072. @Override
  1073. public int getAllFilesCount(NodeRef attachmentRoot, boolean countFilesInSubfolders, int limit) {
  1074. int count = bulkLoadNodeService.countChildNodes(attachmentRoot, ContentModel.TYPE_CONTENT);
  1075. if (countFilesInSubfolders && count < limit) {
  1076. Set<NodeRef> childRefs = bulkLoadNodeService.loadChildRefs(attachmentRoot, null, null, ContentModel.TYPE_CONTENT);
  1077. Map<NodeRef, Integer> childCounts = bulkLoadNodeService.countChildNodes(new ArrayList<NodeRef>(childRefs), ContentModel.TYPE_CONTENT);
  1078. for (Integer childCount : childCounts.values()) {
  1079. count += childCount;
  1080. if (count > limit) {
  1081. break;
  1082. }
  1083. }
  1084. }
  1085. return count;
  1086. }
  1087. @Override
  1088. public List<Subfolder> getImapSubfoldersWithChildCount(NodeRef parentRef, QName countableChildNodeType) {
  1089. return fileService.getSubfolders(parentRef, ImapModel.Types.IMAP_FOLDER, countableChildNodeType, true);
  1090. }
  1091. @Override
  1092. public List<Subfolder> getImapSubfolders(NodeRef parentRef) {
  1093. return fileService.getSubfolders(parentRef, ImapModel.Types.IMAP_FOLDER, null, false);
  1094. }
  1095. private Collection<MailFolder> addBehaviour(final Collection<AlfrescoImapFolder> folders) {
  1096. Collection<MailFolder> immutableFolders = new ArrayList<MailFolder>();
  1097. for (AlfrescoImapFolder folder : folders) {
  1098. immutableFolders.add(addBehaviour(folder));
  1099. }
  1100. return immutableFolders;
  1101. }
  1102. public Set<String> getAllowedFolders() {
  1103. if (allowedFolders == null) {
  1104. allowedFolders = new HashSet<String>();
  1105. allowedFolders.add(I18NUtil.getMessage("imap.folder_letters"));
  1106. allowedFolders.add(getIncomingInvoiceFolderName());
  1107. allowedFolders.add(I18NUtil.getMessage("imap.folder_attachments"));
  1108. allowedFolders.add(I18NUtil.getMessage("imap.folder_sent_letters"));
  1109. allowedFolders.add(I18NUtil.getMessage("imap-folders.sendFailureNotices"));
  1110. }
  1111. return allowedFolders;
  1112. }
  1113. private String getIncomingInvoiceFolderName() {
  1114. return I18NUtil.getMessage("imap.folder_incomingInvoice");
  1115. }
  1116. private synchronized void setImapFolderTypesAndSubFolders() {
  1117. imapFolderTypes = new HashMap<NodeRef, String>();
  1118. imapFolderFixedSubfolders = new HashMap<NodeRef, Set<String>>();
  1119. setSubfolderType(incomingLetterSubfolderType, generalService.getNodeRef(ImapModel.Repo.INCOMING_SPACE));
  1120. setSubfolderType(attachmentsSubfolderType, generalService.getNodeRef(ImapModel.Repo.ATTACHMENT_SPACE));
  1121. setSubfolderType(outgoingLettersSubfolderType, generalService.getNodeRef(ImapModel.Repo.SENT_SPACE));
  1122. setSubfolderType(sendFailureNoticesSubfolderType, generalService.getNodeRef(ImapModel.Repo.SEND_FAILURE_NOTICE_SPACE));
  1123. }
  1124. private void setSubfolderType(String subfolderType, NodeRef parentNodeRef) {
  1125. if (StringUtils.isNotBlank(subfolderType)) {
  1126. StringTokenizer tokenizer = new StringTokenizer(subfolderType, ";");
  1127. String folderType = tokenizer.nextToken();
  1128. if (StringUtils.equals(FOLDER_TYPE_PREFIX_FIXED, folderType)) {
  1129. imapFolderTypes.put(parentNodeRef, FOLDER_TYPE_PREFIX_FIXED);
  1130. Set<String> fixedFolderNames = new HashSet<String>();
  1131. while (tokenizer.hasMoreTokens()) {
  1132. String folderName = tokenizer.nextToken();
  1133. QName.createValidLocalName(folderName);
  1134. if (StringUtils.isNotBlank(folderName)) {
  1135. fixedFolderNames.add(QName.createValidLocalName(folderName));
  1136. }
  1137. }
  1138. imapFolderFixedSubfolders.put(parentNodeRef, fixedFolderNames);
  1139. } else if (StringUtils.equals(FOLDER_TYPE_PREFIX_USER_BASED, folderType)) {
  1140. imapFolderTypes.put(parentNodeRef, FOLDER_TYPE_PREFIX_USER_BASED);
  1141. imapFolderFixedSubfolders.remove(parentNodeRef);
  1142. }
  1143. }
  1144. }
  1145. public Map<NodeRef, String> getImapFolderTypes() {
  1146. if (imapFolderTypes == null) {
  1147. setImapFolderTypesAndSubFolders();
  1148. }
  1149. return imapFolderTypes;
  1150. }
  1151. public Map<NodeRef, Set<String>> getImapFolderFixedSubfolders() {
  1152. if (imapFolderFixedSubfolders == null) {
  1153. setImapFolderTypesAndSubFolders();
  1154. }
  1155. return imapFolderFixedSubfolders;
  1156. }
  1157. public void setIncomingLettersSubfolderType(String incomingLetterSubfolderType) {
  1158. this.incomingLetterSubfolderType = incomingLetterSubfolderType;
  1159. }
  1160. public void setAttachmentsSubfolderType(String attachmentsSubfolderType) {
  1161. this.attachmentsSubfolderType = attachmentsSubfolderType;
  1162. }
  1163. public void setOutgoingLettersSubfolderType(String outgoingLettersSubfolderType) {
  1164. this.outgoingLettersSubfolderType = outgoingLettersSubfolderType;
  1165. }
  1166. public void setSendFailureNoticesSubfolderType(String sendFailureNoticesSubfolderType) {
  1167. this.sendFailureNoticesSubfolderType = sendFailureNoticesSubfolderType;
  1168. }
  1169. public void setImapService(ImapService imapService) {
  1170. this.imapService = imapService;
  1171. }
  1172. public void setDocumentLogService(DocumentLogService documentLogService) {
  1173. this.documentLogService = documentLogService;
  1174. }
  1175. public void setFileFolderService(FileFolderService fileFolderService) {
  1176. this.fileFolderService = fileFolderService;
  1177. }
  1178. public void setNodeService(NodeService nodeService) {
  1179. this.nodeService = nodeService;
  1180. }
  1181. public void setContentService(ContentService contentService) {
  1182. this.contentService = contentService;
  1183. }
  1184. public void setGeneralService(GeneralService generalService) {
  1185. this.generalService = generalService;
  1186. }
  1187. public void setFileService(FileService fileService) {
  1188. this.fileService = fileService;
  1189. }
  1190. public void setMimetypeService(MimetypeService mimetypeService) {
  1191. this.mimetypeService = mimetypeService;
  1192. }
  1193. public void setDocumentDynamicService(DocumentDynamicService documentDynamicService) {
  1194. this.documentDynamicService = documentDynamicService;
  1195. }
  1196. public void setEinvoiceService(EInvoiceService einvoiceService) {
  1197. this.einvoiceService = einvoiceService;
  1198. }
  1199. public void setUserService(UserService userService) {
  1200. this.userService = userService;
  1201. }
  1202. public void setMessageCopyFolder(String messageCopyFolder) {
  1203. this.messageCopyFolder = messageCopyFolder;
  1204. }
  1205. public void setSaveOriginalToRepo(boolean saveOriginalToRepo) {
  1206. this.saveOriginalToRepo = saveOriginalToRepo;
  1207. }
  1208. public void setApplicationConstantsBean(ApplicationConstantsBean applicationConstantsBean) {
  1209. this.applicationConstantsBean = applicationConstantsBean;
  1210. }
  1211. public void setBulkLoadNodeService(BulkLoadNodeService bulkLoadNodeService) {
  1212. this.bulkLoadNodeService = bulkLoadNodeService;
  1213. }
  1214. }