001: /*
002: * Copyright 2002-2005 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package info.jtrac.mail;
018:
019: import info.jtrac.domain.Item;
020: import info.jtrac.domain.ItemUser;
021: import info.jtrac.domain.User;
022: import info.jtrac.util.ItemUtils;
023: import java.util.Date;
024: import java.util.Enumeration;
025: import java.util.Locale;
026: import java.util.Map;
027: import java.util.Properties;
028: import javax.mail.Header;
029: import javax.mail.Session;
030: import javax.mail.internet.MimeMessage;
031: import org.slf4j.Logger;
032: import org.slf4j.LoggerFactory;
033: import org.springframework.context.MessageSource;
034: import org.springframework.jndi.JndiObjectFactoryBean;
035: import org.springframework.mail.javamail.JavaMailSenderImpl;
036: import org.springframework.mail.javamail.MimeMessageHelper;
037: import org.springframework.util.StringUtils;
038:
039: /**
040: * Class to handle sending of E-mail and pre-formatted messages
041: */
042: public class MailSender {
043:
044: private final Logger logger = LoggerFactory.getLogger(getClass());
045:
046: private JavaMailSenderImpl sender;
047: private String prefix;
048: private String from;
049: private String url;
050: private MessageSource messageSource;
051: private Locale defaultLocale;
052:
053: public MailSender(Map<String, String> config,
054: MessageSource messageSource, String defaultLocale) {
055: // initialize email sender
056: this .messageSource = messageSource;
057: this .defaultLocale = StringUtils
058: .parseLocaleString(defaultLocale);
059: String mailSessionJndiName = config
060: .get("mail.session.jndiname");
061: if (StringUtils.hasText(mailSessionJndiName)) {
062: initMailSenderFromJndi(mailSessionJndiName);
063: }
064: if (sender == null) {
065: initMailSenderFromConfig(config);
066: }
067: // if sender is still null the send* methods will not
068: // do anything when called and will just return immediately
069: }
070:
071: /**
072: * we bend the rules a little and fire off a new thread for sending
073: * an email message. This has the advantage of not slowing down the item
074: * create and update screens, i.e. the system returns the next screen
075: * after "submit" without blocking. This has been used in production
076: * (and now I guess in many JTrac installations worldwide)
077: * for quite a while now, on Tomcat without any problems. This helps a lot
078: * especially when the SMTP server is slow to respond, etc.
079: */
080: private void sendInNewThread(final MimeMessage message) {
081: new Thread() {
082: @Override
083: public void run() {
084: logger.debug("send mail thread start");
085: try {
086: try {
087: sender.send(message);
088: logger.debug("send mail thread successfull");
089: } catch (Exception e) {
090: logger.error("send mail thread failed", e);
091: logger.error("mail headers dump start");
092: Enumeration headers = message.getAllHeaders();
093: while (headers.hasMoreElements()) {
094: Header h = (Header) headers.nextElement();
095: logger.info(h.getName() + ": "
096: + h.getValue());
097: }
098: logger.error("mail headers dump end");
099: }
100: } catch (Exception e) {
101: throw new RuntimeException(e);
102: }
103: }
104: }.start();
105: }
106:
107: private String fmt(String key, Locale locale) {
108: try {
109: return messageSource.getMessage("mail_sender." + key, null,
110: locale);
111: } catch (Exception e) {
112: logger.debug(e.getMessage());
113: return "???mail_sender." + key + "???";
114: }
115: }
116:
117: private String addHeaderAndFooter(StringBuffer html) {
118: StringBuffer sb = new StringBuffer();
119: // additional cosmetic tweaking of e-mail layout
120: // style just after the body tag does not work for a minority of clients like gmail, thunderbird etc.
121: // ItemUtils adds the main inline CSS when generating the email content, so we gracefully degrade
122: sb
123: .append("<html><body><style type='text/css'>table.jtrac th, table.jtrac td { padding-left: 0.2em; padding-right: 0.2em; }</style>");
124: sb.append(html);
125: sb.append("</html>");
126: return sb.toString();
127: }
128:
129: private String getItemViewAnchor(Item item, Locale locale) {
130: String itemUrl = url + "app/item/" + item.getRefId();
131: return "<p style='font-family: Arial; font-size: 75%'><a href='"
132: + itemUrl + "'>" + itemUrl + "</a></p>";
133: }
134:
135: private String getSubject(Item item) {
136: String summary = null;
137: if (item.getSummary() == null) {
138: summary = "";
139: } else if (item.getSummary().length() > 80) {
140: summary = item.getSummary().substring(0, 80);
141: } else {
142: summary = item.getSummary();
143: }
144: return prefix + " #" + item.getRefId() + " " + summary;
145: }
146:
147: public void send(Item item) {
148: if (sender == null) {
149: logger
150: .debug("mail sender is null, not sending notifications");
151: return;
152: }
153: // TODO make this locale sensitive per recipient
154: logger.debug("attempting to send mail for item update");
155: // prepare message content
156: StringBuffer sb = new StringBuffer();
157: String anchor = getItemViewAnchor(item, defaultLocale);
158: sb.append(anchor);
159: sb.append(ItemUtils.getAsHtml(item, messageSource,
160: defaultLocale));
161: sb.append(anchor);
162: if (logger.isDebugEnabled()) {
163: logger.debug("html content: " + sb);
164: }
165: // prepare message
166: MimeMessage message = sender.createMimeMessage();
167: MimeMessageHelper helper = new MimeMessageHelper(message,
168: "UTF-8");
169: try {
170: helper.setText(addHeaderAndFooter(sb), true);
171: helper.setSubject(getSubject(item));
172: helper.setSentDate(new Date());
173: helper.setFrom(from);
174: // set TO
175: if (item.getAssignedTo() != null) {
176: helper.setTo(item.getAssignedTo().getEmail());
177: } else {
178: helper.setTo(item.getLoggedBy().getEmail());
179: }
180: // set CC
181: if (item.getItemUsers() != null) {
182: String[] cc = new String[item.getItemUsers().size()];
183: int i = 0;
184: for (ItemUser itemUser : item.getItemUsers()) {
185: cc[i++] = itemUser.getUser().getEmail();
186: }
187: helper.setCc(cc);
188: }
189: // send message
190: sendInNewThread(message);
191: } catch (Exception e) {
192: logger.error("failed to prepare e-mail", e);
193: }
194: }
195:
196: public void sendUserPassword(User user, String clearText) {
197: if (sender == null) {
198: logger
199: .debug("mail sender is null, not sending new user / password change notification");
200: return;
201: }
202: logger.debug("attempting to send mail for user password");
203: String localeString = user.getLocale();
204: Locale locale = null;
205: if (localeString == null) {
206: locale = defaultLocale;
207: } else {
208: locale = StringUtils.parseLocaleString(localeString);
209: }
210: MimeMessage message = sender.createMimeMessage();
211: MimeMessageHelper helper = new MimeMessageHelper(message,
212: "UTF-8");
213: try {
214: helper.setTo(user.getEmail());
215: helper.setSubject(prefix + " "
216: + fmt("loginMailSubject", locale));
217: StringBuffer sb = new StringBuffer();
218: sb.append("<p>" + fmt("loginMailGreeting", locale) + " "
219: + user.getName() + ",</p>");
220: sb.append("<p>" + fmt("loginMailLine1", locale) + "</p>");
221: sb.append("<table class='jtrac'>");
222: sb.append("<tr><th style='background: #CCCCCC'>"
223: + fmt("loginName", locale)
224: + "</th><td style='border: 1px solid black'>"
225: + user.getLoginName() + "</td></tr>");
226: sb.append("<tr><th style='background: #CCCCCC'>"
227: + fmt("password", locale)
228: + "</th><td style='border: 1px solid black'>"
229: + clearText + "</td></tr>");
230: sb.append("</table>");
231: sb.append("<p>" + fmt("loginMailLine2", locale) + "</p>");
232: sb.append("<p><a href='" + url + "'>" + url + "</a></p>");
233: helper.setText(addHeaderAndFooter(sb), true);
234: helper.setSentDate(new Date());
235: // helper.setCc(from);
236: helper.setFrom(from);
237: sendInNewThread(message);
238: } catch (Exception e) {
239: logger.error("failed to prepare e-mail", e);
240: }
241: }
242:
243: private void initMailSenderFromJndi(String mailSessionJndiName) {
244: logger
245: .info("attempting to initialize mail sender from jndi name = '"
246: + mailSessionJndiName + "'");
247: JndiObjectFactoryBean factoryBean = new JndiObjectFactoryBean();
248: factoryBean.setJndiName(mailSessionJndiName);
249: // "java:comp/env/" will be prefixed if the JNDI name doesn't already have it
250: factoryBean.setResourceRef(true);
251: try {
252: // this step actually does the JNDI lookup
253: factoryBean.afterPropertiesSet();
254: } catch (Exception e) {
255: logger.warn("failed to locate mail session : " + e);
256: return;
257: }
258: Session session = (Session) factoryBean.getObject();
259: sender = new JavaMailSenderImpl();
260: sender.setSession(session);
261: logger.info("email sender initialized from jndi name = '"
262: + mailSessionJndiName + "'");
263: }
264:
265: private void initMailSenderFromConfig(Map<String, String> config) {
266: String host = config.get("mail.server.host");
267: if (host == null) {
268: logger
269: .warn("'mail.server.host' config is null, mail sender not initialized");
270: return;
271: }
272: String port = config.get("mail.server.port");
273: String tempUrl = config.get("jtrac.url.base");
274: from = config.get("mail.from");
275: prefix = config.get("mail.subject.prefix");
276: String userName = config.get("mail.server.username");
277: String password = config.get("mail.server.password");
278: String startTls = config.get("mail.server.starttls.enable");
279: logger
280: .info("initializing email adapter: host = '" + host
281: + "', port = '" + port + "', url = '" + tempUrl
282: + "', from = '" + from + "', prefix = '"
283: + prefix + "'");
284: this .prefix = prefix == null ? "[jtrac]" : prefix;
285: this .from = from == null ? "jtrac" : from;
286: this .url = tempUrl == null ? "http://localhost/jtrac/"
287: : tempUrl;
288: if (!this .url.endsWith("/")) {
289: this .url = url + "/";
290: }
291: int p = 25;
292: if (port != null) {
293: try {
294: p = Integer.parseInt(port);
295: } catch (NumberFormatException e) {
296: logger.warn("mail.server.port not an integer : '"
297: + port + "', defaulting to 25");
298: }
299: }
300: sender = new JavaMailSenderImpl();
301: sender.setHost(host);
302: sender.setPort(p);
303: if (userName != null) {
304: // authentication requested
305: Properties props = new Properties();
306: props.put("mail.smtp.auth", "true");
307: if (startTls != null
308: && startTls.toLowerCase().equals("true")) {
309: props.put("mail.smtp.starttls.enable", "true");
310: }
311: sender.setJavaMailProperties(props);
312: sender.setUsername(userName);
313: sender.setPassword(password);
314: }
315: logger.info("email sender initialized from config: host = '"
316: + host + "', port = '" + p + "'");
317: }
318:
319: }
|