001: package org.contineo.core.communication;
002:
003: import java.io.File;
004: import java.io.FileInputStream;
005: import java.io.FileOutputStream;
006: import java.io.InputStream;
007: import java.util.Collection;
008: import java.util.Properties;
009:
010: import javax.mail.Address;
011: import javax.mail.Flags;
012: import javax.mail.Folder;
013: import javax.mail.Multipart;
014: import javax.mail.Part;
015: import javax.mail.Session;
016: import javax.mail.Store;
017: import javax.mail.internet.AddressException;
018: import javax.mail.internet.InternetAddress;
019:
020: import org.apache.commons.io.FileUtils;
021: import org.apache.commons.io.FilenameUtils;
022: import org.apache.commons.lang.StringUtils;
023: import org.apache.commons.logging.Log;
024: import org.apache.commons.logging.LogFactory;
025: import org.contineo.core.communication.dao.EMailAccountDAO;
026: import org.contineo.core.communication.dao.EMailDAO;
027: import org.contineo.core.document.Document;
028: import org.contineo.core.document.History;
029: import org.contineo.core.document.Version;
030: import org.contineo.core.document.dao.DocumentDAO;
031: import org.contineo.core.document.dao.HistoryDAO;
032: import org.contineo.core.doxter.Storer;
033: import org.contineo.core.i18n.DateBean;
034: import org.contineo.core.searchengine.SearchDocument;
035: import org.contineo.core.searchengine.crawler.Indexer;
036: import org.contineo.core.searchengine.dao.SearchDocumentDAO;
037: import org.contineo.core.security.Menu;
038: import org.contineo.core.security.dao.MenuDAO;
039: import org.contineo.core.text.parser.Parser;
040: import org.contineo.core.text.parser.ParserFactory;
041: import org.contineo.core.util.IconSelector;
042: import org.contineo.util.config.SettingsConfig;
043:
044: /**
045: * This component downloads new emails from one or more e-mail accounts
046: *
047: * @author Michael Scholz, Marco Meschieri
048: */
049: public class EMailReceiver {
050: protected static Log log = LogFactory.getLog(EMailReceiver.class);
051:
052: // The default username that owns downloaded documents
053: private String defaultOwner = "admin";
054:
055: private EMailAccountDAO accountDao;
056:
057: private EMailDAO emailDao;
058:
059: private MenuDAO menuDao;
060:
061: private SettingsConfig settingsConfig;
062:
063: private Storer storer;
064:
065: private DocumentDAO documentDao;
066:
067: private HistoryDAO historyDao;
068:
069: private SearchDocumentDAO searchDocDao;
070:
071: private Indexer indexer;
072:
073: private EMailReceiver() {
074: }
075:
076: public Indexer getIndexer() {
077: return indexer;
078: }
079:
080: public void setIndexer(Indexer indexer) {
081: this .indexer = indexer;
082: }
083:
084: public SearchDocumentDAO getSearchDocDao() {
085: return searchDocDao;
086: }
087:
088: public void setSearchDocDao(SearchDocumentDAO searchDocDao) {
089: this .searchDocDao = searchDocDao;
090: }
091:
092: public DocumentDAO getDocumentDao() {
093: return documentDao;
094: }
095:
096: public void setDocumentDao(DocumentDAO documentDao) {
097: this .documentDao = documentDao;
098: }
099:
100: public HistoryDAO getHistoryDao() {
101: return historyDao;
102: }
103:
104: public void setHistoryDao(HistoryDAO historyDAO) {
105: this .historyDao = historyDAO;
106: }
107:
108: public MenuDAO getMenuDao() {
109: return menuDao;
110: }
111:
112: public void setMenuDao(MenuDAO menuDao) {
113: this .menuDao = menuDao;
114: }
115:
116: public Storer getStorer() {
117: return storer;
118: }
119:
120: public void setStorer(Storer storer) {
121: this .storer = storer;
122: }
123:
124: public EMailDAO getEmailDao() {
125: return emailDao;
126: }
127:
128: public void setEmailDao(EMailDAO emailDao) {
129: this .emailDao = emailDao;
130: }
131:
132: public EMailAccountDAO getAccountDao() {
133: return accountDao;
134: }
135:
136: public void setAccountDao(EMailAccountDAO accountDao) {
137: this .accountDao = accountDao;
138: }
139:
140: public String getDefaultOwner() {
141: return defaultOwner;
142: }
143:
144: public void setDefaultOwner(String defaultOwner) {
145: this .defaultOwner = defaultOwner;
146: }
147:
148: public SettingsConfig getSettingsConfig() {
149: return settingsConfig;
150: }
151:
152: public void setSettingsConfig(SettingsConfig settingsConfig) {
153: this .settingsConfig = settingsConfig;
154: }
155:
156: /**
157: * Downloads all new mails from all accounts. The stored document will be
158: * owned by the specified default owner
159: *
160: * @throws Exception
161: */
162: public synchronized void receiveMails() {
163: receiveMails(defaultOwner);
164: }
165:
166: /**
167: * Downloads all new mails from all accounts. The stored document will be
168: * owned by the specified username
169: *
170: * @throws Exception
171: */
172: public synchronized void receiveMails(String username) {
173: log.info("Receive all mails");
174:
175: Collection<EMailAccount> accounts = accountDao.findAll();
176:
177: for (EMailAccount account : accounts) {
178: if (account.getEnabled() == 0) {
179: log.warn("Skip account " + account.getMailAddress()
180: + " beacuse disabled");
181: }
182: try {
183: receive(account, defaultOwner);
184: } catch (Exception e) {
185: log.error(e.getMessage(), e);
186: }
187: }
188: }
189:
190: public synchronized void receive(EMailAccount account,
191: String username) throws Exception {
192: // Connect to POP3-Server
193: Session sess = Session.getDefaultInstance(new Properties());
194: Store store = sess.getStore(account.getProvider());
195: store.connect(account.getHost(), account.getAccountUser(),
196: account.getAccountPassword());
197:
198: // Open Folder INBOX
199: Folder inbox = store.getFolder("INBOX");
200: if (inbox != null) {
201: inbox.open(Folder.READ_WRITE);
202:
203: // fetch messages from server
204: javax.mail.Message[] messages = inbox.getMessages();
205:
206: for (int i = 0; i < messages.length; i++) {
207: EMail email = new EMail();
208:
209: javax.mail.Message message = messages[i];
210: message.setFlag(Flags.Flag.DELETED, account
211: .getDeleteFromMailbox() > 0);
212:
213: if (message.getHeader("Message-ID") == null)
214: continue;
215:
216: String mailId = message.getHeader("Message-ID")[0];
217:
218: Collection<String> alreadyRetrievedIds = this .emailDao
219: .collectEmailIds(account.getAccountId());
220: if (alreadyRetrievedIds.contains(mailId)) {
221: if (log.isDebugEnabled())
222: log.debug("Skip message " + mailId
223: + " because already fetched from "
224: + account.toString());
225: continue;
226: }
227:
228: InternetAddress from = ((InternetAddress) message
229: .getFrom()[0]);
230: Address[] recipients = new Address[] {};
231: try {
232: recipients = message.getAllRecipients();
233: } catch (AddressException e) {
234: log.error(e);
235: }
236:
237: // store message in database
238: if (from != null) {
239: email.setAuthor(from.getPersonal());
240: email.setAuthorAddress(from.getAddress());
241: }
242:
243: for (int j = 0; j < recipients.length; j++) {
244: Address rec = recipients[j];
245: Recipient recipient = new Recipient();
246: recipient.setAddress(rec.toString());
247: email.addRecipient(recipient);
248: }
249:
250: email.setSubject(message.getSubject());
251: email.setRead(0);
252: email.setUserName(username);
253: email.setFolder("inbox");
254: email.setSentDate(String.valueOf(message.getSentDate()
255: .getTime()));
256: email.setEmailId(mailId);
257: email.setAccountId(account.getAccountId());
258: getEmailDao().store(email);
259:
260: if (log.isDebugEnabled())
261: log.debug("Store email " + email.getSubject());
262: dumpPart(message, 0, account, email, null);
263: }
264:
265: inbox.close(true);
266: }
267:
268: store.close();
269: }
270:
271: private Menu dumpPart(Part p, int partCount, EMailAccount account,
272: EMail email, Menu parent) throws Exception {
273: String mailsdir = settingsConfig.getValue("userdir")
274: + "/mails/";
275: File mailDir = new File(FilenameUtils.normalize(mailsdir + "/"
276: + email.getMessageId()));
277: FileUtils.forceMkdir(mailDir);
278:
279: if (p.isMimeType("multipart/*")) {
280: Multipart mp = (Multipart) p.getContent();
281: int count = mp.getCount();
282:
283: int partId = 0;
284: boolean textBodyFound = false;
285:
286: Menu mailMenu = null;
287:
288: // Search for text mail body
289: for (int i = 0; i < count; i++) {
290: Part part = mp.getBodyPart(i);
291: if (StringUtils.isEmpty(part.getFileName())
292: && part.getContentType().startsWith(
293: "text/plain")) {
294: mailMenu = dumpPart(mp.getBodyPart(i), partId++,
295: account, email, null);
296: textBodyFound = true;
297: }
298: }
299:
300: // Search for html mail body
301: for (int i = 0; i < count && !textBodyFound; i++) {
302: Part part = mp.getBodyPart(i);
303: if (StringUtils.isEmpty(part.getFileName())
304: && part.getContentType()
305: .startsWith("text/html")
306: && !textBodyFound) {
307: // This is an HTML-only mail
308: mailMenu = dumpPart(mp.getBodyPart(i), partId++,
309: account, email, null);
310: }
311: }
312:
313: // Dump other parts skipping not-allowed extensions
314: for (int i = 0; i < count; i++) {
315: Part part = mp.getBodyPart(i);
316: if (!StringUtils.isEmpty(part.getFileName())
317: && account.isAllowed(FilenameUtils
318: .getExtension(part.getFileName()))) {
319: dumpPart(mp.getBodyPart(i), partId++, account,
320: email, mailMenu);
321: }
322: }
323: } else {
324: Attachment attachment = new Attachment();
325: String cType = p.getContentType();
326: String filename = p.getFileName();
327: String docName = filename;
328:
329: // Check if this is the email body or an attachment
330: if (StringUtils.isEmpty(filename)) {
331: filename = "email";
332:
333: if (cType.startsWith("text/plain")) {
334: filename += ".mail";
335: }
336:
337: if (cType.startsWith("text/html")) {
338: filename += ".html";
339: }
340: docName = StringUtils.abbreviate(email.getSubject(),
341: 100);
342: }
343:
344: int end = cType.indexOf(";");
345: String mimeType = "";
346:
347: if (end != -1) {
348: mimeType = cType.substring(0, cType.indexOf(";"));
349: } else {
350: mimeType = cType;
351: }
352:
353: InputStream is = p.getInputStream();
354: File file = new File(mailDir, filename);
355: FileOutputStream fos = new FileOutputStream(file);
356: int letter = 0;
357:
358: while ((letter = is.read()) != -1) {
359: fos.write(letter);
360: }
361:
362: is.close();
363: fos.close();
364:
365: String icon = "";
366: if (mimeType.equals("text/plain")
367: || mimeType.equals("text/rtf")
368: || mimeType.equals("application/msword")
369: || mimeType
370: .equals("application/vnd.sun.xml.writer")) {
371: icon = "textdoc.gif";
372: } else if (mimeType.equals("application/msexcel")
373: || mimeType.equals("application/vnd.sun.xml.calc")) {
374: icon = "tabledoc.gif";
375: } else if (mimeType.equals("application/mspowerpoint")
376: || mimeType
377: .equals("application/vnd.sun.xml.impress")) {
378: icon = "presentdoc.gif";
379: } else if (mimeType.equals("application/pdf")) {
380: icon = "pdf.gif";
381: } else if (mimeType.equals("text/html")) {
382: icon = "internet.gif";
383: } else {
384: icon += "document.gif";
385: }
386:
387: attachment.setIcon(icon);
388: attachment.setMimeType(mimeType);
389: attachment.setFilename(filename);
390: email.addAttachment(partCount, attachment);
391:
392: Menu parentMenu = parent;
393: if (parentMenu == null)
394: parentMenu = account.getTargetFolder();
395:
396: return storeDocument(account, parentMenu, file, docName);
397: }
398: return null;
399: }
400:
401: /**
402: * Stores a document file in the archive
403: *
404: * @param account
405: * @param file File to be stored
406: * @param folder The folder in which the document must be created, if null
407: * account target folder is used
408: * @param docName Name of the document to be created
409: * @return The newly created menu
410: * @throws Exception
411: */
412: private Menu storeDocument(EMailAccount account, Menu folder,
413: File file, String docName) throws Exception {
414: log.info("Store email document " + file);
415:
416: // Gets file name
417: String filename = file.getName();
418: String ext = filename.substring(filename.lastIndexOf(".") + 1);
419:
420: Menu parent = account.getTargetFolder();
421: if (folder != null)
422: parent = folder;
423:
424: // Makes menuPath
425: String menupath = new StringBuilder(parent.getMenuPath())
426: .append(File.separator).append(parent.getMenuId())
427: .toString();
428: int menuhier = parent.getMenuHier();
429:
430: // Makes new Menu
431: Menu menu = new Menu();
432: menu.setMenuParent(parent.getMenuId());
433: menu.setMenuPath(menupath);
434: menu.setMenuHier(menuhier++);
435: menu.setMenuText(docName);
436: menu.setMenuType(Menu.MENUTYPE_FILE);
437: menu.setMenuRef(filename);
438:
439: String icon = IconSelector.selectIcon(ext);
440: menu.setMenuIcon(icon.toString());
441:
442: // Set permissions from parent folder
443: menu.setMenuGroup(parent.getMenuGroupNames());
444:
445: // and stores it
446: menuDao.store(menu);
447:
448: // Stores the document in the repository
449: String path = new StringBuilder(settingsConfig
450: .getValue("docdir")).append(menupath).append(
451: File.separator)
452: .append(String.valueOf(menu.getMenuId())).append(
453: File.separator).toString();
454: String mpath = new StringBuilder(menupath).append(
455: File.separator)
456: .append(String.valueOf(menu.getMenuId())).toString();
457:
458: // Get file to upload inputStream
459: InputStream stream = null;
460: stream = new FileInputStream(file);
461:
462: // stores it in folder
463: try {
464: storer.store(stream, mpath, filename, "1.0");
465: } finally {
466: if (stream != null) {
467: stream.close();
468: }
469: }
470:
471: File f = new File(new StringBuilder(path).append(filename)
472: .toString());
473:
474: // Parses the file where it is already stored
475: Parser parser = ParserFactory.getParser(f);
476: StringBuffer content = null;
477: String name = docName;
478: String author = "";
479: String sourceDate = "";
480: String keywords = "";
481:
482: // and gets some fields
483: if (parser != null) {
484: content = parser.getContent();
485: if (docName == null)
486: name = parser.getTitle();
487: author = parser.getAuthor();
488: sourceDate = parser.getSourceDate();
489: keywords = parser.getKeywords();
490: }
491:
492: if (content == null) {
493: content = new StringBuffer("");
494: }
495:
496: String language = account.getLanguage();
497:
498: Document doc = new Document();
499: doc.setMenu(menu);
500: doc.setDocDate(DateBean.toCompactString());
501: doc.setDocPublisher("mailer");
502: doc.setDocStatus(Document.DOC_CHECKED_IN);
503: doc.setDocType(filename
504: .substring(filename.lastIndexOf(".") + 1));
505: doc.setDocVersion("1.0");
506: doc.setSource(account.getMailAddress());
507: doc.setSourceAuthor(author);
508: doc.setKeywords(documentDao.toKeywords(keywords));
509: doc.setSourceType("mail");
510: doc.setCoverage("");
511: doc.setLanguage(language);
512: doc.setDocName(name);
513: doc.setSourceDate(sourceDate);
514:
515: /* insert initial version 1.0 */
516: Version vers = new Version();
517: vers.setVersion("1.0");
518: vers.setVersionComment("");
519: vers.setVersionDate(DateBean.toCompactString());
520:
521: vers.setVersionUser(account.getUserName());
522: doc.addVersion(vers);
523:
524: documentDao.store(doc);
525:
526: /* create history entry */
527: History history = new History();
528: history.setDocId(doc.getDocId());
529: history.setDate(DateBean.toCompactString());
530: history.setUsername(account.getUserName());
531: history.setEvent(History.STORED);
532:
533: historyDao.store(history);
534:
535: menupath = menu.getMenuPath();
536:
537: /* create search indexer entry */
538: int luceneId = indexer.addFile(new File(new StringBuilder(path)
539: .append(File.separator).append(filename).toString()),
540: doc, content, language);
541: SearchDocument searchDoc = new SearchDocument();
542: searchDoc.setLuceneId(luceneId);
543: searchDoc.setMenuId(menu.getMenuId());
544:
545: if (language.equals("de")) {
546: searchDoc.setIndex("german");
547: } else if (language.equals("fr")) {
548: searchDoc.setIndex("french");
549: } else if (language.equals("es")) {
550: searchDoc.setIndex("spanish");
551: } else if (language.equals("it")) {
552: searchDoc.setIndex("italian");
553: } else {
554: searchDoc.setIndex("english");
555: }
556:
557: searchDocDao.store(searchDoc);
558:
559: // Update the file size
560: menuDao.store(menu);
561:
562: return menu;
563: }
564: }
|