001: /*
002: Copyright (C) 2003 Know Gate S.L. All rights reserved.
003: C/Oņa, 107 1š2 28050 Madrid (Spain)
004:
005: Redistribution and use in source and binary forms, with or without
006: modification, are permitted provided that the following conditions
007: are met:
008:
009: 1. Redistributions of source code must retain the above copyright
010: notice, this list of conditions and the following disclaimer.
011:
012: 2. The end-user documentation included with the redistribution,
013: if any, must include the following acknowledgment:
014: "This product includes software parts from hipergate
015: (http://www.hipergate.org/)."
016: Alternately, this acknowledgment may appear in the software itself,
017: if and wherever such third-party acknowledgments normally appear.
018:
019: 3. The name hipergate must not be used to endorse or promote products
020: derived from this software without prior written permission.
021: Products derived from this software may not be called hipergate,
022: nor may hipergate appear in their name, without prior written
023: permission.
024:
025: This library is distributed in the hope that it will be useful,
026: but WITHOUT ANY WARRANTY; without even the implied warranty of
027: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
028:
029: You should have received a copy of hipergate License with this code;
030: if not, visit http://www.hipergate.org or mail to info@hipergate.org
031: */
032:
033: package com.knowgate.scheduler.jobs;
034:
035: import java.lang.ref.SoftReference;
036:
037: import java.util.HashMap;
038: import java.util.Iterator;
039:
040: import java.sql.SQLException;
041:
042: import java.io.IOException;
043: import java.io.FileNotFoundException;
044: import java.io.FileReader;
045: import java.io.File;
046: import java.io.StringBufferInputStream;
047:
048: import java.net.URL;
049: import java.net.MalformedURLException;
050:
051: import javax.activation.DataHandler;
052: import javax.activation.DataSource;
053: import javax.activation.FileDataSource;
054: import javax.activation.URLDataSource;
055:
056: import javax.mail.Session;
057: import javax.mail.Transport;
058: import javax.mail.MessagingException;
059: import javax.mail.NoSuchProviderException;
060: import javax.mail.BodyPart;
061:
062: import javax.mail.internet.AddressException;
063: import javax.mail.internet.InternetAddress;
064: import javax.mail.internet.MimeMessage;
065: import javax.mail.internet.MimeBodyPart;
066: import javax.mail.internet.MimeMultipart;
067:
068: import org.htmlparser.Parser;
069: import org.htmlparser.Node;
070: import org.htmlparser.util.NodeIterator;
071: import org.htmlparser.util.ParserException;
072: import org.htmlparser.tags.ImageTag;
073:
074: import org.apache.oro.text.regex.*;
075:
076: import com.knowgate.debug.DebugFile;
077: import com.knowgate.jdc.JDCConnection;
078: import com.knowgate.dataobjs.DB;
079: import com.knowgate.dataxslt.FastStreamReplacer;
080: import com.knowgate.dfs.FileSystem;
081:
082: import com.knowgate.scheduler.Atom;
083: import com.knowgate.scheduler.Job;
084:
085: /**
086: * <p>Add database fields to a document template and send it to a mail recipient</p>
087: * <p>Mails are send using Sun JavaMail</p>
088: * @author Sergio Montoro Ten
089: * @version 1.0
090: */
091:
092: public class EmailSender extends Job {
093:
094: // This flag is set if the first Job execution finds replacements of the form
095: // {#Section.Field} witch is data retrived from the database and inserted
096: // dynamically into the document final template.
097: // If the execution of this job for the first Atom find no tags of the form
098: // {#Section.Field} then the replacement subroutine can be skipped in next
099: // execution saving CPU cycles.
100: private boolean bHasReplacements;
101:
102: // This is a soft reference to a String holding the base document template
103: // if virtual memory runs low the garbage collector can discard the soft
104: // reference that would be reloaded from disk later upon the next atom processing
105: private SoftReference oFileStr;
106:
107: // A reference to the replacer class witch maps tags of the form {#Section.Field}
108: // to their corresponding database fields.
109: private FastStreamReplacer oReplacer;
110:
111: // javax.mail objects
112: Session oMailSession;
113: Transport oMailTransport;
114:
115: // Images repeated at HTML document are only attached once and referenced multiple times
116: // This hashmap keeps a record of the file names of images that have been already attached.
117: HashMap oDocumentImages;
118:
119: // Because the HTML may be loaded once and then passed throught FastStreamReplacer
120: // and be send multiple times, a Soft Reference to a String holding the HTML is kept.
121: private SoftReference oHTMLStr;
122:
123: // ---------------------------------------------------------------------------
124:
125: public EmailSender() {
126: bHasReplacements = true;
127: oFileStr = null;
128: oHTMLStr = null;
129: oReplacer = new FastStreamReplacer();
130: oDocumentImages = new HashMap();
131: oMailSession = null;
132: oMailTransport = null;
133: }
134:
135: // ---------------------------------------------------------------------------
136:
137: public void free() {
138: }
139:
140: // ---------------------------------------------------------------------------
141:
142: /**
143: * <p>Set Job Status</p>
144: * <p>If Status if set to Job.STATUS_FINISHED then dt_finished is set to current
145: * system date.</p>
146: * <p>If Status if set to any value other than Job.STATUS_RUNNING then the MailTransport is closed.
147: * @param oConn Database Connection
148: * @param iStatus Job Status
149: * @throws SQLException
150: */
151: public void setStatus(JDCConnection oConn, int iStatus)
152: throws SQLException {
153:
154: if (DebugFile.trace) {
155: DebugFile
156: .writeln("Begin EmailSender.setStatus([Connection], "
157: + String.valueOf(iStatus) + ")");
158: DebugFile.incIdent();
159: }
160:
161: super .setStatus(oConn, iStatus);
162:
163: if (Job.STATUS_RUNNING != iStatus) {
164:
165: if (oMailTransport != null) {
166: try {
167: if (oMailTransport.isConnected())
168: oMailTransport.close();
169: } catch (MessagingException msge) {
170: if (DebugFile.trace)
171: DebugFile
172: .writeln("Transport.close() MessagingException "
173: + msge.getMessage());
174: }
175:
176: oMailTransport = null;
177: } // fi (oMailTransport)
178:
179: if (null != oMailSession)
180: oMailSession = null;
181:
182: } // fi (STATUS_RUNNING)
183:
184: if (DebugFile.trace) {
185: DebugFile.decIdent();
186: DebugFile.writeln("End EMailSender.setStatus()");
187: }
188: } // setStatus
189:
190: // ---------------------------------------------------------------------------
191:
192: private String attachFiles(String sHTMLPath)
193: throws FileNotFoundException, IOException {
194: String sHtml = null;
195:
196: if (DebugFile.trace) {
197: DebugFile.writeln("Begin EmailSender.attachFiles("
198: + sHTMLPath + ")");
199: DebugFile.incIdent();
200: DebugFile.writeln("new File(" + sHTMLPath + ")");
201: }
202:
203: try {
204: FileSystem oFS = new FileSystem();
205: sHtml = oFS.readfilestr(sHTMLPath, null);
206: oFS = null;
207: } catch (com.enterprisedt.net.ftp.FTPException ftpe) {
208: }
209:
210: PatternMatcher oMatcher = new Perl5Matcher();
211: PatternCompiler oCompiler = new Perl5Compiler();
212:
213: Parser parser = Parser.createParser(sHtml, null);
214:
215: StringBuffer oRetVal = new StringBuffer(sHtml.length());
216:
217: try {
218: for (NodeIterator i = parser.elements(); i.hasMoreNodes();) {
219: Node node = i.nextNode();
220:
221: if (node instanceof ImageTag) {
222: ImageTag oImgNode = (ImageTag) node;
223: String sSrc = oImgNode.extractImageLocn();
224: String sTag = oImgNode.getText();
225:
226: Pattern oPattern;
227:
228: try {
229: oPattern = oCompiler.compile(sSrc);
230: } catch (MalformedPatternException neverthrown) {
231: oPattern = null;
232: }
233:
234: if (!oDocumentImages.containsKey(sSrc)) {
235: int iSlash = sSrc.lastIndexOf('/');
236: String sCid;
237:
238: if (iSlash >= 0) {
239: while (sSrc.charAt(iSlash) == '/') {
240: if (++iSlash == sSrc.length())
241: break;
242: }
243: sCid = sSrc.substring(iSlash);
244: } else
245: sCid = sSrc;
246:
247: oDocumentImages.put(sSrc, sCid);
248: }
249:
250: oRetVal.append(Util.substitute(oMatcher, oPattern,
251: new Perl5Substitution("cid:"
252: + oDocumentImages.get(sSrc),
253: Perl5Substitution.INTERPOLATE_ALL),
254: sTag, Util.SUBSTITUTE_ALL));
255:
256: } else {
257: oRetVal.append(node.getText());
258: }
259: }
260: } catch (ParserException pe) {
261: if (DebugFile.trace) {
262: DebugFile.writeln("ParserException " + pe.getMessage());
263: }
264:
265: oRetVal = new StringBuffer(sHtml.length());
266: oRetVal.append(sHtml);
267: }
268:
269: if (DebugFile.trace) {
270: DebugFile.decIdent();
271: DebugFile.writeln("End EmailSender.attachFiles()");
272: }
273:
274: return oRetVal.toString();
275: } // attachFiles
276:
277: // ---------------------------------------------------------------------------
278:
279: /**
280: * <p>Send PageSet document instance by e-mail.</p>
281: * <p>Transforming and sending aPageSet is a two stages task. First the PageSet
282: * stylesheet is combined via XSLT with user defined XML data and an XHTML
283: * document is pre-generated. This document still contains fixed database reference
284: * tags. At second stage the database reference tags are replaced for each document
285: * using FastStreamReplacer. Thus PageSet templates must have been previously
286: * transformed via XSLT before sending the PageSet instance by e-mail.</p>
287: * <p>This method uses javax.mail package for e-mail sending</p>
288: * <p>Parameters for locating e-mail server are stored at properties
289: * mail.transport.protocol, mail.host, mail.user from hipergate.cnf</p>
290: * <p>If parameter bo_attachimages is set to "1" then any <IMG SRC=""> tag
291: * will be replaced by a cid: reference to an attached file.</p>
292: * @param oAtm Atom containing reference to PageSet.<br>
293: * Atom must have the following parameters set:<br>
294: * <table border=1 cellpadding=4>
295: * <tr><td>gu_workarea</td><td>GUID of WorkArea owner of document to be sent</td></tr>
296: * <tr><td>gu_pageset</td><td>GUID of PageSet to be sent</td></tr>
297: * <tr><td>nm_pageset</td><td>Name of PageSet to be sent</td></tr>
298: * <tr><td>bo_attachimages</td><td>"1" if must attach images on document,<br>"0" if images must be absolute references</td></tr>
299: * <tr><td>tx_sender</td><td>Full Name of sender to be displayed</td></tr>
300: * <tr><td>tx_from</td><td>Sender e-mail address</td></tr>
301: * <tr><td>tx_subject</td><td>e-mail subject</td></tr>
302: * </table>
303: * @return String with document template after replacing database tags
304: * @throws FileNotFoundException
305: * @throws IOException
306: * @throws MessagingException
307: * @see com.knowgate.dataxslt.FastStreamReplacer
308: */
309: public Object process(Atom oAtm) throws FileNotFoundException,
310: IOException, MessagingException {
311:
312: File oFile; // Document Template File
313: FileReader oFileRead; // Document Template Reader
314: String sPathHTML; // Full Path to Document Template File
315: char cBuffer[]; // Internal Buffer for Document Template File Data
316: StringBufferInputStream oInStrm; // Document Template File Data after replacing images src http: with cid:
317: Object oReplaced; // Document Template File Data after FastStreamReplacer processing
318: final String Yes = "1";
319:
320: final String sSep = System.getProperty("file.separator"); // Alias for file.separator
321:
322: if (DebugFile.trace) {
323: DebugFile.writeln("Begin EMailSender.process([Job:"
324: + getStringNull(DB.gu_job, "") + ", Atom:"
325: + String.valueOf(oAtm.getInt(DB.pg_atom)) + "])");
326: DebugFile.incIdent();
327: }
328:
329: if (bHasReplacements) { // Initially the document is assumed to have tags to replace
330:
331: // *************************************************
332: // Compose the full path to document template file
333:
334: // First get the storage base path from hipergate.cnf
335: sPathHTML = getProperty("workareasput");
336: if (!sPathHTML.endsWith(sSep))
337: sPathHTML += sSep;
338:
339: // Concatenate PageSet workarea guid and subpath to Mailwire application directory
340: sPathHTML += getParameter("gu_workarea") + sSep + "apps"
341: + sSep + "Mailwire" + sSep + "html" + sSep
342: + getParameter("gu_pageset") + sSep;
343:
344: // Concatenate PageSet Name
345: sPathHTML += getParameter("nm_pageset").replace(' ', '_')
346: + ".html";
347:
348: if (DebugFile.trace)
349: DebugFile.writeln("PathHTML = " + sPathHTML);
350:
351: // ***********************************************************************
352: // Change <IMG SRC=""> tags for embeding document images into mime message
353:
354: if (Yes.equals(getParameter("bo_attachimages"))) {
355:
356: if (DebugFile.trace)
357: DebugFile.writeln("bo_attachimages=true");
358:
359: // Check first the SoftReference to the tag-replaced in-memory String cache
360: oInStrm = null;
361:
362: if (null != oHTMLStr) {
363: if (null != oHTMLStr.get())
364: // Get substituted html source as a StringBufferInputStream suitable
365: // for FastStreamReplacer replace() method.
366: oInStrm = new StringBufferInputStream(
367: (String) oHTMLStr.get());
368: }
369: if (null == oInStrm)
370: // If SoftReference was not found then
371: // call html processor for <IMG> tag substitution
372: oInStrm = new StringBufferInputStream(
373: attachFiles(sPathHTML));
374:
375: oHTMLStr = new SoftReference(oInStrm);
376:
377: // Call FastStreamReplacer for {#Section.Field} tags
378: oReplaced = oReplacer.replace(oInStrm, oAtm
379: .getItemMap());
380: }
381:
382: else { // do not attach images with message body
383:
384: if (DebugFile.trace)
385: DebugFile.writeln("bo_attachimages=false");
386:
387: // Call FastStreamReplacer for {#Section.Field} tags
388: oReplaced = oReplacer.replace(sPathHTML, oAtm
389: .getItemMap());
390: }
391:
392: // Count number of replacements done and update bHasReplacements flag accordingly
393: bHasReplacements = (oReplacer.lastReplacements() > 0);
394: }
395:
396: else {
397:
398: oReplaced = null;
399:
400: if (null != oFileStr)
401: oReplaced = oFileStr.get();
402:
403: if (null == oReplaced) {
404:
405: // If document template has no database replacement tags
406: // then just cache the document template into a SoftReference String
407:
408: // Compose the full path to document template file
409: sPathHTML = getProperty("workareasput");
410: if (!sPathHTML.endsWith(sSep))
411: sPathHTML += sSep;
412:
413: sPathHTML += getParameter("gu_workarea") + sSep
414: + "apps" + sSep + "Mailwire" + sSep + "html"
415: + sSep + getParameter("gu_pageset") + sSep
416: + getParameter("nm_pageset").replace(' ', '_')
417: + ".html";
418:
419: if (DebugFile.trace)
420: DebugFile.writeln("PathHTML = " + sPathHTML);
421:
422: // ***************************
423: // Read document template file
424:
425: if (DebugFile.trace)
426: DebugFile.writeln("new File(" + sPathHTML + ")");
427:
428: oFile = new File(sPathHTML);
429:
430: cBuffer = new char[new Long(oFile.length()).intValue()];
431:
432: oFileRead = new FileReader(oFile);
433: oFileRead.read(cBuffer);
434: oFileRead.close();
435:
436: if (DebugFile.trace)
437: DebugFile.writeln(String.valueOf(cBuffer.length)
438: + " characters readed");
439:
440: if (Yes.equals(getParameter("bo_attachimages")))
441: oReplaced = attachFiles(new String(cBuffer));
442: else
443: oReplaced = new String(cBuffer);
444:
445: // *********************************************************
446: // Assign SoftReference to File cached in-memory as a String
447:
448: oFileStr = new SoftReference(oReplaced);
449:
450: } // fi (oReplaced)
451:
452: } // fi (bHasReplacements)
453:
454: // ***********************************************
455: // Send replaced file data by e-mail
456:
457: if (null == oMailSession) {
458: if (DebugFile.trace)
459: DebugFile
460: .writeln("Session.getInstance(Job.getProperties(), null)");
461:
462: java.util.Properties oMailProps = getProperties();
463:
464: if (oMailProps.getProperty("mail.transport.protocol") == null)
465: oMailProps.put("mail.transport.protocol", "smtp");
466:
467: if (oMailProps.getProperty("mail.host") == null)
468: oMailProps.put("mail.host", "localhost");
469:
470: oMailSession = Session.getInstance(getProperties(), null);
471:
472: if (null != oMailSession) {
473: oMailTransport = oMailSession.getTransport();
474:
475: try {
476: oMailTransport.connect();
477: } catch (NoSuchProviderException nspe) {
478: if (DebugFile.trace)
479: DebugFile
480: .writeln("MailTransport.connect() NoSuchProviderException "
481: + nspe.getMessage());
482: throw new MessagingException(nspe.getMessage(),
483: nspe);
484: }
485: } // fi (Session.getInstance())
486: } // fi (oMailSession)
487:
488: MimeMessage oMsg;
489: InternetAddress oFrom, oTo;
490:
491: try {
492: if (null == getParameter("tx_sender"))
493: oFrom = new InternetAddress(getParameter("tx_from"));
494: else
495: oFrom = new InternetAddress(getParameter("tx_from"),
496: getParameter("tx_sender"));
497:
498: if (DebugFile.trace)
499: DebugFile.writeln("to: "
500: + oAtm.getStringNull(DB.tx_email, "ERROR Atom["
501: + String.valueOf(oAtm
502: .getInt(DB.pg_atom))
503: + "].tx_email is null!"));
504:
505: oTo = new InternetAddress(oAtm.getString(DB.tx_email), oAtm
506: .getStringNull(DB.tx_name, "")
507: + " " + oAtm.getStringNull(DB.tx_surname, ""));
508: } catch (AddressException adre) {
509: if (DebugFile.trace)
510: DebugFile.writeln("AddressException "
511: + adre.getMessage() + " job "
512: + getString(DB.gu_job) + " atom "
513: + String.valueOf(oAtm.getInt(DB.pg_atom)));
514:
515: oFrom = null;
516: oTo = null;
517:
518: throw new MessagingException("AddressException "
519: + adre.getMessage() + " job "
520: + getString(DB.gu_job) + " atom "
521: + String.valueOf(oAtm.getInt(DB.pg_atom)));
522: }
523:
524: if (DebugFile.trace)
525: DebugFile.writeln("new MimeMessage([Session])");
526:
527: oMsg = new MimeMessage(oMailSession);
528:
529: oMsg.setSubject(getParameter("tx_subject"));
530:
531: oMsg.setFrom(oFrom);
532:
533: if (DebugFile.trace)
534: DebugFile
535: .writeln("MimeMessage.addRecipient(MimeMessage.RecipientType.TO, "
536: + oTo.getAddress());
537:
538: oMsg.addRecipient(MimeMessage.RecipientType.TO, oTo);
539:
540: String sSrc = null, sCid = null;
541:
542: try {
543:
544: // Images may be attached into message or be absolute http source references
545: if (Yes.equals(getParameter("bo_attachimages"))) {
546:
547: BodyPart oMsgBodyPart = new MimeBodyPart();
548: oMsgBodyPart.setContent(oReplaced, "text/html");
549:
550: // Create a related multi-part to combine the parts
551: MimeMultipart oMultiPart = new MimeMultipart("related");
552: oMultiPart.addBodyPart(oMsgBodyPart);
553:
554: Iterator oImgs = oDocumentImages.keySet().iterator();
555:
556: while (oImgs.hasNext()) {
557: BodyPart oImgBodyPart = new MimeBodyPart();
558:
559: sSrc = (String) oImgs.next();
560: sCid = (String) oDocumentImages.get(sSrc);
561:
562: if (sSrc.startsWith("www."))
563: sSrc = "http://" + sSrc;
564:
565: if (sSrc.startsWith("http://")
566: || sSrc.startsWith("https://")) {
567: oImgBodyPart.setDataHandler(new DataHandler(
568: new URL(sSrc)));
569: } else {
570: oImgBodyPart.setDataHandler(new DataHandler(
571: new FileDataSource(sSrc)));
572: }
573:
574: oImgBodyPart.setHeader("Content-ID", sCid);
575:
576: // Add part to multi-part
577: oMultiPart.addBodyPart(oImgBodyPart);
578: } // wend
579:
580: if (DebugFile.trace)
581: DebugFile
582: .writeln("MimeMessage.setContent([MultiPart])");
583:
584: oMsg.setContent(oMultiPart);
585: }
586:
587: else {
588:
589: if (DebugFile.trace)
590: DebugFile
591: .writeln("MimeMessage.setContent([String], \"text/html\")");
592:
593: oMsg.setContent(oReplaced, "text/html");
594:
595: }
596:
597: oMsg.saveChanges();
598:
599: if (DebugFile.trace)
600: DebugFile
601: .writeln("Transport.sendMessage([MimeMessage], MimeMessage.getAllRecipients())");
602:
603: oMailTransport.sendMessage(oMsg, oMsg.getAllRecipients());
604:
605: // ************************************************************
606: // Decrement de count of atoms peding of processing at this job
607: iPendingAtoms--;
608: } catch (MalformedURLException urle) {
609:
610: if (DebugFile.trace)
611: DebugFile.writeln("MalformedURLException " + sSrc);
612: throw new MessagingException("MalformedURLException "
613: + sSrc);
614: }
615:
616: if (DebugFile.trace) {
617: DebugFile.writeln("End EMailSender.process([Job:"
618: + getStringNull(DB.gu_job, "") + ", Atom:"
619: + String.valueOf(oAtm.getInt(DB.pg_atom)) + "])");
620: DebugFile.decIdent();
621: }
622:
623: return oReplaced;
624:
625: } //process
626:
627: } // EmailSender
|