From af0d5f29d6cf9da0003d4ae3986a726ae8ce21af Mon Sep 17 00:00:00 2001 From: Bret McGowen Date: Thu, 28 Apr 2016 17:50:42 -0400 Subject: [PATCH] Added JavaMail API examples for App Engine (#208) * Added JavaMail API examples for App Engine * Added BounceHandlerServlet and HandleEmailDiscussion.java --- appengine/mail/README.md | 23 ++++ appengine/mail/pom.xml | 69 ++++++++++ .../appengine/mail/BounceHandlerServlet.java | 54 ++++++++ .../appengine/mail/HandleDiscussionEmail.java | 43 ++++++ .../appengine/mail/MailHandlerBase.java | 116 ++++++++++++++++ .../appengine/mail/MailHandlerServlet.java | 49 +++++++ .../example/appengine/mail/MailServlet.java | 124 ++++++++++++++++++ .../src/main/webapp/WEB-INF/appengine-web.xml | 29 ++++ .../main/webapp/WEB-INF/logging.properties | 27 ++++ .../mail/src/main/webapp/WEB-INF/web.xml | 79 +++++++++++ 10 files changed, 613 insertions(+) create mode 100644 appengine/mail/README.md create mode 100644 appengine/mail/pom.xml create mode 100644 appengine/mail/src/main/java/com/example/appengine/mail/BounceHandlerServlet.java create mode 100644 appengine/mail/src/main/java/com/example/appengine/mail/HandleDiscussionEmail.java create mode 100644 appengine/mail/src/main/java/com/example/appengine/mail/MailHandlerBase.java create mode 100644 appengine/mail/src/main/java/com/example/appengine/mail/MailHandlerServlet.java create mode 100644 appengine/mail/src/main/java/com/example/appengine/mail/MailServlet.java create mode 100644 appengine/mail/src/main/webapp/WEB-INF/appengine-web.xml create mode 100644 appengine/mail/src/main/webapp/WEB-INF/logging.properties create mode 100644 appengine/mail/src/main/webapp/WEB-INF/web.xml diff --git a/appengine/mail/README.md b/appengine/mail/README.md new file mode 100644 index 00000000000..d3663f112aa --- /dev/null +++ b/appengine/mail/README.md @@ -0,0 +1,23 @@ +# JavaMail API Email Sample for Google App Engine Standard Environment + +This sample demonstrates how to use [JavaMail][javamail-api] on [Google App Engine +standard environment][ae-docs]. + +See the [sample application documentaion][sample-docs] for more detailed +instructions. + +[ae-docs]: https://cloud.google.com/appengine/docs/java/ +[javamail-api]: http://javamail.java.net/ +[sample-docs]: https://cloud.google.com/appengine/docs/java/mail/ + +## Setup +1. Update the `` tag in `src/main/webapp/WEB-INF/appengine-web.xml` + with your project name. +1. Update the `` tag in `src/main/webapp/WEB-INF/appengine-web.xml` + with your version name. + +## Running locally + $ mvn appengine:devserver + +## Deploying + $ mvn appengine:update diff --git a/appengine/mail/pom.xml b/appengine/mail/pom.xml new file mode 100644 index 00000000000..19318a04121 --- /dev/null +++ b/appengine/mail/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + war + 1.0-SNAPSHOT + com.example.appengine + appengine-mail + + + + com.google.cloud + doc-samples + 1.0.0 + ../.. + + + + + javax.servlet + servlet-api + jar + provided + + + com.google.appengine + appengine-api-1.0-sdk + + + javax.mail + mail + 1.4.7 + + + + + ${project.build.directory}/${project.build.finalName}/WEB-INF/classes + + + org.apache.maven.plugins + 3.3 + maven-compiler-plugin + + 1.7 + 1.7 + + + + + com.google.appengine + appengine-maven-plugin + ${appengine.sdk.version} + + + + diff --git a/appengine/mail/src/main/java/com/example/appengine/mail/BounceHandlerServlet.java b/appengine/mail/src/main/java/com/example/appengine/mail/BounceHandlerServlet.java new file mode 100644 index 00000000000..0868f5b9c1e --- /dev/null +++ b/appengine/mail/src/main/java/com/example/appengine/mail/BounceHandlerServlet.java @@ -0,0 +1,54 @@ +/** + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed 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 com.example.appengine.mail; + +// [START bounce_handler_servlet] +import com.google.appengine.api.mail.BounceNotification; +import com.google.appengine.api.mail.BounceNotificationParser; + +import java.io.IOException; +import java.util.logging.Logger; +import javax.mail.MessagingException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class BounceHandlerServlet extends HttpServlet { + + private static final Logger log = Logger.getLogger(BounceHandlerServlet.class.getName()); + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { + try { + BounceNotification bounce = BounceNotificationParser.parse(req); + log.warning("Bounced email notification."); + // The following data is available in a BounceNotification object + // bounce.getOriginal().getFrom() + // bounce.getOriginal().getTo() + // bounce.getOriginal().getSubject() + // bounce.getOriginal().getText() + // bounce.getNotification().getFrom() + // bounce.getNotification().getTo() + // bounce.getNotification().getSubject() + // bounce.getNotification().getText() + // ... + } catch (MessagingException e) { + // ... + } + } +} +// [END bounce_handler_servlet] diff --git a/appengine/mail/src/main/java/com/example/appengine/mail/HandleDiscussionEmail.java b/appengine/mail/src/main/java/com/example/appengine/mail/HandleDiscussionEmail.java new file mode 100644 index 00000000000..d1db81cf5c8 --- /dev/null +++ b/appengine/mail/src/main/java/com/example/appengine/mail/HandleDiscussionEmail.java @@ -0,0 +1,43 @@ +/** + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed 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 com.example.appengine.mail; + +// [START example] +import javax.mail.internet.MimeMessage; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.ServletException; +import java.util.logging.Logger; +import java.util.regex.Matcher; + +public class HandleDiscussionEmail extends MailHandlerBase { + + private static final Logger log = Logger.getLogger(HandleDiscussionEmail.class.getName()); + public HandleDiscussionEmail() { super("discuss-(.*)@(.*)"); } + + @Override + protected boolean processMessage(HttpServletRequest req, HttpServletResponse res) + throws ServletException + { + log.info("Received e-mail sent to discuss list."); + MimeMessage msg = getMessageFromRequest(req); + Matcher match = getMatcherFromRequest(req); + // ... + return true; + } +} +// [END example] diff --git a/appengine/mail/src/main/java/com/example/appengine/mail/MailHandlerBase.java b/appengine/mail/src/main/java/com/example/appengine/mail/MailHandlerBase.java new file mode 100644 index 00000000000..b8458224e53 --- /dev/null +++ b/appengine/mail/src/main/java/com/example/appengine/mail/MailHandlerBase.java @@ -0,0 +1,116 @@ +/** + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed 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 com.example.appengine.mail; + +import javax.mail.internet.MimeMessage; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import java.io.IOException; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Base class for handling the filtering of incoming emails in App Engine. + */ +// [START example] +public abstract class MailHandlerBase implements Filter { + + private Pattern pattern = null; + + protected MailHandlerBase(String pattern) { + if (pattern == null || pattern.trim().length() == 0) + { + throw new IllegalArgumentException("Expected non-empty regular expression"); + } + this.pattern = Pattern.compile("/_ah/mail/"+pattern); + } + + @Override public void init(FilterConfig config) throws ServletException { } + + @Override public void destroy() { } + + /** + * Process the message. A message will only be passed to this method + * if the servletPath of the message (typically the recipient for + * appengine) satisfies the pattern passed to the constructor. If + * the implementation returns false, control is passed + * to the next filter in the chain. If the implementation returns + * true, the filter chain is terminated. + * + * The Matcher for the pattern can be retrieved via + * getMatcherFromRequest (e.g. if groups are used in the pattern). + */ + protected abstract boolean processMessage(HttpServletRequest req, HttpServletResponse res) throws ServletException; + + @Override + public void doFilter(ServletRequest sreq, ServletResponse sres, FilterChain chain) + throws IOException, ServletException { + + HttpServletRequest req = (HttpServletRequest) sreq; + HttpServletResponse res = (HttpServletResponse) sres; + + MimeMessage message = getMessageFromRequest(req); + Matcher m = applyPattern(req); + + if (m != null && processMessage(req, res)) { + return; + } + + chain.doFilter(req, res); // Try the next one + + } + + private Matcher applyPattern(HttpServletRequest req) { + Matcher m = pattern.matcher(req.getServletPath()); + if (!m.matches()) m = null; + + req.setAttribute("matcher", m); + return m; + } + + protected Matcher getMatcherFromRequest(ServletRequest req) { + return (Matcher) req.getAttribute("matcher"); + } + + protected MimeMessage getMessageFromRequest(ServletRequest req) throws ServletException { + MimeMessage message = (MimeMessage) req.getAttribute("mimeMessage"); + if (message == null) { + try { + Properties props = new Properties(); + Session session = Session.getDefaultInstance(props, null); + message = new MimeMessage(session, req.getInputStream()); + req.setAttribute("mimeMessage", message); + + } catch (MessagingException e) { + throw new ServletException("Error processing inbound message", e); + } catch (IOException e) { + throw new ServletException("Error processing inbound message", e); + } + } + return message; + } +} +// [END example] diff --git a/appengine/mail/src/main/java/com/example/appengine/mail/MailHandlerServlet.java b/appengine/mail/src/main/java/com/example/appengine/mail/MailHandlerServlet.java new file mode 100644 index 00000000000..47eee63801b --- /dev/null +++ b/appengine/mail/src/main/java/com/example/appengine/mail/MailHandlerServlet.java @@ -0,0 +1,49 @@ +/** + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed 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 com.example.appengine.mail; + +// [START mail_handler_servlet] +import java.io.IOException; +import java.util.logging.Logger; +import java.util.Properties; + +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.internet.MimeMessage; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class MailHandlerServlet extends HttpServlet { + + private static final Logger log = Logger.getLogger(MailHandlerServlet.class.getName()); + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { + Properties props = new Properties(); + Session session = Session.getDefaultInstance(props, null); + try { + MimeMessage message = new MimeMessage(session, req.getInputStream()); + log.info("Received mail message."); + } catch (MessagingException e) { + // ... + } + // ... + } +} +// [END mail_handler_servlet] diff --git a/appengine/mail/src/main/java/com/example/appengine/mail/MailServlet.java b/appengine/mail/src/main/java/com/example/appengine/mail/MailServlet.java new file mode 100644 index 00000000000..b655b5a2da2 --- /dev/null +++ b/appengine/mail/src/main/java/com/example/appengine/mail/MailServlet.java @@ -0,0 +1,124 @@ +/** + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed 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 com.example.appengine.mail; + +// [START simple_includes] +import java.io.IOException; +import java.util.Properties; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.AddressException; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; +// [END simple_includes] + +// [START multipart_includes] +import java.io.InputStream; +import java.io.ByteArrayInputStream; +import java.io.UnsupportedEncodingException; +import javax.activation.DataHandler; +import javax.mail.Multipart; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMultipart; +// [END multipart_includes] + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@SuppressWarnings("serial") +public class MailServlet extends HttpServlet { + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { + String type = req.getParameter("type"); + if (type != null && type.equals("multipart")) { + resp.getWriter().print("Sending HTML email with attachment."); + sendMultipartMail(); + } else { + resp.getWriter().print("Sending simple email."); + sendSimpleMail(); + } + } + + private void sendSimpleMail() { + // [START simple_example] + Properties props = new Properties(); + Session session = Session.getDefaultInstance(props, null); + + try { + Message msg = new MimeMessage(session); + msg.setFrom(new InternetAddress("admin@example.com", "Example.com Admin")); + msg.addRecipient(Message.RecipientType.TO, + new InternetAddress("user@example.com", "Mr. User")); + msg.setSubject("Your Example.com account has been activated"); + Transport.send(msg); + } catch (AddressException e) { + // ... + } catch (MessagingException e) { + // ... + } catch (UnsupportedEncodingException e) { + // ... + } + // [END simple_example] + } + + private void sendMultipartMail() { + Properties props = new Properties(); + Session session = Session.getDefaultInstance(props, null); + + String msgBody = "..."; + + try { + Message msg = new MimeMessage(session); + msg.setFrom(new InternetAddress("admin@example.com", "Example.com Admin")); + msg.addRecipient(Message.RecipientType.TO, + new InternetAddress("user@example.com", "Mr. User")); + msg.setSubject("Your Example.com account has been activated"); + msg.setText(msgBody); + + // [START multipart_example] + String htmlBody = ""; // ... + byte[] attachmentData = null; // ... + Multipart mp = new MimeMultipart(); + + MimeBodyPart htmlPart = new MimeBodyPart(); + htmlPart.setContent(htmlBody, "text/html"); + mp.addBodyPart(htmlPart); + + MimeBodyPart attachment = new MimeBodyPart(); + InputStream attachmentDataStream = new ByteArrayInputStream(attachmentData); + attachment.setFileName("manual.pdf"); + attachment.setContent(attachmentDataStream, "application/pdf"); + mp.addBodyPart(attachment); + + msg.setContent(mp); + // [END multipart_example] + + Transport.send(msg); + + } catch (AddressException e) { + // ... + } catch (MessagingException e) { + // ... + } catch (UnsupportedEncodingException e) { + // ... + } + } +} diff --git a/appengine/mail/src/main/webapp/WEB-INF/appengine-web.xml b/appengine/mail/src/main/webapp/WEB-INF/appengine-web.xml new file mode 100644 index 00000000000..176888c9b7d --- /dev/null +++ b/appengine/mail/src/main/webapp/WEB-INF/appengine-web.xml @@ -0,0 +1,29 @@ + + + + + + YOUR-PROJECT-ID + YOUR-VERSION-ID + true + + + + + mail + + mail_bounce + + + diff --git a/appengine/mail/src/main/webapp/WEB-INF/logging.properties b/appengine/mail/src/main/webapp/WEB-INF/logging.properties new file mode 100644 index 00000000000..2939d57bcbe --- /dev/null +++ b/appengine/mail/src/main/webapp/WEB-INF/logging.properties @@ -0,0 +1,27 @@ +# +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed 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. +# +# A default java.util.logging configuration. +# (All App Engine logging is through java.util.logging by default). +# +# To use this configuration, copy it into your application's WEB-INF +# folder and add the following to your appengine-web.xml: +# +# +# +# +# +# Set the default logging level for all loggers to WARNING +.level=INFO diff --git a/appengine/mail/src/main/webapp/WEB-INF/web.xml b/appengine/mail/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..f8bae043de6 --- /dev/null +++ b/appengine/mail/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,79 @@ + + + + + mail + com.example.appengine.mail.MailServlet + + + mail + / + + + + + HandleDiscussionEmail + com.example.appengine.mail.HandleDiscussionEmail + + + HandleDiscussionEmail + /_ah/mail/* + + + + + + + + + bouncehandler + com.example.appengine.mail.BounceHandlerServlet + + + bouncehandler + /_ah/bounce + + + + bounce + /_ah/bounce + + + admin + + + +