Java tutorial
/**************************************************************** * Licensed to the Apache Software Foundation (ASF) under one * * or more contributor license agreements. See the NOTICE file * * distributed with this work for additional information * * regarding copyright ownership. The ASF licenses this file * * to you under the Apache License, Version 2.0 (the * * "License"); you may not use this file except in compliance * * with the License. You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, * * software distributed under the License is distributed on an * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * * KIND, either express or implied. See the License for the * * specific language governing permissions and limitations * * under the License. * ****************************************************************/ package org.apache.hupa.server.service; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.activation.DataSource; import javax.mail.AuthenticationFailedException; import javax.mail.BodyPart; import javax.mail.Flags.Flag; import javax.mail.Folder; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Multipart; import javax.mail.Session; import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMessage.RecipientType; import javax.mail.internet.MimeMultipart; import javax.servlet.http.HttpServletRequest; import org.apache.commons.fileupload.FileItem; import org.apache.hupa.server.FileItemRegistry; import org.apache.hupa.server.IMAPStoreCache; import org.apache.hupa.server.preferences.UserPreferencesStorage; import org.apache.hupa.server.utils.MessageUtils; import org.apache.hupa.server.utils.RegexPatterns; import org.apache.hupa.server.utils.SessionUtils; import org.apache.hupa.shared.SConsts; import org.apache.hupa.shared.data.GenericResultImpl; import org.apache.hupa.shared.domain.GenericResult; import org.apache.hupa.shared.domain.MessageAttachment; import org.apache.hupa.shared.domain.SendMessageAction; import org.apache.hupa.shared.domain.SmtpMessage; import org.apache.hupa.shared.domain.User; import org.apache.hupa.shared.exception.HupaException; import com.google.inject.Inject; import com.google.web.bindery.requestfactory.server.RequestFactoryServlet; import com.sun.mail.imap.IMAPFolder; import com.sun.mail.imap.IMAPStore; public class SendMessageBaseServiceImpl extends AbstractService implements SendMessageService { UserPreferencesStorage userPreferences; @Inject public SendMessageBaseServiceImpl(UserPreferencesStorage preferences, IMAPStoreCache cache) { this.cache = cache; this.userPreferences = preferences; } public GenericResult send(SendMessageAction action) throws Exception { GenericResult result = new GenericResultImpl(); try { User user = getUser(); Message message = createMessage(cache.getMailSession(user), action); message = fillBody(message, action); sendMessage(getUser(), message); if (!user.getSettings().getSmtpServer().contains("gmail.com")) { saveSentMessage(getUser(), message); } resetAttachments(action); // TODO: notify the user more accurately where the error is // if the message was sent and the storage in the sent folder failed, etc. } catch (AddressException e) { result.setError("Error while parsing recipient: " + e.getMessage()); logger.error("Error while parsing recipient", e); } catch (AuthenticationFailedException e) { result.setError("Error while sending message: SMTP Authentication error."); logger.error("SMTP Authentication error", e); } catch (MessagingException e) { result.setError("Error while sending message: " + e.getMessage()); logger.error("Error while sending message", e); } catch (Exception e) { result.setError("Unexpected exception while sendig message: " + e.getMessage()); logger.error("Unexpected exception while sendig message: ", e); } return result; } /** * Create basic Message which contains all headers. No body is filled * * @param session the Session * @param action the action * @return message * @throws AddressException * @throws MessagingException */ public Message createMessage(Session session, SendMessageAction action) throws AddressException, MessagingException { MimeMessage message = new MimeMessage(session); SmtpMessage m = action.getMessage(); message.setFrom(new InternetAddress(m.getFrom())); userPreferences.addContact(m.getTo()); userPreferences.addContact(m.getCc()); userPreferences.addContact(m.getBcc()); message.setRecipients(RecipientType.TO, MessageUtils.getRecipients(m.getTo())); message.setRecipients(RecipientType.CC, MessageUtils.getRecipients(m.getCc())); message.setRecipients(RecipientType.BCC, MessageUtils.getRecipients(m.getBcc())); message.setSentDate(new Date()); message.addHeader("User-Agent:", "HUPA, The Apache JAMES webmail client."); message.addHeader("X-Originating-IP", getClientIpAddr()); message.setSubject(m.getSubject(), "utf-8"); updateHeaders(message, action); message.saveChanges(); return message; } public static String getClientIpAddr() { HttpServletRequest request = RequestFactoryServlet.getThreadLocalRequest(); String ip = "unknown"; if (request != null) { ip = request.getHeader("X-Forwarded-For"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_CLIENT_IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("HTTP_X_FORWARDED_FOR"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } } return ip; } protected void updateHeaders(MimeMessage message, SendMessageAction action) { if (action.getInReplyTo() != null) { try { message.addHeader(SConsts.HEADER_IN_REPLY_TO, action.getInReplyTo()); } catch (MessagingException e) { logger.error("Error while setting header:" + e.getMessage(), e); } } if (action.getReferences() != null) { try { message.addHeader(SConsts.HEADER_REFERENCES, action.getReferences()); } catch (MessagingException e) { logger.error("Error while setting header:" + e.getMessage(), e); } } } /** * Fill the body of the given message with data which the given action contain * * @param message the message * @param action the action * @return filledMessage * @throws MessagingException * @throws IOException * @throws HupaException */ public Message fillBody(Message message, SendMessageAction action) throws MessagingException, IOException, HupaException { String html = restoreInlineLinks(action.getMessage().getText()); // TODO: client sends the message as a html document right now, // the idea is that it should be sent in both formats because // it is easier to handle html in the browser. String text = htmlToText(html); @SuppressWarnings("rawtypes") List items = getAttachments(action); return composeMessage(message, text, html, items); } protected String restoreInlineLinks(String s) { return RegexPatterns.replaceAll(s, RegexPatterns.regex_revertInlineImg, RegexPatterns.repl_revertInlineImg); } // TODO: just temporary stuff because it has to be done in the client side protected String htmlToText(String s) { s = s.replaceAll("\n", " "); s = s.replaceAll("(?si)<br\\s*?/?>", "\n"); s = s.replaceAll("(?si)</div\\s*?>", "\n"); s = s.replaceAll("(\\w)<.*?>(\\w)", "$1 $2"); s = s.replaceAll("<.*?>", ""); s = s.replaceAll("[ \t]+", " "); return s; } /** * Get the attachments stored in the registry. * * @param action * @return A list of stored attachments * @throws HupaException */ @SuppressWarnings("rawtypes") protected List getAttachments(SendMessageAction action) throws MessagingException, HupaException { FileItemRegistry registry = SessionUtils.getSessionRegistry(logger, httpSessionProvider.get()); List<MessageAttachment> attachments = action.getMessage().getMessageAttachments(); ArrayList<FileItem> items = new ArrayList<FileItem>(); if (attachments != null && attachments.size() > 0) { for (MessageAttachment attachment : attachments) { FileItem fItem = registry.get(attachment.getName()); if (fItem != null) items.add(fItem); } logger.debug("Found " + items.size() + " attachmets in the registry."); } return items; } /** * Remove attachments from the registry * * @param action * @throws MessagingException * @throws ActionException */ protected void resetAttachments(SendMessageAction action) throws MessagingException { SmtpMessage msg = action.getMessage(); List<MessageAttachment> attachments = msg.getMessageAttachments(); if (attachments != null && !attachments.isEmpty()) { for (MessageAttachment attach : attachments) SessionUtils.getSessionRegistry(logger, httpSessionProvider.get()).remove(attach.getName()); } } /** * Send the message using SMTP, if the configuration uses authenticated SMTP, it uses * the user stored in session to get the given login and password. * * @param user * @param session * @param message * @throws MessagingException */ protected void sendMessage(User user, Message message) throws MessagingException { cache.sendMessage(message); logger.info("Send message from " + message.getFrom()[0].toString()); } /** * Save the message in the sent folder * * @param user * @param message * @throws MessagingException * @throws IOException */ protected void saveSentMessage(User user, Message message) throws MessagingException, IOException { IMAPStore iStore = cache.get(user); IMAPFolder folder = (IMAPFolder) iStore.getFolder(user.getSettings().getSentFolderName()); if (folder.exists() || folder.create(IMAPFolder.READ_WRITE)) { if (folder.isOpen() == false) { folder.open(Folder.READ_WRITE); } // It is necessary to copy the message, before putting it // in the sent folder. If not, it is not guaranteed that it is // stored in ascii and is not possible to get the attachments // size. message.saveChanges() doesn't fix the problem. // There are tests which demonstrate this. message = new MimeMessage((MimeMessage) message); message.setFlag(Flag.SEEN, true); folder.appendMessages(new Message[] { message }); try { folder.close(false); } catch (MessagingException e) { // we don't care on close } } } /** * Fill the body of a message already created. * The result message depends on the information given. * * @param message * @param text * @param html * @param parts * @return The composed message * @throws MessagingException * @throws IOException */ @SuppressWarnings("rawtypes") public static Message composeMessage(Message message, String text, String html, List parts) throws MessagingException, IOException { MimeBodyPart txtPart = null; MimeBodyPart htmlPart = null; MimeMultipart mimeMultipart = null; if (text == null && html == null) { text = ""; } if (text != null) { txtPart = new MimeBodyPart(); txtPart.setContent(text, "text/plain; charset=UTF-8"); } if (html != null) { htmlPart = new MimeBodyPart(); htmlPart.setContent(html, "text/html; charset=UTF-8"); } if (html != null && text != null) { mimeMultipart = new MimeMultipart(); mimeMultipart.setSubType("alternative"); mimeMultipart.addBodyPart(txtPart); mimeMultipart.addBodyPart(htmlPart); } if (parts == null || parts.isEmpty()) { if (mimeMultipart != null) { message.setContent(mimeMultipart); } else if (html != null) { message.setText(html); message.setHeader("Content-type", "text/html"); } else if (text != null) { message.setText(text); } } else { MimeBodyPart bodyPart = new MimeBodyPart(); if (mimeMultipart != null) { bodyPart.setContent(mimeMultipart); } else if (html != null) { bodyPart.setText(html); bodyPart.setHeader("Content-type", "text/html"); } else if (text != null) { bodyPart.setText(text); } Multipart multipart = new MimeMultipart(); multipart.addBodyPart(bodyPart); for (Object attachment : parts) { if (attachment instanceof FileItem) { multipart.addBodyPart(MessageUtils.fileitemToBodypart((FileItem) attachment)); } else { multipart.addBodyPart((BodyPart) attachment); } } message.setContent(multipart); } message.saveChanges(); return message; } /** * DataStore which wrap a FileItem * */ public static class FileItemDataStore implements DataSource { private FileItem item; public FileItemDataStore(FileItem item) { this.item = item; } /* * (non-Javadoc) * @see javax.activation.DataSource#getContentType() */ public String getContentType() { return item.getContentType(); } /* * (non-Javadoc) * @see javax.activation.DataSource#getInputStream() */ public InputStream getInputStream() throws IOException { return item.getInputStream(); } /* * (non-Javadoc) * @see javax.activation.DataSource#getName() */ public String getName() { String fullName = item.getName(); // Strip path from file int index = fullName.lastIndexOf(File.separator); if (index == -1) { return fullName; } else { return fullName.substring(index + 1, fullName.length()); } } /* * (non-Javadoc) * @see javax.activation.DataSource#getOutputStream() */ public OutputStream getOutputStream() throws IOException { return null; } } }