io.vertx.ext.mail.impl.SMTPConnectionPool Maven / Gradle / Ivy
/*
* Copyright (c) 2011-2015 The original author or authors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.vertx.ext.mail.impl;
import io.vertx.core.AsyncResult;
import io.vertx.core.CompositeFuture;
import io.vertx.core.Context;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import io.vertx.core.impl.ContextInternal;
import io.vertx.core.impl.logging.Logger;
import io.vertx.core.impl.logging.LoggerFactory;
import io.vertx.core.net.NetClient;
import io.vertx.core.net.impl.pool.Lease;
import io.vertx.ext.auth.PRNG;
import io.vertx.ext.mail.MailConfig;
import io.vertx.ext.mail.StartTLSOptions;
import io.vertx.ext.mail.impl.sasl.AuthOperationFactory;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
class SMTPConnectionPool {
private static final Logger log = LoggerFactory.getLogger(SMTPConnectionPool.class);
// max retry times if RSET failed when choosing an existed connection in pool, default to 5.
private static final int RSET_MAX_RETRY = Integer.getInteger("vertx.mail.rset.max.retry", 5);
private final PRNG prng;
private final AuthOperationFactory authOperationFactory;
private final Vertx vertx;
private final NetClient netClient;
private final MailConfig config;
private boolean closed = false;
private final AtomicReference endPoint = new AtomicReference<>();
private long timerID = -1;
SMTPConnectionPool(Vertx vertx, MailConfig config) {
this.vertx = vertx;
this.config = config;
// If the hostname verification isn't set yet, but we are configured to use SSL, update that now
String verification = config.getHostnameVerificationAlgorithm();
if ((verification == null || verification.isEmpty()) && !config.isTrustAll() &&
(config.isSsl() || config.getStarttls() != StartTLSOptions.DISABLED)) {
// we can use HTTPS verification, which matches the requirements for SMTPS
config.setHostnameVerificationAlgorithm("HTTPS");
} else {
config.setHostnameVerificationAlgorithm("");
}
netClient = vertx.createNetClient(config);
this.prng = new PRNG(vertx);
this.authOperationFactory = new AuthOperationFactory(prng);
if (config.getPoolCleanerPeriod() > 0 && config.isKeepAlive() && config.getKeepAliveTimeout() > 0) {
timerID = vertx.setTimer(poolCleanTimeout(config), this::checkExpired);
}
}
private static long poolCleanTimeout(MailConfig config) {
return config.getPoolCleanerPeriodUnit().toMillis(config.getPoolCleanerPeriod());
}
private void checkExpired(long timer) {
getSMTPEndPoint().checkExpired(ar -> {
if (ar.succeeded()) {
ar.result().forEach(c -> c.quitCloseConnection(Promise.promise()));
}
});
synchronized (this) {
if (!closed) {
timerID = vertx.setTimer(poolCleanTimeout(config), this::checkExpired);
}
}
}
AuthOperationFactory getAuthOperationFactory() {
return authOperationFactory;
}
void getConnection(String hostname, Handler> resultHandler) {
getConnection(hostname, vertx.getOrCreateContext(), resultHandler);
}
void getConnection(String hostname, Context ctx, Handler> resultHandler) {
getConnection0(hostname, ctx, resultHandler, 0);
}
private void getConnection0(String hostname, Context ctx, Handler> resultHandler, final int i) {
synchronized (this) {
if (closed) {
resultHandler.handle(Future.failedFuture("connection pool is closed"));
return;
}
}
ContextInternal contextInternal = (ContextInternal)ctx;
Promise> promise = contextInternal.promise();
promise.future()
.map(l -> l.get().setLease(l)).flatMap(conn -> {
final Future future;
final boolean reset;
conn.setInUse();
if (conn.isInitialized()) {
reset = true;
Promise connReset = contextInternal.promise();
new SMTPReset(conn, connReset).start();
future = connReset.future();
} else {
reset = false;
Promise connInitial = contextInternal.promise();
SMTPStarter starter = new SMTPStarter(conn, this.config, hostname, authOperationFactory, connInitial);
try {
conn.init(starter::serverGreeting);
} catch (Exception e) {
connInitial.handle(Future.failedFuture(e));
}
future = connInitial.future();
}
return future.recover(t -> {
// close the connection as it failed either in rset or handshake
Promise quitPromise = contextInternal.promise();
if (t instanceof IOException) {
conn.shutdown();
quitPromise.fail(t);
} else {
conn.quitCloseConnection(quitPromise);
}
return quitPromise.future().transform(v -> {
if (reset && i < RSET_MAX_RETRY) {
Promise getConnAgain = contextInternal.promise();
log.debug("Failed on RSET, try " + (i + 1) + " time");
getConnection0(hostname, ctx, getConnAgain, i + 1);
return getConnAgain.future();
}
conn.shutdown();
return Future.failedFuture(t);
});
});
}).onComplete(resultHandler);
getSMTPEndPoint().getConnection(contextInternal, config.getConnectTimeout(), promise);
}
private SMTPEndPoint getSMTPEndPoint() {
return endPoint.accumulateAndGet(endPoint.get(), (p, n) -> p == null ? new SMTPEndPoint(netClient, config, () -> endPoint.set(null)) : p);
}
public void close() {
close(h -> {
if (h.failed()) {
log.warn("Failed to close the pool", h.cause());
}
log.debug("SMTP connection pool closed.");
});
}
void close(Handler> finishedHandler) {
log.debug("trying to close the connection pool");
synchronized (this) {
if (closed) {
throw new IllegalStateException("pool is already closed");
}
closed = true;
if (timerID >= 0) {
vertx.cancelTimer(timerID);
timerID = -1;
}
}
this.prng.close();
Promise>> closePromise = Promise.promise();
closePromise.future()
.flatMap(list -> {
List futures = list.stream()
.filter(connFuture -> connFuture.succeeded() && connFuture.result().isAvailable())
.map(connFuture -> {
Promise promise = Promise.promise();
connFuture.result().close(promise);
return promise.future();
})
.collect(Collectors.toList());
return CompositeFuture.all(futures);
})
.flatMap(f -> this.netClient.close())
.onComplete(r -> {
log.debug("Close net client");
if (r.succeeded()) {
if (finishedHandler != null) {
finishedHandler.handle(Future.succeededFuture());
}
} else {
if (finishedHandler != null) {
finishedHandler.handle(Future.failedFuture(r.cause()));
}
}
});
getSMTPEndPoint().close(closePromise);
}
int connCount() {
return getSMTPEndPoint().size();
}
NetClient getNetClient() {
return this.netClient;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy