diff --git a/appengine-java21/ee8/mail/README.md b/appengine-java21/ee8/mail/README.md new file mode 100644 index 00000000000..4c667c540d7 --- /dev/null +++ b/appengine-java21/ee8/mail/README.md @@ -0,0 +1,21 @@ +# JakartaMail API Email Sample for Google App Engine Standard Environment + +This sample demonstrates how to use [JakartaMail][jakartamail-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/ +[jakartamail-api]: https://jakartaee.github.io/mail-api/ +[sample-docs]: https://cloud.google.com/appengine/docs/java/mail/ + +## Setup + + gcloud init + +## Running locally + $ mvn appengine:run + +## Deploying + $ mvn clean package appengine:deploy diff --git a/appengine-java21/ee8/mail/pom.xml b/appengine-java21/ee8/mail/pom.xml new file mode 100644 index 00000000000..157e58f6157 --- /dev/null +++ b/appengine-java21/ee8/mail/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + war + 1.0-SNAPSHOT + com.example.appengine + appengine-mail-j21 + + + + com.google.cloud.samples + shared-configuration + 1.2.0 + + + + 21 + 21 + + + + + jakarta.servlet + jakarta.servlet-api + 4.0.4 + jar + provided + + + + com.google.appengine + appengine-api-1.0-sdk + 2.0.39 + + + jakarta.mail + jakarta.mail-api + 1.6.7 + + + com.sun.mail + jakarta.mail + 1.6.7 + + + jakarta.activation + jakarta.activation-api + 1.2.2 + + + + + ${project.build.directory}/${project.build.finalName}/WEB-INF/classes + + + org.apache.maven.plugins + maven-war-plugin + 3.4.0 + + + com.google.cloud.tools + appengine-maven-plugin + 2.8.3 + + GCLOUD_CONFIG + GCLOUD_CONFIG + true + true + + + + + diff --git a/appengine-java21/ee8/mail/src/main/java/com/example/appengine/mail/BounceHandlerServlet.java b/appengine-java21/ee8/mail/src/main/java/com/example/appengine/mail/BounceHandlerServlet.java new file mode 100644 index 00000000000..77491d4ea76 --- /dev/null +++ b/appengine-java21/ee8/mail/src/main/java/com/example/appengine/mail/BounceHandlerServlet.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016 Google LLC + * + * 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 gae_java21_mail_bounce_handler] + +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 gae_java21_mail_bounce_handler] diff --git a/appengine-java21/ee8/mail/src/main/java/com/example/appengine/mail/HandleDiscussionEmail.java b/appengine-java21/ee8/mail/src/main/java/com/example/appengine/mail/HandleDiscussionEmail.java new file mode 100644 index 00000000000..27860f4ea71 --- /dev/null +++ b/appengine-java21/ee8/mail/src/main/java/com/example/appengine/mail/HandleDiscussionEmail.java @@ -0,0 +1,46 @@ +/* + * Copyright 2016 Google LLC + * + * 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 gae_java21_mail_discussion_email] + +import java.util.logging.Logger; +import java.util.regex.Matcher; +import javax.mail.internet.MimeMessage; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +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 gae_java21_mail_discussion_email] diff --git a/appengine-java21/ee8/mail/src/main/java/com/example/appengine/mail/MailHandlerBase.java b/appengine-java21/ee8/mail/src/main/java/com/example/appengine/mail/MailHandlerBase.java new file mode 100644 index 00000000000..3410ea976d5 --- /dev/null +++ b/appengine-java21/ee8/mail/src/main/java/com/example/appengine/mail/MailHandlerBase.java @@ -0,0 +1,116 @@ +/* + * Copyright 2016 Google LLC + * + * 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 gae_java21_mail_handler_base] + +import java.io.IOException; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.internet.MimeMessage; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** Base class for handling the filtering of incoming emails in App Engine. */ +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 gae_java21_mail_handler_base] diff --git a/appengine-java21/ee8/mail/src/main/java/com/example/appengine/mail/MailHandlerServlet.java b/appengine-java21/ee8/mail/src/main/java/com/example/appengine/mail/MailHandlerServlet.java new file mode 100644 index 00000000000..60036ed2880 --- /dev/null +++ b/appengine-java21/ee8/mail/src/main/java/com/example/appengine/mail/MailHandlerServlet.java @@ -0,0 +1,48 @@ +/* + * Copyright 2016 Google LLC + * + * 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 gae_java21_mail_handler_servlet] + +import java.io.IOException; +import java.util.Properties; +import java.util.logging.Logger; +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 gae_java21_mail_handler_servlet] diff --git a/appengine-java21/ee8/mail/src/main/java/com/example/appengine/mail/MailServlet.java b/appengine-java21/ee8/mail/src/main/java/com/example/appengine/mail/MailServlet.java new file mode 100644 index 00000000000..ef9cb3e6370 --- /dev/null +++ b/appengine-java21/ee8/mail/src/main/java/com/example/appengine/mail/MailServlet.java @@ -0,0 +1,118 @@ +/* + * Copyright 2016 Google LLC + * + * 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 java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.Properties; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Multipart; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.AddressException; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; +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 gae_java21_mail_simple] + 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"); + msg.setText("This is a test"); + Transport.send(msg); + } catch (AddressException e) { + // ... + } catch (MessagingException e) { + // ... + } catch (UnsupportedEncodingException e) { + // ... + } + // [END gae_java21_mail_simple] + } + + 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 gae_java21_mail_multipart] + 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 gae_java21_mail_multipart] + + Transport.send(msg); + + } catch (AddressException e) { + // ... + } catch (MessagingException e) { + // ... + } catch (UnsupportedEncodingException e) { + // ... + } + } +} diff --git a/appengine-java21/ee8/mail/src/main/webapp/WEB-INF/appengine-web.xml b/appengine-java21/ee8/mail/src/main/webapp/WEB-INF/appengine-web.xml new file mode 100644 index 00000000000..af94612adc4 --- /dev/null +++ b/appengine-java21/ee8/mail/src/main/webapp/WEB-INF/appengine-web.xml @@ -0,0 +1,31 @@ + + + + + + java21 + true + + + + + + + + mail + + mail_bounce + + + diff --git a/appengine-java21/ee8/mail/src/main/webapp/WEB-INF/logging.properties b/appengine-java21/ee8/mail/src/main/webapp/WEB-INF/logging.properties new file mode 100644 index 00000000000..8d8ee8d09dc --- /dev/null +++ b/appengine-java21/ee8/mail/src/main/webapp/WEB-INF/logging.properties @@ -0,0 +1,27 @@ +# +# Copyright 2016 Google LLC +# +# 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-java21/ee8/mail/src/main/webapp/WEB-INF/web.xml b/appengine-java21/ee8/mail/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..6d130f9aed5 --- /dev/null +++ b/appengine-java21/ee8/mail/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,83 @@ + + + + + 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 + + + +