├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src ├── main └── java │ └── org │ └── nlab │ └── smtp │ ├── exception │ └── MailSendException.java │ ├── pool │ ├── ObjectPoolAware.java │ ├── PoolConfigs.java │ └── SmtpConnectionPool.java │ └── transport │ ├── connection │ ├── ClosableSmtpConnection.java │ └── DefaultClosableSmtpConnection.java │ ├── factory │ ├── SmtpConnectionFactories.java │ ├── SmtpConnectionFactory.java │ └── SmtpConnectionFactoryBuilder.java │ └── strategy │ ├── ConnectionStrategy.java │ ├── ConnectionStrategyFactory.java │ ├── TransportStrategy.java │ └── TransportStrategyFactory.java └── test └── java └── org └── nlab └── smtp ├── AbstractTest.java ├── TestListener.java ├── TestSendException.java ├── TestSendMail.java └── TestSendMailBench.java /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | release.properties 7 | dependency-reduced-pom.xml 8 | buildNumber.properties 9 | .idea 10 | *.iml -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SMTP Connection Pool 2 | 3 | This library implements a SMTP connection pool using [Jakarta Mail](https://eclipse-ee4j.github.io/mail/) 4 | formerly known as [Java Mail](https://java.net/projects/javamail/pages/Home) for the SMTP code and the 5 | [Apache Commons Pool](https://commons.apache.org/proper/commons-pool/) for the pool code. 6 | 7 | The pool, thanks to the Apache library, supports most common pool features: 8 | 9 | - Max total 10 | - Min idle 11 | - Eviction 12 | - Test on borrow 13 | - ... 14 | 15 | # Requirements 16 | 17 | Java 1.8 18 | 19 | # Maven dependency 20 | 21 | Search for the latest version on [Maven central](http://search.maven.org/#search|ga|1|g%3A%22com.github.nithril%22%20a%3A%22smtp-connection-pool%22): 22 | 23 | eg.: 24 | 25 | ```xml 26 | 27 | com.github.nithril 28 | smtp-connection-pool 29 | 1.4.0 30 | 31 | ``` 32 | 33 | 34 | # How to use the connection pool? 35 | 36 | The `SmtpConnectionPool` creates a JavaMail [`Transport`](https://javamail.java.net/nonav/docs/api/javax/mail/Transport.html) using a `SmtpConnectionFactory`. 37 | 38 | ## Smtp Connection Factory 39 | 40 | The `SmtpConnectionFactory` can be created using different ways. 41 | 42 | **If you already have a configured [`Session`](https://javamail.java.net/nonav/docs/api/javax/mail/Session.html)** 43 | ```java 44 | SmtpConnectionFactory factory = SmtpConnectionFactories.newSmtpFactory(aSession); 45 | ``` 46 | JavaMail will retrieve the protocol, host, username... from the `Session`. 47 | 48 | 49 | **You can build the factory using a builder** 50 | ```java 51 | SmtpConnectionFactory factory = SmtpConnectionFactoryBuilder.newSmtpBuilder() 52 | .session(aSession) 53 | .protocol("smtp") 54 | .host("mailer") 55 | .port(2525) 56 | .username("foo") 57 | .password("bar").build(); 58 | ``` 59 | 60 | All builder parameters are optionals. JavaMail will fallback to the default configuration (smtp, port 25...) 61 | 62 | 63 | 64 | **You can instanciate directly the factory** 65 | ```java 66 | new SmtpConnectionFactory(aSession, aTransportStrategy, aConnectionStrategy); 67 | ``` 68 | 69 | Where: 70 | 71 | - `TransportStrategy` allows to configure how the 72 | [transport is got](https://javamail.java.net/nonav/docs/api/javax/mail/Session.html#getTransport%28%29) (default, protocol, url, provider) 73 | - `ConnectionStrategy` allows to configure [how to connect](https://javamail.java.net/nonav/docs/api/javax/mail/Service.html#connect%28%29) (default, username/password...) 74 | 75 | 76 | ## Smtp Connection Pool 77 | 78 | 79 | Java code: 80 | ```java 81 | 82 | //Declare the factory and the connection pool, usually at the application startup 83 | SmtpConnectionPool smtpConnectionPool = new SmtpConnectionPool(SmtpConnectionFactoryBuilder.newSmtpBuilder().build()); 84 | 85 | //borrow an object in a try-with-resource statement or call `close` by yourself 86 | try (ClosableSmtpConnection transport = smtpConnectionPool.borrowObject()) { 87 | MimeMessage mimeMessage = new MimeMessage(transport.getSession()); 88 | mimeMessage.addRecipients(Message.RecipientType.TO, to); 89 | mimeMessage.setFrom("from@example.com"); 90 | mimeMessage.setSubject("Hi!"); 91 | mimeMessage.setText("Hello World!"); 92 | transport.sendMessage(mimeMessage); 93 | } 94 | 95 | //Close the pool, usually when the application shutdown 96 | smtpConnectionPool.close(); 97 | 98 | ``` 99 | 100 | # How to configure the pool? 101 | 102 | Configuration is held by the Pool code, see the [Commons Pool Javadoc](https://commons.apache.org/proper/commons-pool/api-2.3/index.html). 103 | 104 | Example: 105 | ```java 106 | 107 | //Create the configuration 108 | GenericObjectPoolConfig config = new GenericObjectPoolConfig(); 109 | config.setMaxTotal(2); 110 | 111 | //Declare the factory and the connection pool, usually at application startup 112 | SmtpConnectionPool smtpConnectionPool = new SmtpConnectionPool(SmtpConnectionFactoryBuilder.newSmtpBuilder().build(), config); 113 | 114 | ``` 115 | 116 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.github.nithril 8 | smtp-connection-pool 9 | 2.0.1-SNAPSHOT 10 | 11 | SMTP Connection Pool 12 | SMTP Connection Pool which uses JavaMail and Apache Common Pool 13 | https://github.com/nithril/smtp-connection-pool 14 | 15 | 16 | 17 | Apache License, Version 2.0 18 | http://www.apache.org/licenses/LICENSE-2.0 19 | 20 | 21 | 22 | 23 | 24 | nithril 25 | Nicolas Labrot 26 | nithril@gmail.com 27 | https://github.com/nithril 28 | 29 | 30 | 31 | 32 | scm:git:git@github.com:nithril/smtp-connection-pool.git 33 | scm:git:git@github.com:nithril/smtp-connection-pool.git 34 | git@github.com:nithril/smtp-connection-pool.git 35 | 36 | 37 | 38 | 39 | 2.0.1 40 | 1.8 41 | 1.8 42 | 43 | 44 | 45 | 46 | 47 | 48 | com.sun.mail 49 | jakarta.mail 50 | ${jakarta.mail.version} 51 | 52 | 53 | 54 | org.apache.commons 55 | commons-pool2 56 | 2.11.1 57 | 58 | 59 | 60 | org.slf4j 61 | slf4j-api 62 | 1.7.25 63 | 64 | 65 | 66 | org.slf4j 67 | slf4j-simple 68 | 1.7.25 69 | test 70 | 71 | 72 | 73 | junit 74 | junit 75 | 4.13.1 76 | test 77 | 78 | 79 | 80 | com.icegreen 81 | greenmail 82 | 2.0.0-alpha-1 83 | test 84 | 85 | 86 | 87 | com.google.guava 88 | guava 89 | 32.0.0-jre 90 | test 91 | 92 | 93 | 94 | 95 | 96 | 97 | release 98 | 99 | 100 | 101 | org.apache.maven.plugins 102 | maven-source-plugin 103 | 2.4 104 | 105 | 106 | attach-sources 107 | 108 | jar-no-fork 109 | 110 | 111 | 112 | 113 | 114 | org.apache.maven.plugins 115 | maven-javadoc-plugin 116 | 2.10.3 117 | 118 | 119 | attach-javadocs 120 | 121 | jar 122 | 123 | 124 | 125 | 126 | 127 | org.apache.maven.plugins 128 | maven-gpg-plugin 129 | 1.6 130 | 131 | 132 | sign-artifacts 133 | verify 134 | 135 | sign 136 | 137 | 138 | 139 | 140 | 141 | org.sonatype.plugins 142 | nexus-staging-maven-plugin 143 | 1.6.5 144 | true 145 | 146 | ossrh 147 | https://oss.sonatype.org/ 148 | true 149 | 150 | 151 | 152 | 153 | 154 | 155 | ossrh 156 | https://oss.sonatype.org/content/repositories/snapshots 157 | 158 | 159 | ossrh 160 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 161 | 162 | 163 | 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /src/main/java/org/nlab/smtp/exception/MailSendException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2012 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.nlab.smtp.exception; 17 | 18 | import java.io.PrintStream; 19 | import java.io.PrintWriter; 20 | import java.util.LinkedHashMap; 21 | import java.util.Map; 22 | 23 | /** 24 | * From Spring Framework 25 | *

26 | * Exception thrown when a mail sending error is encountered. 27 | * Can register failed messages with their exceptions. 28 | * 29 | * @author Dmitriy Kopylenko 30 | * @author Juergen Hoeller 31 | */ 32 | @SuppressWarnings("serial") 33 | public class MailSendException extends RuntimeException { 34 | 35 | private transient final Map failedMessages; 36 | 37 | private Exception[] messageExceptions; 38 | 39 | 40 | /** 41 | * Constructor for MailSendException. 42 | * 43 | * @param msg the detail message 44 | */ 45 | public MailSendException(String msg) { 46 | this(msg, null); 47 | } 48 | 49 | /** 50 | * Constructor for MailSendException. 51 | * 52 | * @param msg the detail message 53 | * @param cause the root cause from the mail API in use 54 | */ 55 | public MailSendException(String msg, Throwable cause) { 56 | super(msg, cause); 57 | this.failedMessages = new LinkedHashMap(); 58 | } 59 | 60 | /** 61 | * Constructor for registration of failed messages, with the 62 | * messages that failed as keys, and the thrown exceptions as values. 63 | *

The messages should be the same that were originally passed 64 | * to the invoked send method. 65 | * 66 | * @param msg the detail message 67 | * @param cause the root cause from the mail API in use 68 | * @param failedMessages Map of failed messages as keys and thrown 69 | * exceptions as values 70 | */ 71 | public MailSendException(String msg, Throwable cause, Map failedMessages) { 72 | super(msg, cause); 73 | this.failedMessages = new LinkedHashMap(failedMessages); 74 | this.messageExceptions = failedMessages.values().toArray(new Exception[failedMessages.size()]); 75 | } 76 | 77 | /** 78 | * Constructor for registration of failed messages, with the 79 | * messages that failed as keys, and the thrown exceptions as values. 80 | *

The messages should be the same that were originally passed 81 | * to the invoked send method. 82 | * 83 | * @param failedMessages Map of failed messages as keys and thrown 84 | * exceptions as values 85 | */ 86 | public MailSendException(Map failedMessages) { 87 | this(null, null, failedMessages); 88 | } 89 | 90 | 91 | /** 92 | * Return a Map with the failed messages as keys, and the thrown exceptions 93 | * as values. 94 | *

Note that a general mail server connection failure will not result 95 | * in failed messages being returned here: A message will only be 96 | * contained here if actually sending it was attempted but failed. 97 | *

The messages will be the same that were originally passed to the 98 | * invoked send method, that is, SimpleMailMessages in case of using 99 | * the generic MailSender interface. 100 | *

In case of sending MimeMessage instances via JavaMailSender, 101 | * the messages will be of type MimeMessage. 102 | *

NOTE: This Map will not be available after serialization. 103 | * Use {@link #getMessageExceptions()} in such a scenario, which will 104 | * be available after serialization as well. 105 | * 106 | * @return the Map of failed messages as keys and thrown exceptions as values 107 | * @see jakarta.mail.internet.MimeMessage 108 | */ 109 | public final Map getFailedMessages() { 110 | return this.failedMessages; 111 | } 112 | 113 | /** 114 | * Return an array with thrown message exceptions. 115 | *

Note that a general mail server connection failure will not result 116 | * in failed messages being returned here: A message will only be 117 | * contained here if actually sending it was attempted but failed. 118 | * 119 | * @return the array of thrown message exceptions, 120 | * or an empty array if no failed messages 121 | */ 122 | public final Exception[] getMessageExceptions() { 123 | return (this.messageExceptions != null ? this.messageExceptions : new Exception[0]); 124 | } 125 | 126 | 127 | @Override 128 | public String getMessage() { 129 | if (messageExceptions == null || messageExceptions.length == 0) { 130 | return super.getMessage(); 131 | } else { 132 | StringBuilder sb = new StringBuilder(); 133 | String baseMessage = super.getMessage(); 134 | if (baseMessage != null) { 135 | sb.append(baseMessage).append(". "); 136 | } 137 | sb.append("Failed messages: "); 138 | for (int i = 0; i < this.messageExceptions.length; i++) { 139 | Exception subEx = this.messageExceptions[i]; 140 | sb.append(subEx.toString()); 141 | if (i < this.messageExceptions.length - 1) { 142 | sb.append("; "); 143 | } 144 | } 145 | return sb.toString(); 146 | } 147 | } 148 | 149 | @Override 150 | public String toString() { 151 | if (messageExceptions == null || messageExceptions.length == 0) { 152 | return super.toString(); 153 | } else { 154 | StringBuilder sb = new StringBuilder(super.toString()); 155 | sb.append("; message exceptions (").append(this.messageExceptions.length).append(") are:"); 156 | for (int i = 0; i < this.messageExceptions.length; i++) { 157 | Exception subEx = this.messageExceptions[i]; 158 | sb.append('\n').append("Failed message ").append(i + 1).append(": "); 159 | sb.append(subEx); 160 | } 161 | return sb.toString(); 162 | } 163 | } 164 | 165 | @Override 166 | public void printStackTrace(PrintStream ps) { 167 | if (messageExceptions == null || messageExceptions.length == 0) { 168 | super.printStackTrace(ps); 169 | } else { 170 | ps.println(super.toString() + "; message exception details (" + 171 | this.messageExceptions.length + ") are:"); 172 | for (int i = 0; i < this.messageExceptions.length; i++) { 173 | Exception subEx = this.messageExceptions[i]; 174 | ps.println("Failed message " + (i + 1) + ":"); 175 | subEx.printStackTrace(ps); 176 | } 177 | } 178 | } 179 | 180 | @Override 181 | public void printStackTrace(PrintWriter pw) { 182 | if (messageExceptions == null || messageExceptions.length == 0) { 183 | super.printStackTrace(pw); 184 | } else { 185 | pw.println(super.toString() + "; message exception details (" + 186 | this.messageExceptions.length + ") are:"); 187 | for (int i = 0; i < this.messageExceptions.length; i++) { 188 | Exception subEx = this.messageExceptions[i]; 189 | pw.println("Failed message " + (i + 1) + ":"); 190 | subEx.printStackTrace(pw); 191 | } 192 | } 193 | } 194 | 195 | 196 | } 197 | -------------------------------------------------------------------------------- /src/main/java/org/nlab/smtp/pool/ObjectPoolAware.java: -------------------------------------------------------------------------------- 1 | package org.nlab.smtp.pool; 2 | 3 | /** 4 | * Created by nlabrot on 30/04/15. 5 | */ 6 | public interface ObjectPoolAware { 7 | /** 8 | * Called after the object has been borrowed on the pool to set the pool on the object. 9 | * 10 | * @param objectPool 11 | */ 12 | void setObjectPool(SmtpConnectionPool objectPool); 13 | 14 | SmtpConnectionPool getObjectPool(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/nlab/smtp/pool/PoolConfigs.java: -------------------------------------------------------------------------------- 1 | package org.nlab.smtp.pool; 2 | 3 | import org.apache.commons.pool2.impl.GenericObjectPoolConfig; 4 | 5 | import java.util.concurrent.TimeUnit; 6 | 7 | /** 8 | * Ease pool configuration with default pool configuration 9 | *

10 | * Created by nlabrot on 12/06/17. 11 | */ 12 | public class PoolConfigs { 13 | 14 | /** 15 | * Default {@link GenericObjectPoolConfig} config 16 | * See default parameters {@link GenericObjectPoolConfig} 17 | * 18 | * @return 19 | */ 20 | public static GenericObjectPoolConfig defaultConfig() { 21 | return new GenericObjectPoolConfig(); 22 | } 23 | 24 | /** 25 | * Default {@link GenericObjectPoolConfig} config 26 | * {@link GenericObjectPoolConfig#getTestOnBorrow} : true 27 | * minIdle: 0 28 | * maxIdle: 8 29 | * maxTotal: 8 30 | * maxWaitMillis: 10000 31 | * minEvictableIdleTimeMillis: 5 minutes 32 | * timeBetweenEvictionRunsMillis: 10 seconds 33 | * 34 | * @return 35 | */ 36 | public static GenericObjectPoolConfig standardConfig() { 37 | GenericObjectPoolConfig config = new GenericObjectPoolConfig(); 38 | config.setTestOnBorrow(true); 39 | config.setMinIdle(0); 40 | config.setMaxIdle(8); 41 | config.setMaxTotal(8); 42 | 43 | config.setMinEvictableIdleTimeMillis(TimeUnit.MINUTES.toMillis(5)); 44 | config.setTimeBetweenEvictionRunsMillis(10000); 45 | 46 | 47 | config.setMaxWaitMillis(10000); 48 | return config; 49 | } 50 | 51 | 52 | /** 53 | * @param minIdle 54 | * @param maxIdle 55 | * @param maxTotal 56 | * @param maxWaitMillis 57 | * @param minEvictableIdleTimeMillis 58 | * @param timeBetweenEvictionRunsMillis 59 | * @return 60 | */ 61 | public static GenericObjectPoolConfig standardConfig(int minIdle, int maxIdle, int maxTotal, int maxWaitMillis, int minEvictableIdleTimeMillis, int timeBetweenEvictionRunsMillis) { 62 | GenericObjectPoolConfig config = new GenericObjectPoolConfig(); 63 | config.setTestOnBorrow(true); 64 | config.setMinIdle(minIdle); 65 | config.setMaxIdle(maxIdle); 66 | config.setMaxTotal(maxTotal); 67 | 68 | config.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); 69 | config.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); 70 | 71 | 72 | config.setMaxWaitMillis(maxWaitMillis); 73 | return config; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/org/nlab/smtp/pool/SmtpConnectionPool.java: -------------------------------------------------------------------------------- 1 | package org.nlab.smtp.pool; 2 | 3 | import jakarta.mail.Session; 4 | import org.apache.commons.pool2.impl.AbandonedConfig; 5 | import org.apache.commons.pool2.impl.GenericObjectPool; 6 | import org.apache.commons.pool2.impl.GenericObjectPoolConfig; 7 | import org.nlab.smtp.transport.connection.ClosableSmtpConnection; 8 | import org.nlab.smtp.transport.factory.SmtpConnectionFactory; 9 | 10 | /** 11 | * Created by nlabrot on 30/04/15. 12 | */ 13 | public class SmtpConnectionPool extends GenericObjectPool { 14 | 15 | public SmtpConnectionPool(SmtpConnectionFactory factory) { 16 | super(factory); 17 | } 18 | 19 | public SmtpConnectionPool(SmtpConnectionFactory factory, GenericObjectPoolConfig config) { 20 | super(factory, config); 21 | } 22 | 23 | public SmtpConnectionPool(SmtpConnectionFactory factory, GenericObjectPoolConfig config, AbandonedConfig abandonedConfig) { 24 | super(factory, config, abandonedConfig); 25 | } 26 | 27 | @Override 28 | public ClosableSmtpConnection borrowObject() throws Exception { 29 | ClosableSmtpConnection object = super.borrowObject(); 30 | if (object instanceof ObjectPoolAware) { 31 | ((ObjectPoolAware) object).setObjectPool(this); 32 | } 33 | return object; 34 | } 35 | 36 | @Override 37 | public ClosableSmtpConnection borrowObject(long borrowMaxWaitMillis) throws Exception { 38 | ClosableSmtpConnection object = super.borrowObject(borrowMaxWaitMillis); 39 | if (object instanceof ObjectPoolAware) { 40 | ((ObjectPoolAware) object).setObjectPool(this); 41 | } 42 | return object; 43 | } 44 | 45 | public Session getSession() { 46 | return ((SmtpConnectionFactory) getFactory()).getSession(); 47 | } 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/nlab/smtp/transport/connection/ClosableSmtpConnection.java: -------------------------------------------------------------------------------- 1 | package org.nlab.smtp.transport.connection; 2 | 3 | import jakarta.mail.Address; 4 | import jakarta.mail.MessagingException; 5 | import jakarta.mail.Session; 6 | import jakarta.mail.Transport; 7 | import jakarta.mail.event.TransportListener; 8 | import jakarta.mail.internet.MimeMessage; 9 | import org.nlab.smtp.exception.MailSendException; 10 | 11 | /** 12 | * Created by nlabrot on 30/04/15. 13 | */ 14 | public interface ClosableSmtpConnection extends AutoCloseable { 15 | 16 | String HEADER_MESSAGE_ID = "Message-ID"; 17 | 18 | /** 19 | * Marks this pooled object to be invalid such that it is not returned in the pool when closed. 20 | * This is equivalent to setInvalid(true). 21 | */ 22 | void invalidate(); 23 | 24 | /** 25 | * Allows setting the invalid flag to true or false 26 | * 27 | * @param invalid true if the object should not be returned in the pool when closed. 28 | */ 29 | void setInvalidateConnectionOnClose(boolean invalid); 30 | 31 | /** 32 | * Send a message to a list of recipients 33 | * 34 | * @param msg 35 | * @param recipients 36 | * @throws MessagingException 37 | * @throws MailSendException 38 | */ 39 | void sendMessage(MimeMessage msg, Address[] recipients) throws MessagingException, MailSendException; 40 | 41 | /** 42 | * Send a message. The list of recipients are taken from {@link MimeMessage#getAllRecipients()} 43 | * 44 | * @param msg MimeMessage 45 | * @throws MessagingException 46 | */ 47 | void sendMessage(MimeMessage msg) throws MessagingException; 48 | 49 | /** 50 | * Send the given array of JavaMail MIME messages in batch. Do not stop the batch when a message could not be sent 51 | * {@link MailSendException#getFailedMessages()} will contain the failed messages 52 | * 53 | * @param msgs Array of MimeMessage 54 | * @throws MailSendException in case of failure when sending a message 55 | */ 56 | void sendMessages(MimeMessage... msgs) throws MailSendException; 57 | 58 | /** 59 | * Test if the current connection is connected 60 | * 61 | * @return 62 | */ 63 | boolean isConnected(); 64 | 65 | /** 66 | * Add a new {@link TransportListener} 67 | * 68 | * @param l 69 | */ 70 | void addTransportListener(TransportListener l); 71 | 72 | /** 73 | * Remove the provided {@link TransportListener} 74 | * 75 | * @param l 76 | */ 77 | void removeTransportListener(TransportListener l); 78 | 79 | /** 80 | * Clear the list of {@link TransportListener} 81 | */ 82 | void clearListeners(); 83 | 84 | /** 85 | * @return the {@link Transport} associated to this connection 86 | */ 87 | Transport getDelegate(); 88 | 89 | /** 90 | * @return the {@link Session} 91 | */ 92 | Session getSession(); 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/org/nlab/smtp/transport/connection/DefaultClosableSmtpConnection.java: -------------------------------------------------------------------------------- 1 | package org.nlab.smtp.transport.connection; 2 | 3 | import jakarta.mail.Address; 4 | import jakarta.mail.MessagingException; 5 | import jakarta.mail.Session; 6 | import jakarta.mail.Transport; 7 | import jakarta.mail.event.TransportListener; 8 | import jakarta.mail.internet.MimeMessage; 9 | import org.nlab.smtp.exception.MailSendException; 10 | import org.nlab.smtp.pool.ObjectPoolAware; 11 | import org.nlab.smtp.pool.SmtpConnectionPool; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import java.util.*; 16 | 17 | /** 18 | * Created by nlabrot on 30/04/15. 19 | */ 20 | public class DefaultClosableSmtpConnection implements ClosableSmtpConnection, ObjectPoolAware { 21 | 22 | private static final Logger LOG = LoggerFactory.getLogger(DefaultClosableSmtpConnection.class); 23 | 24 | private final Transport delegate; 25 | private SmtpConnectionPool objectPool; 26 | private boolean invalidateConnectionOnException; 27 | private boolean invalidateConnectionOnClose; 28 | 29 | private final List transportListeners = new ArrayList<>(); 30 | 31 | public DefaultClosableSmtpConnection(Transport delegate, boolean invalidateConnectionOnException) { 32 | this.delegate = delegate; 33 | this.invalidateConnectionOnException = invalidateConnectionOnException; 34 | } 35 | 36 | @Override 37 | public void invalidate() { 38 | invalidateConnectionOnClose = true; 39 | } 40 | 41 | @Override 42 | public void setInvalidateConnectionOnClose(boolean invalidateConnectionOnClose) { 43 | this.invalidateConnectionOnClose = invalidateConnectionOnClose; 44 | } 45 | 46 | public void sendMessage(MimeMessage msg, Address[] recipients) throws MessagingException { 47 | doSend(msg, recipients); 48 | } 49 | 50 | public void sendMessage(MimeMessage msg) throws MessagingException { 51 | doSend(msg, msg.getAllRecipients()); 52 | } 53 | 54 | public void sendMessages(MimeMessage... msgs) throws MailSendException { 55 | doSend(msgs); 56 | } 57 | 58 | public void addTransportListener(TransportListener l) { 59 | transportListeners.add(l); 60 | delegate.addTransportListener(l); 61 | } 62 | 63 | public void removeTransportListener(TransportListener l) { 64 | transportListeners.remove(l); 65 | delegate.removeTransportListener(l); 66 | } 67 | 68 | 69 | public void clearListeners() { 70 | for (TransportListener transportListener : transportListeners) { 71 | delegate.removeTransportListener(transportListener); 72 | } 73 | transportListeners.clear(); 74 | } 75 | 76 | public boolean isConnected() { 77 | return delegate.isConnected(); 78 | } 79 | 80 | 81 | @Override 82 | public void close() { 83 | if (!invalidateConnectionOnClose) { 84 | objectPool.returnObject(this); 85 | } else { 86 | try { 87 | objectPool.invalidateObject(this); 88 | } catch (Exception e) { 89 | LOG.error("Failed to invalidate object in the pool", e); 90 | } 91 | } 92 | } 93 | 94 | @Override 95 | public void setObjectPool(SmtpConnectionPool objectPool) { 96 | this.objectPool = objectPool; 97 | } 98 | 99 | @Override 100 | public SmtpConnectionPool getObjectPool() { 101 | return objectPool; 102 | } 103 | 104 | @Override 105 | public Transport getDelegate() { 106 | return delegate; 107 | } 108 | 109 | @Override 110 | public Session getSession() { 111 | return objectPool.getSession(); 112 | } 113 | 114 | 115 | private void doSend(MimeMessage mimeMessage, Address[] recipients) throws MessagingException { 116 | 117 | try { 118 | if (mimeMessage.getSentDate() == null) { 119 | mimeMessage.setSentDate(new Date()); 120 | } 121 | String messageId = mimeMessage.getMessageID(); 122 | mimeMessage.saveChanges(); 123 | if (messageId != null) { 124 | // Preserve explicitly specified message id... 125 | mimeMessage.setHeader(HEADER_MESSAGE_ID, messageId); 126 | } 127 | delegate.sendMessage(mimeMessage, recipients); 128 | } catch (Exception e) { 129 | // TODO: An exception can be sent because the recipient is invalid, ie. not because the connection is invalid 130 | // TODO: Invalidate based on the MessagingException subclass / cause: IOException 131 | if (invalidateConnectionOnException) { 132 | invalidate(); 133 | } 134 | throw e; 135 | } 136 | } 137 | 138 | 139 | private void doSend(MimeMessage... mimeMessages) throws MailSendException { 140 | Map failedMessages = new LinkedHashMap<>(); 141 | 142 | for (MimeMessage mimeMessage : mimeMessages) { 143 | 144 | // Send message via current transport... 145 | try { 146 | // doSend takes care to invalidate the connection if needed 147 | doSend(mimeMessage, mimeMessage.getAllRecipients()); 148 | } catch (Exception ex) { 149 | failedMessages.put(mimeMessage, ex); 150 | } 151 | } 152 | 153 | if (!failedMessages.isEmpty()) { 154 | throw new MailSendException(failedMessages); 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/org/nlab/smtp/transport/factory/SmtpConnectionFactories.java: -------------------------------------------------------------------------------- 1 | package org.nlab.smtp.transport.factory; 2 | 3 | import jakarta.mail.Session; 4 | 5 | import java.util.Properties; 6 | 7 | import static org.nlab.smtp.transport.strategy.ConnectionStrategyFactory.newConnectionStrategy; 8 | import static org.nlab.smtp.transport.strategy.TransportStrategyFactory.newSessiontStrategy; 9 | 10 | /** 11 | * {@link SmtpConnectionFactory} factory 12 | */ 13 | public final class SmtpConnectionFactories { 14 | 15 | private SmtpConnectionFactories() { 16 | } 17 | 18 | /** 19 | * Initialize the {@link SmtpConnectionFactory} with a 20 | * {@link Session} initialized to {@code Session.getInstance(new Properties())}, 21 | * {@link org.nlab.smtp.transport.strategy.TransportStrategyFactory#newSessiontStrategy}, 22 | * {@link org.nlab.smtp.transport.strategy.ConnectionStrategyFactory#newConnectionStrategy} 23 | * 24 | * @return 25 | */ 26 | public static SmtpConnectionFactory newSmtpFactory() { 27 | return new SmtpConnectionFactory(Session.getInstance(new Properties()), newSessiontStrategy(), newConnectionStrategy(), false); 28 | } 29 | 30 | /** 31 | * Initialize the {@link SmtpConnectionFactory} using the provided 32 | * {@link Session} and 33 | * {@link org.nlab.smtp.transport.strategy.TransportStrategyFactory#newSessiontStrategy}, 34 | * {@link org.nlab.smtp.transport.strategy.ConnectionStrategyFactory#newConnectionStrategy} 35 | * 36 | * @param session 37 | * @return 38 | */ 39 | public static SmtpConnectionFactory newSmtpFactory(Session session) { 40 | return new SmtpConnectionFactory(session, newSessiontStrategy(), newConnectionStrategy(), false); 41 | } 42 | 43 | 44 | } -------------------------------------------------------------------------------- /src/main/java/org/nlab/smtp/transport/factory/SmtpConnectionFactory.java: -------------------------------------------------------------------------------- 1 | package org.nlab.smtp.transport.factory; 2 | 3 | import jakarta.mail.Session; 4 | import jakarta.mail.Transport; 5 | import jakarta.mail.event.TransportListener; 6 | import org.apache.commons.pool2.PooledObject; 7 | import org.apache.commons.pool2.PooledObjectFactory; 8 | import org.apache.commons.pool2.impl.DefaultPooledObject; 9 | import org.nlab.smtp.transport.connection.ClosableSmtpConnection; 10 | import org.nlab.smtp.transport.connection.DefaultClosableSmtpConnection; 11 | import org.nlab.smtp.transport.strategy.ConnectionStrategy; 12 | import org.nlab.smtp.transport.strategy.TransportStrategy; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import java.util.ArrayList; 17 | import java.util.Collection; 18 | import java.util.Collections; 19 | import java.util.List; 20 | 21 | /** 22 | * A part of the code of this class is taken from the Spring JavaMailSenderImpl class. 23 | */ 24 | public class SmtpConnectionFactory implements PooledObjectFactory { 25 | 26 | private static final Logger LOG = LoggerFactory.getLogger(SmtpConnectionFactory.class); 27 | 28 | protected final Session session; 29 | 30 | protected final TransportStrategy transportFactory; 31 | protected final ConnectionStrategy connectionStrategy; 32 | 33 | protected final boolean invalidateConnectionOnException; 34 | 35 | protected List defaultTransportListeners; 36 | 37 | public SmtpConnectionFactory(Session session, TransportStrategy transportStrategy, ConnectionStrategy connectionStrategy, boolean invalidateConnectionOnException, Collection defaultTransportListeners) { 38 | this.session = session; 39 | this.transportFactory = transportStrategy; 40 | this.connectionStrategy = connectionStrategy; 41 | this.invalidateConnectionOnException = invalidateConnectionOnException; 42 | this.defaultTransportListeners = new ArrayList<>(defaultTransportListeners); 43 | } 44 | 45 | public SmtpConnectionFactory(Session session, TransportStrategy transportFactory, ConnectionStrategy connectionStrategy, boolean invalidateConnectionOnException) { 46 | this(session, transportFactory, connectionStrategy, invalidateConnectionOnException, Collections.emptyList()); 47 | } 48 | 49 | 50 | @Override 51 | public PooledObject makeObject() throws Exception { 52 | LOG.debug("makeObject"); 53 | 54 | Transport transport = transportFactory.getTransport(session); 55 | connectionStrategy.connect(transport); 56 | 57 | DefaultClosableSmtpConnection closableSmtpTransport = new DefaultClosableSmtpConnection(transport, invalidateConnectionOnException); 58 | initDefaultListeners(closableSmtpTransport); 59 | 60 | return new DefaultPooledObject(closableSmtpTransport); 61 | } 62 | 63 | @Override 64 | public void destroyObject(PooledObject pooledObject) { 65 | try { 66 | if (LOG.isDebugEnabled()) { 67 | LOG.debug("destroyObject [{}]", pooledObject.getObject().isConnected()); 68 | } 69 | pooledObject.getObject().clearListeners(); 70 | pooledObject.getObject().getDelegate().close(); 71 | } catch (Exception e) { 72 | LOG.warn(e.getMessage(), e); 73 | } 74 | } 75 | 76 | @Override 77 | public boolean validateObject(PooledObject pooledObject) { 78 | boolean connected = pooledObject.getObject().isConnected(); 79 | LOG.debug("Is connected [{}]", connected); 80 | return connected; 81 | } 82 | 83 | 84 | @Override 85 | public void activateObject(PooledObject pooledObject) throws Exception { 86 | initDefaultListeners(pooledObject.getObject()); 87 | } 88 | 89 | @Override 90 | public void passivateObject(PooledObject pooledObject) throws Exception { 91 | if (LOG.isDebugEnabled()) { 92 | LOG.debug("passivateObject [{}]", pooledObject.getObject().isConnected()); 93 | } 94 | pooledObject.getObject().clearListeners(); 95 | } 96 | 97 | 98 | public List getDefaultListeners() { 99 | return Collections.unmodifiableList(defaultTransportListeners); 100 | } 101 | 102 | public boolean isInvalidateConnectionOnException() { 103 | return invalidateConnectionOnException; 104 | } 105 | 106 | public Session getSession() { 107 | return session; 108 | } 109 | 110 | public TransportStrategy getTransportFactory() { 111 | return transportFactory; 112 | } 113 | 114 | public ConnectionStrategy getConnectionStrategy() { 115 | return connectionStrategy; 116 | } 117 | 118 | private void initDefaultListeners(ClosableSmtpConnection smtpTransport) { 119 | for (TransportListener transportListener : defaultTransportListeners) { 120 | smtpTransport.addTransportListener(transportListener); 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /src/main/java/org/nlab/smtp/transport/factory/SmtpConnectionFactoryBuilder.java: -------------------------------------------------------------------------------- 1 | package org.nlab.smtp.transport.factory; 2 | 3 | import jakarta.mail.Authenticator; 4 | import jakarta.mail.Session; 5 | import jakarta.mail.event.TransportListener; 6 | import org.nlab.smtp.transport.strategy.ConnectionStrategy; 7 | import org.nlab.smtp.transport.strategy.ConnectionStrategyFactory; 8 | import org.nlab.smtp.transport.strategy.TransportStrategy; 9 | import org.nlab.smtp.transport.strategy.TransportStrategyFactory; 10 | 11 | import java.util.Arrays; 12 | import java.util.Collections; 13 | import java.util.List; 14 | import java.util.Properties; 15 | 16 | import static java.util.Objects.requireNonNull; 17 | import static org.nlab.smtp.transport.strategy.ConnectionStrategyFactory.newConnectionStrategy; 18 | import static org.nlab.smtp.transport.strategy.TransportStrategyFactory.newProtocolStrategy; 19 | import static org.nlab.smtp.transport.strategy.TransportStrategyFactory.newSessiontStrategy; 20 | 21 | /** 22 | * A part of the code of this class is taken from the Spring 23 | * JavaMailSenderImpl class. 24 | *

25 | * {@link SmtpConnectionFactory} builder

26 | *

27 | * If no {@link Session} is provided, a default one is created.
28 | * If any of the host , port, username, password properties are provided the factory is initialized with the {@link ConnectionStrategyFactory#newConnectionStrategy(String, int, String, String)} 29 | * otherwise with the {@link ConnectionStrategyFactory#newConnectionStrategy()}
30 | * If the protocol is provided the factory is initialized with the {@link TransportStrategyFactory#newProtocolStrategy} 31 | * otherwise with the {@link TransportStrategyFactory#newSessiontStrategy()} ()}
32 | */ 33 | public class SmtpConnectionFactoryBuilder { 34 | 35 | protected Session session = null; 36 | protected String protocol = null; 37 | protected String host = null; 38 | protected int port = -1; 39 | protected String username; 40 | protected String password; 41 | 42 | protected boolean invalidateConnectionOnException; 43 | 44 | protected List defaultTransportListeners = Collections.emptyList(); 45 | 46 | private SmtpConnectionFactoryBuilder() { 47 | } 48 | 49 | public static SmtpConnectionFactoryBuilder newSmtpBuilder() { 50 | return new SmtpConnectionFactoryBuilder(); 51 | } 52 | 53 | public SmtpConnectionFactoryBuilder session(Properties properties) { 54 | this.session = Session.getInstance(properties); 55 | return this; 56 | } 57 | 58 | public SmtpConnectionFactoryBuilder session(Properties properties, Authenticator authenticator) { 59 | this.session = Session.getInstance(properties, authenticator); 60 | return this; 61 | } 62 | 63 | public SmtpConnectionFactoryBuilder session(Session session) { 64 | this.session = requireNonNull(session); 65 | return this; 66 | } 67 | 68 | public SmtpConnectionFactoryBuilder protocol(String protocol) { 69 | this.protocol = protocol; 70 | return this; 71 | } 72 | 73 | public SmtpConnectionFactoryBuilder host(String host) { 74 | this.host = host; 75 | return this; 76 | } 77 | 78 | public SmtpConnectionFactoryBuilder port(int port) { 79 | this.port = port; 80 | return this; 81 | } 82 | 83 | public SmtpConnectionFactoryBuilder username(String username) { 84 | this.username = username; 85 | return this; 86 | } 87 | 88 | public SmtpConnectionFactoryBuilder password(String password) { 89 | this.password = password; 90 | return this; 91 | } 92 | 93 | public SmtpConnectionFactoryBuilder defaultTransportListeners(TransportListener... listeners) { 94 | defaultTransportListeners = Arrays.asList(requireNonNull(listeners)); 95 | return this; 96 | } 97 | 98 | public SmtpConnectionFactoryBuilder invalidateConnectionOnException(boolean invalidateConnectionOnException) { 99 | this.invalidateConnectionOnException = invalidateConnectionOnException; 100 | return this; 101 | } 102 | 103 | /** 104 | * Build the {@link SmtpConnectionFactory} 105 | * 106 | * @return 107 | */ 108 | public SmtpConnectionFactory build() { 109 | if (session == null) { 110 | session = Session.getInstance(new Properties()); 111 | } 112 | 113 | TransportStrategy transportStrategy = protocol == null ? newSessiontStrategy() : newProtocolStrategy(protocol); 114 | 115 | ConnectionStrategy connectionStrategy; 116 | if (host == null && port == -1 && username == null && password == null) { 117 | connectionStrategy = newConnectionStrategy(); 118 | } else { 119 | connectionStrategy = newConnectionStrategy(host, port, username, password); 120 | } 121 | 122 | return new SmtpConnectionFactory(session, transportStrategy, connectionStrategy, invalidateConnectionOnException, defaultTransportListeners); 123 | } 124 | } -------------------------------------------------------------------------------- /src/main/java/org/nlab/smtp/transport/strategy/ConnectionStrategy.java: -------------------------------------------------------------------------------- 1 | package org.nlab.smtp.transport.strategy; 2 | 3 | import jakarta.mail.MessagingException; 4 | import jakarta.mail.Transport; 5 | 6 | /** 7 | * Connection strategy that abstract {@link Transport#connect} 8 | *

9 | * Created by nlabrot on 04/06/15. 10 | */ 11 | public interface ConnectionStrategy { 12 | 13 | void connect(Transport transport) throws MessagingException; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/nlab/smtp/transport/strategy/ConnectionStrategyFactory.java: -------------------------------------------------------------------------------- 1 | package org.nlab.smtp.transport.strategy; 2 | 3 | import jakarta.mail.MessagingException; 4 | import jakarta.mail.Transport; 5 | 6 | /** 7 | * {@link Transport} supports actually 4 types of connections which are handled by this connection strategy factory 8 | *

    9 | *
  1. {@link Transport#connect()} => {@link #newConnectionStrategy()}
  2. 10 | *
  3. {@link Transport#connect(String, String)} ()} => {@link #newConnectionStrategy(String, String)}
  4. 11 | *
  5. {@link Transport#connect(String, String, String)} ()} => {@link #newConnectionStrategy(String, String, String)}
  6. 12 | *
  7. {@link Transport#connect(String, int, String, String)} ()} => {@link #newConnectionStrategy(String, int, String, String)}
  8. 13 | *
14 | *

15 | * Created by nlabrot on 04/06/15. 16 | */ 17 | public class ConnectionStrategyFactory { 18 | 19 | 20 | public static ConnectionStrategy newConnectionStrategy() { 21 | return new ConnectionStrategy() { 22 | @Override 23 | public void connect(Transport transport) throws MessagingException { 24 | transport.connect(); 25 | } 26 | }; 27 | } 28 | 29 | public static ConnectionStrategy newConnectionStrategy(final String username, final String password) { 30 | return new ConnectionStrategy() { 31 | @Override 32 | public void connect(Transport transport) throws MessagingException { 33 | transport.connect(username, password); 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return "ConnectionStrategy{" + 39 | "username=" + username + 40 | '}'; 41 | } 42 | 43 | }; 44 | } 45 | 46 | public static ConnectionStrategy newConnectionStrategy(final String host, final String username, final String password) { 47 | return new ConnectionStrategy() { 48 | @Override 49 | public void connect(Transport transport) throws MessagingException { 50 | transport.connect(host, username, password); 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return "ConnectionStrategy{" + 56 | "host=" + host + 57 | ", username=" + username + 58 | '}'; 59 | } 60 | }; 61 | } 62 | 63 | public static ConnectionStrategy newConnectionStrategy(final String host, final int port, final String username, final String password) { 64 | return new ConnectionStrategy() { 65 | @Override 66 | public void connect(Transport transport) throws MessagingException { 67 | transport.connect(host, port, username, password); 68 | } 69 | 70 | @Override 71 | public String toString() { 72 | return "ConnectionStrategy{" + 73 | "host=" + host + 74 | ", port=" + port + 75 | ", username=" + username + 76 | '}'; 77 | } 78 | }; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/org/nlab/smtp/transport/strategy/TransportStrategy.java: -------------------------------------------------------------------------------- 1 | package org.nlab.smtp.transport.strategy; 2 | 3 | import jakarta.mail.NoSuchProviderException; 4 | import jakarta.mail.Session; 5 | import jakarta.mail.Transport; 6 | 7 | /** 8 | * Connection strategy that abstract {@link Session#getTransport} 9 | *

10 | *

11 | * Created by nlabrot on 04/06/15. 12 | */ 13 | public interface TransportStrategy { 14 | 15 | Transport getTransport(Session session) throws NoSuchProviderException; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/nlab/smtp/transport/strategy/TransportStrategyFactory.java: -------------------------------------------------------------------------------- 1 | package org.nlab.smtp.transport.strategy; 2 | 3 | import jakarta.mail.*; 4 | 5 | /** 6 | * {@link Session} supports actually 4 types of get transport which are handled by this transport strategy 7 | *

    8 | *
  1. {@link Session#getTransport()} => {@link #newSessiontStrategy()}
  2. 9 | *
  3. {@link Session#getTransport(String)} )} => {@link #newProtocolStrategy(String)}
  4. 10 | *
  5. {@link Session#getTransport(URLName)} ()} => {@link #newUrlNameStrategy(URLName)}
  6. 11 | *
  7. {@link Session#getTransport(Address)} => {@link #newUrlNameStrategy(URLName)}
  8. 12 | *
  9. {@link Session#getTransport(Provider)} => {@link #newProviderStrategy(Provider)}
  10. 13 | *
14 | *

15 | *

16 | * Created by nlabrot on 04/06/15. 17 | */ 18 | public class TransportStrategyFactory { 19 | 20 | public static TransportStrategy newSessiontStrategy() { 21 | return new TransportStrategy() { 22 | @Override 23 | public Transport getTransport(Session session) throws NoSuchProviderException { 24 | return session.getTransport(); 25 | } 26 | }; 27 | } 28 | 29 | public static TransportStrategy newProtocolStrategy(final String protocol) { 30 | return new TransportStrategy() { 31 | @Override 32 | public Transport getTransport(Session session) throws NoSuchProviderException { 33 | return session.getTransport(protocol); 34 | } 35 | }; 36 | } 37 | 38 | public static TransportStrategy newUrlNameStrategy(final URLName urlName) { 39 | return new TransportStrategy() { 40 | @Override 41 | public Transport getTransport(Session session) throws NoSuchProviderException { 42 | return session.getTransport(urlName); 43 | } 44 | }; 45 | } 46 | 47 | public static TransportStrategy newAddressStrategy(final Address address) { 48 | return new TransportStrategy() { 49 | @Override 50 | public Transport getTransport(Session session) throws NoSuchProviderException { 51 | return session.getTransport(address); 52 | } 53 | }; 54 | } 55 | 56 | public static TransportStrategy newProviderStrategy(final Provider provider) { 57 | return new TransportStrategy() { 58 | @Override 59 | public Transport getTransport(Session session) throws NoSuchProviderException { 60 | return session.getTransport(provider); 61 | } 62 | }; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/org/nlab/smtp/AbstractTest.java: -------------------------------------------------------------------------------- 1 | package org.nlab.smtp; 2 | 3 | import com.google.common.base.Stopwatch; 4 | import com.icegreen.greenmail.imap.ImapHostManager; 5 | import com.icegreen.greenmail.util.GreenMail; 6 | import com.icegreen.greenmail.util.ServerSetupTest; 7 | import jakarta.mail.Message; 8 | import jakarta.mail.Session; 9 | import jakarta.mail.internet.MimeMessage; 10 | import org.apache.commons.pool2.impl.GenericObjectPoolConfig; 11 | import org.junit.After; 12 | import org.junit.Before; 13 | import org.nlab.smtp.pool.SmtpConnectionPool; 14 | import org.nlab.smtp.transport.connection.ClosableSmtpConnection; 15 | import org.nlab.smtp.transport.factory.SmtpConnectionFactory; 16 | import org.nlab.smtp.transport.factory.SmtpConnectionFactoryBuilder; 17 | 18 | import java.util.concurrent.TimeUnit; 19 | 20 | /** 21 | * Created by nlabrot on 01/05/15. 22 | */ 23 | public class AbstractTest { 24 | 25 | public static final int PORT = 3025; 26 | public static final int MAX_CONNECTION = 8; 27 | 28 | protected SmtpConnectionPool smtpConnectionPool; 29 | protected SmtpConnectionFactory transportFactory; 30 | 31 | static { 32 | //System.setProperty("org.slf4j.simpleLogger.defaultLogLevel" , "debug"); 33 | } 34 | 35 | protected GreenMail greenMail; 36 | 37 | 38 | public int getMaxTotalConnection() { 39 | return MAX_CONNECTION; 40 | } 41 | 42 | 43 | @Before 44 | public void init() { 45 | GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig(); 46 | genericObjectPoolConfig.setMaxTotal(getMaxTotalConnection()); 47 | genericObjectPoolConfig.setTestOnBorrow(true); 48 | 49 | genericObjectPoolConfig.setMinIdle(0); 50 | genericObjectPoolConfig.setTimeBetweenEvictionRunsMillis(1000); 51 | 52 | 53 | transportFactory = SmtpConnectionFactoryBuilder.newSmtpBuilder().port(PORT).build(); 54 | smtpConnectionPool = new SmtpConnectionPool(transportFactory, genericObjectPoolConfig); 55 | 56 | 57 | startServer(); 58 | } 59 | 60 | @After 61 | public void release() { 62 | smtpConnectionPool.close(); 63 | stopServer(); 64 | } 65 | 66 | protected void startServer() { 67 | greenMail = new GreenMail(ServerSetupTest.SMTP); 68 | greenMail.start(); 69 | } 70 | 71 | protected void stopServer() { 72 | greenMail.stop(); 73 | } 74 | 75 | protected MimeMessage send() throws Exception { 76 | try (ClosableSmtpConnection connection = smtpConnectionPool.borrowObject()) { 77 | MimeMessage mimeMessage = createMessage(connection.getSession(), "nithril@example.com", "nithril@example.com", "foo", "example"); 78 | connection.sendMessage(mimeMessage, mimeMessage.getAllRecipients()); 79 | return mimeMessage; 80 | } 81 | } 82 | 83 | protected MimeMessage createMessage(Session session, String to, String from, String subject, String text) throws Exception { 84 | MimeMessage message = new MimeMessage(session); 85 | message.addRecipients(Message.RecipientType.TO, to); 86 | message.setFrom(from); 87 | message.setSubject(subject); 88 | message.setText(text); 89 | return message; 90 | } 91 | 92 | protected void waitForMessagesCount(int count) throws InterruptedException { 93 | Stopwatch stopwatch = Stopwatch.createStarted(); 94 | while (getImapHostManager().getAllMessages().size() < count 95 | && stopwatch.elapsed(TimeUnit.MILLISECONDS) < 500) { 96 | Thread.sleep(50l); 97 | } 98 | } 99 | 100 | protected ImapHostManager getImapHostManager() { 101 | return greenMail.getManagers().getImapHostManager(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/test/java/org/nlab/smtp/TestListener.java: -------------------------------------------------------------------------------- 1 | package org.nlab.smtp; 2 | 3 | import jakarta.mail.event.TransportAdapter; 4 | import jakarta.mail.event.TransportEvent; 5 | import jakarta.mail.event.TransportListener; 6 | import jakarta.mail.internet.MimeMessage; 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | import org.nlab.smtp.transport.connection.ClosableSmtpConnection; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.util.concurrent.Phaser; 14 | import java.util.concurrent.atomic.AtomicInteger; 15 | 16 | /** 17 | * Created by nlabrot on 29/04/15. 18 | */ 19 | public class TestListener extends AbstractTest { 20 | 21 | private static final Logger LOG = LoggerFactory.getLogger(TestListener.class); 22 | 23 | @Override 24 | public int getMaxTotalConnection() { 25 | return 1; 26 | } 27 | 28 | 29 | @Test 30 | public void testListenerPassivation() throws Exception { 31 | 32 | final Phaser phaser = new Phaser(2); 33 | 34 | final AtomicInteger countDelivered = new AtomicInteger(); 35 | 36 | TransportListener listener = new TransportAdapter() { 37 | @Override 38 | public void messageDelivered(TransportEvent e) { 39 | countDelivered.incrementAndGet(); 40 | phaser.arrive(); 41 | } 42 | }; 43 | 44 | try (ClosableSmtpConnection transport = smtpConnectionPool.borrowObject()) { 45 | transport.addTransportListener(listener); 46 | MimeMessage mimeMessage = createMessage(transport.getSession(), "nithril@example.com", "nithril@example.com", "foo", "example"); 47 | transport.sendMessage(mimeMessage, mimeMessage.getAllRecipients()); 48 | } 49 | 50 | phaser.arriveAndAwaitAdvance(); 51 | 52 | Assert.assertEquals(1, countDelivered.get()); 53 | 54 | try (ClosableSmtpConnection transport = smtpConnectionPool.borrowObject()) { 55 | transport.addTransportListener(listener); 56 | MimeMessage mimeMessage = createMessage(transport.getSession(), "nithril@example.com", "nithril@example.com", "foo", "example"); 57 | transport.sendMessage(mimeMessage, mimeMessage.getAllRecipients()); 58 | } 59 | 60 | phaser.arriveAndAwaitAdvance(); 61 | 62 | Assert.assertEquals(2, countDelivered.get()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/org/nlab/smtp/TestSendException.java: -------------------------------------------------------------------------------- 1 | package org.nlab.smtp; 2 | 3 | import jakarta.mail.MessagingException; 4 | import jakarta.mail.internet.MimeMessage; 5 | import org.apache.commons.pool2.impl.GenericObjectPoolConfig; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | import org.nlab.smtp.exception.MailSendException; 9 | import org.nlab.smtp.pool.SmtpConnectionPool; 10 | import org.nlab.smtp.transport.connection.ClosableSmtpConnection; 11 | import org.nlab.smtp.transport.factory.SmtpConnectionFactoryBuilder; 12 | 13 | public class TestSendException extends AbstractTest { 14 | @Test 15 | public void testReturnedOnException() throws Exception { 16 | try (ClosableSmtpConnection connection = smtpConnectionPool.borrowObject()) { 17 | MimeMessage mimeMessage = createMessage(connection.getSession(), "nithril@example.com", "nithril@example.com", "foo", "example"); 18 | // We stop the server before we actually send the message 19 | stopServer(); 20 | connection.sendMessage(mimeMessage, mimeMessage.getAllRecipients()); 21 | Assert.fail("The connection should fail since the server is stopped"); 22 | } catch (MailSendException | MessagingException e) { 23 | // It should come here, but the connection should not be returned in the pool 24 | } 25 | Assert.assertEquals(1, smtpConnectionPool.getBorrowedCount()); 26 | Assert.assertEquals(0, smtpConnectionPool.getDestroyedCount()); 27 | Assert.assertEquals(1, smtpConnectionPool.getReturnedCount()); 28 | } 29 | 30 | @Test 31 | public void testInvalidateOnException() throws Exception { 32 | GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig(); 33 | genericObjectPoolConfig.setMaxTotal(getMaxTotalConnection()); 34 | genericObjectPoolConfig.setTestOnBorrow(true); 35 | 36 | // We need to instantiate a new factory and pool to set the flag on the factory 37 | transportFactory = SmtpConnectionFactoryBuilder.newSmtpBuilder().port(PORT).invalidateConnectionOnException(true).build(); 38 | smtpConnectionPool = new SmtpConnectionPool(transportFactory, genericObjectPoolConfig); 39 | 40 | try (ClosableSmtpConnection connection = smtpConnectionPool.borrowObject()) { 41 | MimeMessage mimeMessage = createMessage(connection.getSession(), "nithril@example.com", "nithril@example.com", "foo", "example"); 42 | // We stop the server before we actually send the message 43 | stopServer(); 44 | connection.sendMessage(mimeMessage, mimeMessage.getAllRecipients()); 45 | Assert.fail("The connection should fail since the server is stopped"); 46 | } catch (MailSendException | MessagingException e) { 47 | // It should come here, but the connection should not be returned in the pool 48 | } 49 | Assert.assertEquals(1, smtpConnectionPool.getBorrowedCount()); 50 | Assert.assertEquals(1, smtpConnectionPool.getDestroyedCount()); 51 | Assert.assertEquals(0, smtpConnectionPool.getReturnedCount()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/org/nlab/smtp/TestSendMail.java: -------------------------------------------------------------------------------- 1 | package org.nlab.smtp; 2 | 3 | import com.icegreen.greenmail.store.StoredMessage; 4 | import jakarta.mail.internet.MimeMessage; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | import org.nlab.smtp.transport.connection.ClosableSmtpConnection; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.util.List; 12 | import java.util.concurrent.*; 13 | import java.util.concurrent.atomic.AtomicInteger; 14 | 15 | /** 16 | * Created by nlabrot on 29/04/15. 17 | */ 18 | public class TestSendMail extends AbstractTest { 19 | 20 | private static final Logger LOG = LoggerFactory.getLogger(TestSendMail.class); 21 | 22 | public static final int NB_THREAD = 10; 23 | 24 | 25 | @Test 26 | public void testSend() throws Exception { 27 | MimeMessage mimeMessage = send(); 28 | waitForMessagesCount(1); 29 | List allMessages = getImapHostManager().getAllMessages(); 30 | Assert.assertEquals(1, allMessages.size()); 31 | Assert.assertArrayEquals(allMessages.get(0).getMimeMessage().getAllRecipients(), mimeMessage.getAllRecipients()); 32 | } 33 | 34 | 35 | @Test 36 | public void testConcurrentSend() throws Exception { 37 | final AtomicInteger counter = new AtomicInteger(); 38 | 39 | ExecutorService executorService = Executors.newFixedThreadPool(NB_THREAD); 40 | 41 | for (int i = 0; i < NB_THREAD; i++) { 42 | executorService.submit(new Callable() { 43 | @Override 44 | public Object call() throws Exception { 45 | for (int m = 0; m < 200; m++) { 46 | send(); 47 | counter.incrementAndGet(); 48 | } 49 | return null; 50 | } 51 | }); 52 | } 53 | executorService.shutdown(); 54 | executorService.awaitTermination(10, TimeUnit.SECONDS); 55 | Assert.assertEquals(NB_THREAD * 200, counter.get()); 56 | Assert.assertEquals(8, smtpConnectionPool.getCreatedCount()); 57 | 58 | waitForMessagesCount(NB_THREAD * 200); 59 | Assert.assertEquals(NB_THREAD * 200, getImapHostManager().getAllMessages().size()); 60 | 61 | } 62 | 63 | 64 | @Test 65 | public void testSendAfterServerStopStart() throws Exception { 66 | final AtomicInteger counter = new AtomicInteger(); 67 | 68 | ExecutorService executorService = Executors.newFixedThreadPool(1000); 69 | 70 | final CountDownLatch countDownLatch = new CountDownLatch(10); 71 | 72 | 73 | for (int i = 0; i < NB_THREAD; i++) { 74 | executorService.submit(new Callable() { 75 | @Override 76 | public Object call() throws Exception { 77 | for (int m = 0; m < 10; m++) { 78 | send(); 79 | counter.incrementAndGet(); 80 | } 81 | countDownLatch.countDown(); 82 | return null; 83 | } 84 | }); 85 | } 86 | 87 | countDownLatch.await(); 88 | 89 | waitForMessagesCount(NB_THREAD * 10); 90 | Assert.assertEquals(NB_THREAD * 10, greenMail.getReceivedMessages().length); 91 | 92 | 93 | stopServer(); 94 | startServer(); 95 | 96 | for (int i = 0; i < NB_THREAD; i++) { 97 | executorService.submit(new Callable() { 98 | @Override 99 | public Object call() throws Exception { 100 | for (int m = 0; m < 10; m++) { 101 | send(); 102 | counter.incrementAndGet(); 103 | } 104 | return null; 105 | } 106 | }); 107 | } 108 | 109 | waitForMessagesCount(NB_THREAD * 10); 110 | Assert.assertEquals(NB_THREAD * 10, greenMail.getReceivedMessages().length); 111 | 112 | executorService.shutdown(); 113 | executorService.awaitTermination(10, TimeUnit.SECONDS); 114 | Assert.assertEquals(2 * NB_THREAD * 10, counter.get()); 115 | 116 | } 117 | 118 | @Test 119 | public void testSend_Batch() throws Exception { 120 | try (ClosableSmtpConnection connection = smtpConnectionPool.borrowObject()) { 121 | 122 | MimeMessage mimeMessage1 = createMessage(connection.getSession(), "foo1@example.com", "foo@example.com", "foo", "example"); 123 | MimeMessage mimeMessage2 = createMessage(connection.getSession(), "foo2@example.com", "foo@example.com", "foo", "example"); 124 | 125 | connection.sendMessages(mimeMessage1, mimeMessage2); 126 | 127 | waitForMessagesCount(2); 128 | 129 | List allMessages = getImapHostManager().getAllMessages(); 130 | Assert.assertEquals(2, allMessages.size()); 131 | 132 | Assert.assertArrayEquals(allMessages.get(0).getMimeMessage().getAllRecipients(), mimeMessage1.getAllRecipients()); 133 | Assert.assertArrayEquals(allMessages.get(1).getMimeMessage().getAllRecipients(), mimeMessage2.getAllRecipients()); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/test/java/org/nlab/smtp/TestSendMailBench.java: -------------------------------------------------------------------------------- 1 | package org.nlab.smtp; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Ignore; 5 | import org.junit.Test; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.util.concurrent.Callable; 10 | import java.util.concurrent.ExecutorService; 11 | import java.util.concurrent.Executors; 12 | import java.util.concurrent.TimeUnit; 13 | import java.util.concurrent.atomic.AtomicInteger; 14 | 15 | /** 16 | * Created by nlabrot on 29/04/15. 17 | */ 18 | public class TestSendMailBench extends AbstractTest { 19 | 20 | private static final Logger LOG = LoggerFactory.getLogger(TestSendMailBench.class); 21 | 22 | public static final int NB_THREAD = 100; 23 | 24 | 25 | @Test 26 | @Ignore 27 | public void testSend() throws Exception { 28 | final AtomicInteger counter = new AtomicInteger(); 29 | 30 | ExecutorService executorService = Executors.newFixedThreadPool(NB_THREAD); 31 | 32 | for (int i = 0; i < NB_THREAD; i++) { 33 | executorService.submit(new Callable() { 34 | @Override 35 | public Object call() throws Exception { 36 | for (int m = 0; m < 20000; m++) { 37 | TestSendMailBench.this.send(); 38 | counter.incrementAndGet(); 39 | 40 | if (counter.get() % 1000 == 0) { 41 | System.out.println(counter.get()); 42 | } 43 | } 44 | return null; 45 | } 46 | }); 47 | } 48 | executorService.shutdown(); 49 | executorService.awaitTermination(1000, TimeUnit.SECONDS); 50 | Assert.assertEquals(NB_THREAD * 200, counter.get()); 51 | Assert.assertEquals(8, smtpConnectionPool.getCreatedCount()); 52 | } 53 | 54 | 55 | } 56 | --------------------------------------------------------------------------------