|
@@ -20,12 +20,10 @@
|
|
|
|
|
|
package password.pwm.svc.email;
|
|
package password.pwm.svc.email;
|
|
|
|
|
|
-import password.pwm.AppProperty;
|
|
|
|
import password.pwm.PwmApplication;
|
|
import password.pwm.PwmApplication;
|
|
import password.pwm.PwmApplicationMode;
|
|
import password.pwm.PwmApplicationMode;
|
|
import password.pwm.bean.EmailItemBean;
|
|
import password.pwm.bean.EmailItemBean;
|
|
import password.pwm.config.Configuration;
|
|
import password.pwm.config.Configuration;
|
|
-import password.pwm.config.PwmSetting;
|
|
|
|
import password.pwm.config.option.DataStorageMethod;
|
|
import password.pwm.config.option.DataStorageMethod;
|
|
import password.pwm.error.ErrorInformation;
|
|
import password.pwm.error.ErrorInformation;
|
|
import password.pwm.error.PwmError;
|
|
import password.pwm.error.PwmError;
|
|
@@ -38,8 +36,10 @@ import password.pwm.ldap.UserInfo;
|
|
import password.pwm.svc.PwmService;
|
|
import password.pwm.svc.PwmService;
|
|
import password.pwm.svc.stats.Statistic;
|
|
import password.pwm.svc.stats.Statistic;
|
|
import password.pwm.svc.stats.StatisticsManager;
|
|
import password.pwm.svc.stats.StatisticsManager;
|
|
-import password.pwm.util.java.AtomicLoopIntIncrementer;
|
|
|
|
|
|
+import password.pwm.util.java.ConditionalTaskExecutor;
|
|
import password.pwm.util.java.JavaHelper;
|
|
import password.pwm.util.java.JavaHelper;
|
|
|
|
+import password.pwm.util.java.JsonUtil;
|
|
|
|
+import password.pwm.util.java.StatisticIntCounterMap;
|
|
import password.pwm.util.java.StringUtil;
|
|
import password.pwm.util.java.StringUtil;
|
|
import password.pwm.util.java.TimeDuration;
|
|
import password.pwm.util.java.TimeDuration;
|
|
import password.pwm.util.localdb.LocalDB;
|
|
import password.pwm.util.localdb.LocalDB;
|
|
@@ -54,14 +54,10 @@ import javax.mail.Transport;
|
|
import java.time.Instant;
|
|
import java.time.Instant;
|
|
import java.util.ArrayList;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.Collections;
|
|
-import java.util.HashMap;
|
|
|
|
-import java.util.HashSet;
|
|
|
|
-import java.util.LinkedHashMap;
|
|
|
|
import java.util.List;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Map;
|
|
-import java.util.Set;
|
|
|
|
-import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
-import java.util.concurrent.atomic.AtomicInteger;
|
|
|
|
|
|
+import java.util.TreeMap;
|
|
|
|
+import java.util.concurrent.atomic.AtomicReference;
|
|
|
|
|
|
/**
|
|
/**
|
|
* @author Jason D. Rivard
|
|
* @author Jason D. Rivard
|
|
@@ -71,23 +67,26 @@ public class EmailService implements PwmService
|
|
private static final PwmLogger LOGGER = PwmLogger.forClass( EmailService.class );
|
|
private static final PwmLogger LOGGER = PwmLogger.forClass( EmailService.class );
|
|
|
|
|
|
private PwmApplication pwmApplication;
|
|
private PwmApplication pwmApplication;
|
|
- private final Map<EmailServer, ErrorInformation> serverErrors = new ConcurrentHashMap<>( );
|
|
|
|
private ErrorInformation startupError;
|
|
private ErrorInformation startupError;
|
|
- private final List<EmailServer> servers = new ArrayList<>( );
|
|
|
|
private WorkQueueProcessor<EmailItemBean> workQueueProcessor;
|
|
private WorkQueueProcessor<EmailItemBean> workQueueProcessor;
|
|
- private AtomicLoopIntIncrementer serverIncrementer;
|
|
|
|
- private Set<Integer> retryableStatusResponses = Collections.emptySet();
|
|
|
|
|
|
+ private EmailServiceSettings emailServiceSettings;
|
|
|
|
+ private EmailConnectionPool connectionPool;
|
|
|
|
|
|
- private PwmService.STATUS status = STATUS.NEW;
|
|
|
|
|
|
+ private final AtomicReference<ErrorInformation> lastSendError = new AtomicReference<>();
|
|
|
|
+
|
|
|
|
+ private final ConditionalTaskExecutor statsLogger = ConditionalTaskExecutor.forPeriodicTask( this::logStats, TimeDuration.MINUTE );
|
|
|
|
|
|
- private final ThreadLocal<EmailConnection> threadLocalTransport = new ThreadLocal<>();
|
|
|
|
|
|
+ private PwmService.STATUS status = STATUS.NEW;
|
|
|
|
|
|
public void init( final PwmApplication pwmApplication )
|
|
public void init( final PwmApplication pwmApplication )
|
|
throws PwmException
|
|
throws PwmException
|
|
{
|
|
{
|
|
status = STATUS.OPENING;
|
|
status = STATUS.OPENING;
|
|
this.pwmApplication = pwmApplication;
|
|
this.pwmApplication = pwmApplication;
|
|
|
|
+ this.emailServiceSettings = EmailServiceSettings.fromConfiguration( pwmApplication.getConfig() );
|
|
|
|
+ LOGGER.trace( () -> "initializing with settings: " + JsonUtil.serialize( emailServiceSettings ) );
|
|
|
|
|
|
|
|
+ final List<EmailServer> servers = new ArrayList<>();
|
|
try
|
|
try
|
|
{
|
|
{
|
|
servers.addAll( EmailServerUtil.makeEmailServersMap( pwmApplication.getConfig() ) );
|
|
servers.addAll( EmailServerUtil.makeEmailServersMap( pwmApplication.getConfig() ) );
|
|
@@ -107,8 +106,6 @@ public class EmailService implements PwmService
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
- serverIncrementer = AtomicLoopIntIncrementer.builder().ceiling( servers.size() - 1 ).build();
|
|
|
|
-
|
|
|
|
if ( pwmApplication.getLocalDB() == null || pwmApplication.getLocalDB().status() != LocalDB.Status.OPEN )
|
|
if ( pwmApplication.getLocalDB() == null || pwmApplication.getLocalDB().status() != LocalDB.Status.OPEN )
|
|
{
|
|
{
|
|
LOGGER.warn( () -> "localdb is not open, EmailService will remain closed" );
|
|
LOGGER.warn( () -> "localdb is not open, EmailService will remain closed" );
|
|
@@ -116,22 +113,23 @@ public class EmailService implements PwmService
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ LOGGER.debug( () -> "starting with settings: " + JsonUtil.serialize( emailServiceSettings ) );
|
|
|
|
+
|
|
final WorkQueueProcessor.Settings settings = WorkQueueProcessor.Settings.builder()
|
|
final WorkQueueProcessor.Settings settings = WorkQueueProcessor.Settings.builder()
|
|
- .maxEvents( Integer.parseInt( pwmApplication.getConfig().readAppProperty( AppProperty.QUEUE_EMAIL_MAX_COUNT ) ) )
|
|
|
|
- .retryDiscardAge( TimeDuration.of( pwmApplication.getConfig().readSettingAsLong( PwmSetting.EMAIL_MAX_QUEUE_AGE ), TimeDuration.Unit.SECONDS ) )
|
|
|
|
- .retryInterval( TimeDuration.of(
|
|
|
|
- Long.parseLong( pwmApplication.getConfig().readAppProperty( AppProperty.QUEUE_EMAIL_RETRY_TIMEOUT_MS ) ),
|
|
|
|
- TimeDuration.Unit.MILLISECONDS )
|
|
|
|
- )
|
|
|
|
- .preThreads( Integer.parseInt( pwmApplication.getConfig().readAppProperty( AppProperty.QUEUE_EMAIL_MAX_THREADS ) ) )
|
|
|
|
|
|
+ .maxEvents( emailServiceSettings.getQueueMaxItems() )
|
|
|
|
+ .retryDiscardAge( emailServiceSettings.getQueueDiscardAge() )
|
|
|
|
+ .retryInterval( emailServiceSettings.getQueueRetryTimeout() )
|
|
|
|
+ .preThreads( emailServiceSettings.getMaxThreads() )
|
|
.build();
|
|
.build();
|
|
final LocalDBStoredQueue localDBStoredQueue = LocalDBStoredQueue.createLocalDBStoredQueue( pwmApplication, pwmApplication.getLocalDB(), LocalDB.DB.EMAIL_QUEUE );
|
|
final LocalDBStoredQueue localDBStoredQueue = LocalDBStoredQueue.createLocalDBStoredQueue( pwmApplication, pwmApplication.getLocalDB(), LocalDB.DB.EMAIL_QUEUE );
|
|
|
|
|
|
workQueueProcessor = new WorkQueueProcessor<>( pwmApplication, localDBStoredQueue, settings, new EmailItemProcessor(), this.getClass() );
|
|
workQueueProcessor = new WorkQueueProcessor<>( pwmApplication, localDBStoredQueue, settings, new EmailItemProcessor(), this.getClass() );
|
|
|
|
|
|
- retryableStatusResponses = readRetryableStatusCodes( pwmApplication.getConfig() );
|
|
|
|
|
|
+ connectionPool = new EmailConnectionPool( servers, emailServiceSettings );
|
|
|
|
|
|
status = STATUS.OPEN;
|
|
status = STATUS.OPEN;
|
|
|
|
+
|
|
|
|
+ statsLogger.conditionallyExecuteTask();
|
|
}
|
|
}
|
|
|
|
|
|
public void close( )
|
|
public void close( )
|
|
@@ -140,6 +138,12 @@ public class EmailService implements PwmService
|
|
if ( workQueueProcessor != null )
|
|
if ( workQueueProcessor != null )
|
|
{
|
|
{
|
|
workQueueProcessor.close();
|
|
workQueueProcessor.close();
|
|
|
|
+ workQueueProcessor = null;
|
|
|
|
+ }
|
|
|
|
+ if ( connectionPool != null )
|
|
|
|
+ {
|
|
|
|
+ connectionPool.close();
|
|
|
|
+ connectionPool = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -167,24 +171,25 @@ public class EmailService implements PwmService
|
|
}
|
|
}
|
|
|
|
|
|
final List<HealthRecord> records = new ArrayList<>( );
|
|
final List<HealthRecord> records = new ArrayList<>( );
|
|
- final Map<EmailServer, ErrorInformation> localMap = new HashMap<>( serverErrors );
|
|
|
|
- for ( final Map.Entry<EmailServer, ErrorInformation> entry : localMap.entrySet() )
|
|
|
|
{
|
|
{
|
|
- final ErrorInformation errorInformation = entry.getValue();
|
|
|
|
- records.add( HealthRecord.forMessage( HealthMessage.Email_SendFailure, errorInformation.toDebugStr() ) );
|
|
|
|
|
|
+ final ErrorInformation lastError = lastSendError.get();
|
|
|
|
+ if ( lastError != null )
|
|
|
|
+ {
|
|
|
|
+ records.add( HealthRecord.forMessage( HealthMessage.Email_SendFailure, lastError.toDebugStr() ) );
|
|
|
|
+
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ records.addAll( EmailServerUtil.checkAllConfiguredServers( connectionPool.getServers() ) );
|
|
|
|
+
|
|
return Collections.unmodifiableList( records );
|
|
return Collections.unmodifiableList( records );
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
public ServiceInfoBean serviceInfo( )
|
|
public ServiceInfoBean serviceInfo( )
|
|
{
|
|
{
|
|
- final Map<String, String> debugItems = new LinkedHashMap<>();
|
|
|
|
- if ( workQueueProcessor != null )
|
|
|
|
- {
|
|
|
|
- debugItems.putAll( workQueueProcessor.debugInfo() );
|
|
|
|
- }
|
|
|
|
|
|
+ final Map<String, String> debugItems = stats();
|
|
|
|
+
|
|
if ( status() == STATUS.OPEN )
|
|
if ( status() == STATUS.OPEN )
|
|
{
|
|
{
|
|
return new ServiceInfoBean( Collections.singletonList( DataStorageMethod.LOCALDB ), debugItems );
|
|
return new ServiceInfoBean( Collections.singletonList( DataStorageMethod.LOCALDB ), debugItems );
|
|
@@ -223,11 +228,50 @@ public class EmailService implements PwmService
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ private void logStats()
|
|
|
|
+ {
|
|
|
|
+ LOGGER.trace( () -> "stats: " + StringUtil.mapToString( stats() ) );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ private Map<String, String> stats()
|
|
|
|
+ {
|
|
|
|
+ final Map<String, String> stats = new TreeMap<>( );
|
|
|
|
+ if ( workQueueProcessor != null )
|
|
|
|
+ {
|
|
|
|
+ stats.putAll( workQueueProcessor.debugInfo() );
|
|
|
|
+ stats.put( "workQueueSize", String.valueOf( workQueueProcessor.queueSize() ) );
|
|
|
|
+ }
|
|
|
|
+ if ( connectionPool != null )
|
|
|
|
+ {
|
|
|
|
+ stats.put( "idleConnections", Integer.toString( connectionPool.idleConnectionCount() ) );
|
|
|
|
+ stats.put( "activeConnections", Integer.toString( connectionPool.activeConnectionCount() ) );
|
|
|
|
+
|
|
|
|
+ for ( final EmailServer emailServer : connectionPool.getServers() )
|
|
|
|
+ {
|
|
|
|
+ final StatisticIntCounterMap<EmailServer.ServerStat> serverStats = emailServer.getConnectionStats();
|
|
|
|
+ for ( final EmailServer.ServerStat serverStat : EmailServer.ServerStat.values() )
|
|
|
|
+ {
|
|
|
|
+ final String name = serverStat.name() + "[" + emailServer.getId() + "]";
|
|
|
|
+ stats.put( name, String.valueOf( serverStats.get( serverStat ) ) );
|
|
|
|
+ }
|
|
|
|
+ {
|
|
|
|
+ final String name = "averageSendTime[" + emailServer.getId() + "]";
|
|
|
|
+ final TimeDuration value = TimeDuration.of( ( long ) emailServer.getAverageSendTime().getAverage(), TimeDuration.Unit.MILLISECONDS );
|
|
|
|
+ stats.put( name, value.asCompactString() );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return Collections.unmodifiableMap( stats );
|
|
|
|
+ }
|
|
|
|
+
|
|
private boolean determineIfItemCanBeDelivered( final EmailItemBean emailItem )
|
|
private boolean determineIfItemCanBeDelivered( final EmailItemBean emailItem )
|
|
{
|
|
{
|
|
- if ( servers.isEmpty() )
|
|
|
|
|
|
+ if ( status() != STATUS.OPEN )
|
|
{
|
|
{
|
|
- LOGGER.debug( () -> "discarding email send event, no email servers configured" );
|
|
|
|
|
|
+ LOGGER.debug( () -> "discarding email send event, no service is not running" );
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -306,6 +350,8 @@ public class EmailService implements PwmService
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ checkIfServiceIsOpen();
|
|
|
|
+
|
|
final EmailItemBean finalBean;
|
|
final EmailItemBean finalBean;
|
|
{
|
|
{
|
|
EmailItemBean workingItemBean = emailItem;
|
|
EmailItemBean workingItemBean = emailItem;
|
|
@@ -347,6 +393,17 @@ public class EmailService implements PwmService
|
|
{
|
|
{
|
|
LOGGER.warn( () -> "unable to add email to queue: " + e.getMessage() );
|
|
LOGGER.warn( () -> "unable to add email to queue: " + e.getMessage() );
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ statsLogger.conditionallyExecuteTask();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void checkIfServiceIsOpen()
|
|
|
|
+ throws PwmUnrecoverableException
|
|
|
|
+ {
|
|
|
|
+ if ( !STATUS.OPEN.equals( status ) )
|
|
|
|
+ {
|
|
|
|
+ throw new PwmUnrecoverableException( PwmError.ERROR_SERVICE_NOT_AVAILABLE, "email service is closed and will not accent new jobs" );
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
public static void sendEmailSynchronous(
|
|
public static void sendEmailSynchronous(
|
|
@@ -379,21 +436,6 @@ public class EmailService implements PwmService
|
|
transport.close();
|
|
transport.close();
|
|
}
|
|
}
|
|
|
|
|
|
- private final AtomicInteger newThreadLocalTransport = new AtomicInteger();
|
|
|
|
- private final AtomicInteger useExistingConnection = new AtomicInteger();
|
|
|
|
- private final AtomicInteger useExistingTransport = new AtomicInteger();
|
|
|
|
- private final AtomicInteger newConnectionCounter = new AtomicInteger();
|
|
|
|
-
|
|
|
|
- private String stats( )
|
|
|
|
- {
|
|
|
|
- final Map<String, Integer> map = new HashMap<>();
|
|
|
|
- map.put( "newThreadLocalTransport", newThreadLocalTransport.get() );
|
|
|
|
- map.put( "useExistingConnection", newThreadLocalTransport.get() );
|
|
|
|
- map.put( "useExistingTransport", useExistingTransport.get() );
|
|
|
|
- map.put( "newConnectionCounter", newConnectionCounter.get() );
|
|
|
|
- return StringUtil.mapToString( map );
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
private WorkQueueProcessor.ProcessResult sendItem( final EmailItemBean emailItemBean )
|
|
private WorkQueueProcessor.ProcessResult sendItem( final EmailItemBean emailItemBean )
|
|
{
|
|
{
|
|
try
|
|
try
|
|
@@ -403,7 +445,7 @@ public class EmailService implements PwmService
|
|
}
|
|
}
|
|
catch ( final MessagingException | PwmException e )
|
|
catch ( final MessagingException | PwmException e )
|
|
{
|
|
{
|
|
- if ( EmailServerUtil.examineSendFailure( e, retryableStatusResponses ) )
|
|
|
|
|
|
+ if ( EmailServerUtil.examineSendFailure( e, emailServiceSettings.getRetryableStatusResponses() ) )
|
|
{
|
|
{
|
|
LOGGER.error( () -> "error sending email (" + e.getMessage() + ") " + emailItemBean.toDebugString() + ", will retry" );
|
|
LOGGER.error( () -> "error sending email (" + e.getMessage() + ") " + emailItemBean.toDebugString() + ", will retry" );
|
|
StatisticsManager.incrementStat( pwmApplication, Statistic.EMAIL_SEND_FAILURES );
|
|
StatisticsManager.incrementStat( pwmApplication, Statistic.EMAIL_SEND_FAILURES );
|
|
@@ -418,59 +460,37 @@ public class EmailService implements PwmService
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+
|
|
private void executeEmailSend( final EmailItemBean emailItemBean )
|
|
private void executeEmailSend( final EmailItemBean emailItemBean )
|
|
throws PwmUnrecoverableException, MessagingException
|
|
throws PwmUnrecoverableException, MessagingException
|
|
{
|
|
{
|
|
- EmailConnection serverTransport = null;
|
|
|
|
|
|
+ final Instant startTime = Instant.now();
|
|
|
|
+ EmailConnection emailConnection = null;
|
|
|
|
|
|
try
|
|
try
|
|
{
|
|
{
|
|
- // create a new MimeMessage object (using the Session created above)
|
|
|
|
- if ( threadLocalTransport.get() == null )
|
|
|
|
- {
|
|
|
|
-
|
|
|
|
- LOGGER.trace( () -> "initializing new threadLocal transport, stats: " + stats() );
|
|
|
|
- threadLocalTransport.set( getSmtpTransport( ) );
|
|
|
|
- newThreadLocalTransport.getAndIncrement();
|
|
|
|
- }
|
|
|
|
- else
|
|
|
|
- {
|
|
|
|
- LOGGER.trace( () -> "using existing threadLocal transport, stats: " + stats() );
|
|
|
|
- useExistingTransport.getAndIncrement();
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- serverTransport = threadLocalTransport.get();
|
|
|
|
-
|
|
|
|
- if ( !serverTransport.getTransport().isConnected() )
|
|
|
|
- {
|
|
|
|
- LOGGER.trace( () -> "connecting threadLocal transport, stats: " + stats() );
|
|
|
|
- threadLocalTransport.set( getSmtpTransport( ) );
|
|
|
|
- serverTransport = threadLocalTransport.get();
|
|
|
|
- newConnectionCounter.getAndIncrement();
|
|
|
|
- }
|
|
|
|
- else
|
|
|
|
- {
|
|
|
|
- LOGGER.trace( () -> "using existing threadLocal: stats: " + stats() );
|
|
|
|
- useExistingConnection.getAndIncrement();
|
|
|
|
- }
|
|
|
|
|
|
+ emailConnection = connectionPool.getConnection();
|
|
|
|
|
|
final List<Message> messages = EmailServerUtil.convertEmailItemToMessages(
|
|
final List<Message> messages = EmailServerUtil.convertEmailItemToMessages(
|
|
emailItemBean,
|
|
emailItemBean,
|
|
this.pwmApplication.getConfig(),
|
|
this.pwmApplication.getConfig(),
|
|
- serverTransport.getEmailServer()
|
|
|
|
|
|
+ emailConnection.getEmailServer()
|
|
);
|
|
);
|
|
|
|
|
|
for ( final Message message : messages )
|
|
for ( final Message message : messages )
|
|
{
|
|
{
|
|
message.saveChanges();
|
|
message.saveChanges();
|
|
- serverTransport.getTransport().sendMessage( message, message.getAllRecipients() );
|
|
|
|
|
|
+ emailConnection.getTransport().sendMessage( message, message.getAllRecipients() );
|
|
}
|
|
}
|
|
|
|
|
|
- serverErrors.remove( serverTransport.getEmailServer() );
|
|
|
|
|
|
+ emailConnection.incrementSentItems();
|
|
|
|
+ emailConnection.getEmailServer().getConnectionStats().increment( EmailServer.ServerStat.sendCount );
|
|
|
|
+ final TimeDuration sendTime = TimeDuration.fromCurrent( startTime );
|
|
|
|
+ emailConnection.getEmailServer().getAverageSendTime().update( sendTime.asMillis() );
|
|
|
|
+ lastSendError.set( null );
|
|
|
|
|
|
- LOGGER.debug( () -> "sent email: " + emailItemBean.toDebugString() );
|
|
|
|
|
|
+ LOGGER.debug( () -> "sent email: " + emailItemBean.toDebugString(), () -> sendTime );
|
|
StatisticsManager.incrementStat( pwmApplication, Statistic.EMAIL_SEND_SUCCESSES );
|
|
StatisticsManager.incrementStat( pwmApplication, Statistic.EMAIL_SEND_SUCCESSES );
|
|
-
|
|
|
|
}
|
|
}
|
|
catch ( final MessagingException | PwmException e )
|
|
catch ( final MessagingException | PwmException e )
|
|
{
|
|
{
|
|
@@ -488,68 +508,30 @@ public class EmailService implements PwmService
|
|
new String[] {
|
|
new String[] {
|
|
emailItemBean.toDebugString(),
|
|
emailItemBean.toDebugString(),
|
|
JavaHelper.readHostileExceptionMessage( e ),
|
|
JavaHelper.readHostileExceptionMessage( e ),
|
|
- }
|
|
|
|
|
|
+ }
|
|
);
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
- if ( serverTransport != null )
|
|
|
|
|
|
+ if ( emailConnection != null )
|
|
{
|
|
{
|
|
- serverErrors.put( serverTransport.getEmailServer(), errorInformation );
|
|
|
|
|
|
+ lastSendError.set( errorInformation );
|
|
|
|
+ emailConnection.getEmailServer().getConnectionStats().increment( EmailServer.ServerStat.sendFailures );
|
|
|
|
+
|
|
}
|
|
}
|
|
LOGGER.error( errorInformation );
|
|
LOGGER.error( errorInformation );
|
|
throw e;
|
|
throw e;
|
|
}
|
|
}
|
|
- }
|
|
|
|
-
|
|
|
|
- private EmailConnection getSmtpTransport( )
|
|
|
|
- throws PwmUnrecoverableException
|
|
|
|
- {
|
|
|
|
-
|
|
|
|
- // the global server incrementer rotates the server list by 1 offset each attempt to get an smtp transport.
|
|
|
|
- int nextSlot = serverIncrementer.next();
|
|
|
|
-
|
|
|
|
- for ( int i = 0; i < servers.size(); i++ )
|
|
|
|
|
|
+ finally
|
|
{
|
|
{
|
|
- nextSlot = nextSlot >= ( servers.size() - 1 )
|
|
|
|
- ? 0
|
|
|
|
- : nextSlot + 1;
|
|
|
|
-
|
|
|
|
- final EmailServer server = servers.get( nextSlot );
|
|
|
|
- try
|
|
|
|
|
|
+ if ( emailConnection != null )
|
|
{
|
|
{
|
|
- final Transport transport = EmailServerUtil.makeSmtpTransport( server );
|
|
|
|
-
|
|
|
|
- serverErrors.remove( server );
|
|
|
|
- return new EmailConnection( server, transport );
|
|
|
|
- }
|
|
|
|
- catch ( final Exception e )
|
|
|
|
- {
|
|
|
|
- final String exceptionMsg = JavaHelper.readHostileExceptionMessage( e );
|
|
|
|
- final String msg = "unable to connect to email server '" + server.toDebugString() + "', error: " + exceptionMsg;
|
|
|
|
- final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_SERVICE_UNREACHABLE, msg );
|
|
|
|
- serverErrors.put( server, errorInformation );
|
|
|
|
- LOGGER.warn( () -> errorInformation.toDebugStr() );
|
|
|
|
|
|
+ connectionPool.returnEmailConnection( emailConnection );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- throw PwmUnrecoverableException.newException( PwmError.ERROR_SERVICE_UNREACHABLE, "unable to reach any configured email server" );
|
|
|
|
|
|
+ statsLogger.conditionallyExecuteTask();
|
|
}
|
|
}
|
|
|
|
|
|
- private static Set<Integer> readRetryableStatusCodes( final Configuration configuration )
|
|
|
|
- {
|
|
|
|
- final String rawAppProp = configuration.readAppProperty( AppProperty.SMTP_RETRYABLE_SEND_RESPONSE_STATUSES );
|
|
|
|
- if ( StringUtil.isEmpty( rawAppProp ) )
|
|
|
|
- {
|
|
|
|
- return Collections.emptySet();
|
|
|
|
- }
|
|
|
|
|
|
|
|
- final Set<Integer> returnData = new HashSet<>();
|
|
|
|
- for ( final String loopString : rawAppProp.split( "," ) )
|
|
|
|
- {
|
|
|
|
- final Integer loopInt = Integer.parseInt( loopString );
|
|
|
|
- returnData.add( loopInt );
|
|
|
|
- }
|
|
|
|
- return Collections.unmodifiableSet( returnData );
|
|
|
|
- }
|
|
|
|
}
|
|
}
|
|
|
|
|