/*
* Sun Public License
*
* The contents of this file are subject to the Sun Public License Version
* 1.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is available at http://www.sun.com/
*
* The Original Code is the SLAMD Distributed Load Generation Engine.
* The Initial Developer of the Original Code is Neil A. Wilson.
* Portions created by Neil A. Wilson are Copyright (C) 2004-2010.
* Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc.
* All Rights Reserved.
*
* Contributor(s): Neil A. Wilson
*/
package com.slamd.jobs;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Random;
import com.unboundid.util.FixedRateBarrier;
import com.unboundid.util.ValuePattern;
import com.slamd.job.JobClass;
import com.slamd.job.UnableToRunException;
import com.slamd.parameter.IntegerParameter;
import com.slamd.parameter.InvalidValueException;
import com.slamd.parameter.Parameter;
import com.slamd.parameter.ParameterList;
import com.slamd.parameter.PlaceholderParameter;
import com.slamd.parameter.StringParameter;
import com.slamd.stat.IncrementalTracker;
import com.slamd.stat.IntegerValueTracker;
import com.slamd.stat.RealTimeStatReporter;
import com.slamd.stat.StatTracker;
import com.slamd.stat.TimeTracker;
/**
* This class defines a SLAMD job that interacts with an SMTP mail server by
* sending messages of a fixed or randomly-chosen size to one or more recipients
* at a rate that is as high as possible.
*
*
* @author Neil A. Wilson
*/
public class SMTPSendRateJobClass
extends JobClass
{
/**
* The set of characters that will be used when forming random "words".
*/
public static final char[] ALPHABET =
"abcdefghijklmnopqrstuvwxyz".toCharArray();
/**
* The end-of-line character that is required for SMTP messages.
*/
public static final String EOL = "\r\n";
/**
* The display name of the stat tracker used to count the number of SMTP
* sessions established.
*/
public static final String STAT_TRACKER_SMTP_SESSIONS = "SMTP Sessions";
/**
* The display name of the stat tracker used to count the total number of
* SMTP sessions in which at least one message was sent successfully.
*/
public static final String STAT_TRACKER_SUCCESS_COUNT = "Successful Sessions";
/**
* The display name of the stat tracker used to count the total number of
* SMTP sessions in which no messages were sent successfully.
*/
public static final String STAT_TRACKER_FAILURE_COUNT = "Failed Sessions";
/**
* The display name of the stat tracker used to keep track of the total number
* of recipients specified for each message.
*/
public static final String STAT_TRACKER_TOTAL_RECIPIENTS = "Total Recipients";
/**
* The display name of the stat tracker used to keep track of the number of
* recipient addresses that were accepted by the mail server for each message.
*/
public static final String STAT_TRACKER_ACCEPTED_RECIPIENTS =
"Accepted Recipients";
/**
* The display name of the stat tracker used to keep track of the number of
* recipient addresses that were rejected by the mail server for each message.
*/
public static final String STAT_TRACKER_REJECTED_RECIPIENTS =
"Rejected Recipients";
/**
* The display name of the stat tracker used to time the process of
* authenticating and retrieving the list of messages.
*/
public static final String STAT_TRACKER_SESSION_DURATION =
"Session Duration (ms)";
// The length of time between initial requests.
private IntegerParameter delayParameter =
new IntegerParameter("delay", "Time Between SMTP Sessions (ms)",
"The length of time in milliseconds between " +
"attempts to access the SMTP server.",
true, 0, true, 0, false, 0);
// The parameter that specifies the maximum request rate.
private IntegerParameter maxRateParameter = new IntegerParameter("maxRate",
"Max Request Rate (Requests/Second/Client)",
"Specifies the maximum request rate (in requests per second per " +
"client) to attempt to maintain. If multiple clients are used, " +
"then each client will attempt to maintain this rate. A value " +
"less than or equal to zero indicates that the client should " +
"attempt to perform requests as quickly as possible.",
true, -1);
// The maximum number of recipients to include in the message.
private IntegerParameter maxRecipientsParameter =
new IntegerParameter("max_recipients", "Maximum Number of Recipients",
"The maximum number of recipients that should be " +
"used for any single message.", true, 1, true, 1,
false, 0);
// The minimum number of recipients to include in the message.
private IntegerParameter minRecipientsParameter =
new IntegerParameter("min_recipients", "Minimum Number of Recipients",
"The minimum number of recipients that should be " +
"used for any single message.", true, 1, true, 1,
false, 0);
// The port number of the SMTP server.
private IntegerParameter portParameter =
new IntegerParameter("smtp_port", "SMTP Server Port",
"The port number on which the SMTP server is " +
"listening for requests.", true, 25, true, 1, true,
65535);
// The parameter that specifies the interval over which to enforce the maximum
// request rate.
private IntegerParameter rateLimitDurationParameter = new IntegerParameter(
"maxRateDuration", "Max Rate Enforcement Interval (Seconds)",
"Specifies the duration in seconds of the interval over which to " +
"attempt to maintain the configured maximum rate. A value of " +
"zero indicates that it should be equal to the statistics " +
"collection interval. Large values may allow more variation but " +
"may be more accurate over time. Small values can better " +
"ensure that the rate doesn't exceed the requested level but may " +
"be less able to achieve the desired rate.",
true, 0, true,0, false, 0);
// The size in bytes that should be used for the message body.
private IntegerParameter sizeParameter =
new IntegerParameter("size", "Message Body Size (bytes)",
"The size in bytes that should be used for the " +
"body of the SMTP message. Note that this will " +
"be used as an approximation -- the actual " +
"message size may deviate by a few bytes. It " +
"should also be noted that this does not " +
"include the SMTP message headers.", true, 1024,
true, 1, false, 0);
// A placeholder parameter that is only used for formatting.
private PlaceholderParameter placeholder = new PlaceholderParameter();
// The address from which messages will originate.
private StringParameter fromParameter =
new StringParameter("from_address", "From Address",
"The e-mail address from which messages sent by " +
"this job will originate.", true, "");
// The address of the SMTP server.
private StringParameter hostParameter =
new StringParameter("smtp_host", "SMTP Server Address",
"The fully-qualified domain name or IP address of " +
"the system running the SMTP server.", true, "");
// The recipient(s) to use for the messages.
private StringParameter recipientParameter =
new StringParameter("recipient", "Recipient Address",
"The e-mail address of the recipient that should " +
"be used for each mail message. A range of " +
"values may be specified by enclosing the range " +
"in brackets and separating the minimum and " +
"maximum values with a dash (e.g., [1-1000]), or " +
"a sequential range may be specified by " +
"separating the minimum and maximum values with a " +
"colon (e.g., [1:1000]).", true, "");
// Static variables used to hold parameter values.
private static int delay;
private static int messageSize;
private static int maxRecipients;
private static int minRecipients;
private static int recipientSpan;
private static int smtpPort;
private static String fromAddress;
private static String messageBody;
private static String smtpAddress;
private static String subject;
private static ValuePattern recipientPattern;
// The random number generator for the job.
private static Random parentRandom;
private Random random;
// The rate limiter for this job.
private static FixedRateBarrier rateLimiter;
// The local address associated with this client system.
private String localAddress;
// The stat trackers for the job.
private IncrementalTracker failureCounter;
private IncrementalTracker sessionCounter;
private IncrementalTracker successCounter;
private IntegerValueTracker acceptedRecipientTracker;
private IntegerValueTracker rejectedRecipientTracker;
private IntegerValueTracker totalRecipientTracker;
private TimeTracker sessionTimer;
/**
* The default constructor used to create a new instance of the job class.
* The only thing it should do is to invoke the superclass constructor. All
* other initialization should be performed in the <CODE>initialize</CODE>
* method.
*/
public SMTPSendRateJobClass()
{
super();
}
/**
* {@inheritDoc}
*/
@Override()
public String getJobName()
{
return "SMTP SendRate";
}
/**
* {@inheritDoc}
*/
@Override()
public String getShortDescription()
{
return "Repeatedly send e-mail messages using an SMTP mail server";
}
/**
* {@inheritDoc}
*/
@Override()
public String[] getLongDescription()
{
return new String[]
{
"This job can be used to repeatedly establish sessions with an SMTP " +
"mail server and send messages to one or more recipients."
};
}
/**
* {@inheritDoc}
*/
@Override()
public String getJobCategoryName()
{
return "Mail";
}
/**
* {@inheritDoc}
*/
@Override()
public ParameterList getParameterStubs()
{
Parameter[] parameters =
{
placeholder,
hostParameter,
portParameter,
fromParameter,
recipientParameter,
minRecipientsParameter,
maxRecipientsParameter,
sizeParameter,
delayParameter,
maxRateParameter,
rateLimitDurationParameter
};
return new ParameterList(parameters);
}
/**
* {@inheritDoc}
*/
@Override()
public StatTracker[] getStatTrackerStubs(String clientID, String threadID,
int collectionInterval)
{
return new StatTracker[]
{
new IncrementalTracker(clientID, threadID, STAT_TRACKER_SMTP_SESSIONS,
collectionInterval),
new TimeTracker(clientID, threadID, STAT_TRACKER_SESSION_DURATION,
collectionInterval),
new IncrementalTracker(clientID, threadID, STAT_TRACKER_SUCCESS_COUNT,
collectionInterval),
new IncrementalTracker(clientID, threadID, STAT_TRACKER_FAILURE_COUNT,
collectionInterval),
new IntegerValueTracker(clientID, threadID, STAT_TRACKER_TOTAL_RECIPIENTS,
collectionInterval),
new IntegerValueTracker(clientID, threadID,
STAT_TRACKER_ACCEPTED_RECIPIENTS,
collectionInterval),
new IntegerValueTracker(clientID, threadID,
STAT_TRACKER_REJECTED_RECIPIENTS,
collectionInterval)
};
}
/**
* {@inheritDoc}
*/
@Override()
public StatTracker[] getStatTrackers()
{
return new StatTracker[]
{
sessionCounter,
sessionTimer,
successCounter,
failureCounter,
totalRecipientTracker,
acceptedRecipientTracker,
rejectedRecipientTracker
};
}
/**
* {@inheritDoc}
*/
@Override()
public void validateJobInfo(int numClients, int threadsPerClient,
int threadStartupDelay, Date startTime,
Date stopTime, int duration,
int collectionInterval, ParameterList parameters)
throws InvalidValueException
{
// The recipient parameter must be parseable as a value pattern.
StringParameter p =
parameters.getStringParameter(recipientParameter.getName());
if ((p != null) && p.hasValue())
{
try
{
new ValuePattern(p.getValue());
}
catch (ParseException pe)
{
throw new InvalidValueException("The value provided for the '" +
p.getDisplayName() + "' parameter is not a valid value " +
"pattern: " + pe.getMessage(), pe);
}
}
IntegerParameter minParam =
parameters.getIntegerParameter(minRecipientsParameter.getName());
if (minParam == null)
{
throw new InvalidValueException("No value provided for required " +
"parameter " +
minRecipientsParameter.getDisplayName());
}
IntegerParameter maxParam =
parameters.getIntegerParameter(maxRecipientsParameter.getName());
if (maxParam == null)
{
throw new InvalidValueException("No value provided for required " +
"parameter " +
maxRecipientsParameter.getDisplayName());
}
int minRecip = minParam.getIntValue();
int maxRecip = maxParam.getIntValue();
if ((minRecip <= 0) || (maxRecip <= 0))
{
throw new InvalidValueException("Minimum and maximum number of " +
"recipients must be greater than zero.");
}
else if (minRecip > maxRecip)
{
throw new InvalidValueException("Maximum number of recipients must be " +
"greater than or equal to the minimum " +
"number of recipients.");
}
}
/**
* {@inheritDoc}
*/
@Override()
public boolean providesParameterTest()
{
return true;
}
/**
* {@inheritDoc}
*/
@Override()
public boolean testJobParameters(ParameterList parameters,
ArrayList<String> outputMessages)
{
// Get the parameters necessary to perform the test.
StringParameter hostParam =
parameters.getStringParameter(hostParameter.getName());
if ((hostParam == null) || (! hostParam.hasValue()))
{
outputMessages.add("ERROR: No SMTP server address was provided.");
return false;
}
String host = hostParam.getStringValue();
IntegerParameter portParam =
parameters.getIntegerParameter(portParameter.getName());
if ((portParam == null) || (! portParam.hasValue()))
{
outputMessages.add("ERROR: No SMTP server port was provided.");
return false;
}
int port = portParam.getIntValue();
// Try to establish a connection to the SMTP server.
Socket socket;
BufferedReader reader;
BufferedWriter writer;
try
{
outputMessages.add("Trying to establish a connection to SMTP server " +
host + ':' + port + "....");
socket = new Socket(host, port);
reader = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
writer = new BufferedWriter(new OutputStreamWriter(
socket.getOutputStream()));
outputMessages.add("Connected successfully.");
outputMessages.add("");
}
catch (Exception e)
{
outputMessages.add("ERROR: Unable to connect: " +
stackTraceToString(e));
return false;
}
// Read the initial response line from the server.
try
{
outputMessages.add("Trying to read the hello string from the server....");
String line = reader.readLine();
outputMessages.add("Hello string was '" + line + "'.");
outputMessages.add("");
}
catch (Exception e)
{
outputMessages.add("ERROR: Unable to read the hello string: " +
stackTraceToString(e));
try
{
reader.close();
} catch (Exception e2) {}
try
{
writer.close();
} catch (Exception e2) {}
try
{
socket.close();
} catch (Exception e2) {}
return false;
}
// If we've gotten here, then everything seems to be OK. Close the
// connection and return true.
try
{
outputMessages.add("Sending the QUIT request to the server.");
outputMessages.add("");
writer.write("QUIT" + EOL);
writer.flush();
} catch (Exception e) {}
try
{
reader.close();
} catch (Exception e) {}
try
{
writer.close();
} catch (Exception e) {}
try
{
socket.close();
} catch (Exception e) {}
outputMessages.add("All tests completed.");
return true;
}
/**
* {@inheritDoc}
*/
@Override()
public void initializeClient(String clientID, ParameterList parameters)
throws UnableToRunException
{
// Seed the parent random number generator.
parentRandom = new Random();
// Get the address of the SMTP server.
hostParameter = parameters.getStringParameter(hostParameter.getName());
if (hostParameter != null)
{
smtpAddress = hostParameter.getStringValue();
}
// Get the port for the SMTP server.
portParameter = parameters.getIntegerParameter(portParameter.getName());
if (portParameter != null)
{
smtpPort = portParameter.getIntValue();
}
// Get the from address.
fromParameter = parameters.getStringParameter(fromParameter.getName());
if (fromParameter != null)
{
fromAddress = fromParameter.getStringValue();
}
// Get the recipient pattern.
recipientParameter =
parameters.getStringParameter(recipientParameter.getName());
if (recipientParameter != null)
{
try
{
recipientPattern =
new ValuePattern(recipientParameter.getStringValue());
}
catch (Exception e)
{
throw new UnableToRunException(
"Unable to parse the recipient pattern: " + stackTraceToString(e),
e);
}
}
// Get the minimum number of recipients.
minRecipientsParameter =
parameters.getIntegerParameter(minRecipientsParameter.getName());
if (minRecipientsParameter != null)
{
minRecipients = minRecipientsParameter.getIntValue();
}
// Get the maximum number of recipients.
maxRecipientsParameter =
parameters.getIntegerParameter(maxRecipientsParameter.getName());
if (maxRecipientsParameter != null)
{
maxRecipients = maxRecipientsParameter.getIntValue();
}
recipientSpan = maxRecipients - minRecipients + 1;
// Get the message size.
sizeParameter = parameters.getIntegerParameter(sizeParameter.getName());
if (sizeParameter != null)
{
messageSize = sizeParameter.getIntValue();
}
// Get the delay between requests.
delayParameter = parameters.getIntegerParameter(delayParameter.getName());
if (delayParameter != null)
{
delay = delayParameter.getIntValue();
}
// Initialize the rate limiter.
rateLimiter = null;
maxRateParameter =
parameters.getIntegerParameter(maxRateParameter.getName());
if ((maxRateParameter != null) && maxRateParameter.hasValue())
{
int maxRate = maxRateParameter.getIntValue();
if (maxRate > 0)
{
int rateIntervalSeconds = 0;
rateLimitDurationParameter = parameters.getIntegerParameter(
rateLimitDurationParameter.getName());
if ((rateLimitDurationParameter != null) &&
rateLimitDurationParameter.hasValue())
{
rateIntervalSeconds = rateLimitDurationParameter.getIntValue();
}
if (rateIntervalSeconds <= 0)
{
rateIntervalSeconds = getClientSideJob().getCollectionInterval();
}
rateLimiter = new FixedRateBarrier(rateIntervalSeconds * 1000L,
maxRate * rateIntervalSeconds);
}
}
// Create the message that will be used for all the SMTP sessions.
generateSubject();
generateMessage();
}
/**
* {@inheritDoc}
*/
@Override()
public void initializeThread(String clientID, String threadID,
int collectionInterval, ParameterList parameters)
throws UnableToRunException
{
// Create the stat trackers for this thread.
sessionCounter = new IncrementalTracker(clientID, threadID,
STAT_TRACKER_SMTP_SESSIONS,
collectionInterval);
sessionTimer = new TimeTracker(clientID, threadID,
STAT_TRACKER_SESSION_DURATION,
collectionInterval);
successCounter = new IncrementalTracker(clientID, threadID,
STAT_TRACKER_SUCCESS_COUNT,
collectionInterval);
failureCounter = new IncrementalTracker(clientID, threadID,
STAT_TRACKER_FAILURE_COUNT,
collectionInterval);
totalRecipientTracker = new IntegerValueTracker(clientID, threadID,
STAT_TRACKER_TOTAL_RECIPIENTS,
collectionInterval);
acceptedRecipientTracker = new IntegerValueTracker(clientID, threadID,
STAT_TRACKER_ACCEPTED_RECIPIENTS,
collectionInterval);
rejectedRecipientTracker = new IntegerValueTracker(clientID, threadID,
STAT_TRACKER_REJECTED_RECIPIENTS,
collectionInterval);
// Enable real-time reporting of the data for these stat trackers.
RealTimeStatReporter statReporter = getStatReporter();
if (statReporter != null)
{
String jobID = getJobID();
sessionCounter.enableRealTimeStats(statReporter, jobID);
sessionTimer.enableRealTimeStats(statReporter, jobID);
successCounter.enableRealTimeStats(statReporter, jobID);
failureCounter.enableRealTimeStats(statReporter, jobID);
totalRecipientTracker.enableRealTimeStats(statReporter, jobID);
acceptedRecipientTracker.enableRealTimeStats(statReporter, jobID);
rejectedRecipientTracker.enableRealTimeStats(statReporter, jobID);
}
// Get the local address associated with this client.
try
{
localAddress = InetAddress.getLocalHost().getHostName();
}
catch (IOException ioe)
{
try
{
localAddress = InetAddress.getLocalHost().getHostAddress();
}
catch (IOException ioe2)
{
localAddress = clientID;
}
}
// Seed the random number generator for this thread.
random = new Random(parentRandom.nextLong());
}
/**
* {@inheritDoc}
*/
@Override()
public void runJob()
{
// Define variables that will be used throughout this method.
BufferedReader reader;
BufferedWriter writer;
SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, d MMM yyyy " +
"HH:mm:ss Z");
int numAccepted;
int numRejected;
int numRecipients;
long lastStartTime = 0;
Socket socket;
String serverResponse = null;
String[] recipients;
// Start the stat trackers.
sessionCounter.startTracker();
sessionTimer.startTracker();
successCounter.startTracker();
failureCounter.startTracker();
totalRecipientTracker.startTracker();
acceptedRecipientTracker.startTracker();
rejectedRecipientTracker.startTracker();
// Loop until it is determined that the job should stop.
mainLoop:
while (! shouldStop())
{
if (rateLimiter != null)
{
if (rateLimiter.await())
{
continue;
}
}
// If we need to sleep, then do so.
if (delay > 0)
{
long now = System.currentTimeMillis();
long prevTestTime = now - lastStartTime;
if (prevTestTime < delay)
{
try
{
Thread.sleep(delay - prevTestTime);
} catch (Exception e) {}
}
}
lastStartTime = System.currentTimeMillis();
// Put together the list of recipients.
numRecipients = ((random.nextInt() & 0x7FFFFFFF) % recipientSpan) +
minRecipients;
recipients = new String[numRecipients];
for (int i=0; i < numRecipients; i++)
{
recipients[i] = recipientPattern.nextValue();
}
// Start the attempt timer and indicate the beginning of a new attempt.
sessionCounter.increment();
sessionTimer.startTimer();
// Open the connection to the SMTP server.
try
{
socket = new Socket(smtpAddress, smtpPort);
reader = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
writer = new BufferedWriter(new OutputStreamWriter(
socket.getOutputStream()));
}
catch (IOException ioe)
{
sessionTimer.stopTimer();
failureCounter.increment();
continue;
}
// The SMTP server should first introduce itself to the client. Make sure
// that the introduction is acceptable -- if so then it should start with
// the number "220".
try
{
serverResponse = reader.readLine();
}
catch (IOException ioe)
{
sessionTimer.stopTimer();
failureCounter.increment();
continue;
}
if (! serverResponse.startsWith("220"))
{
sessionTimer.stopTimer();
failureCounter.increment();
try
{
sendLine(writer, "QUIT");
writer.close();
reader.close();
socket.close();
} catch (IOException ioe) {}
continue;
}
// Send a "HELO" request to the server and read the response. Make sure
// that the response starts with a "250".
try
{
sendLine(writer, "HELO " + localAddress);
serverResponse = reader.readLine();
}
catch (IOException ioe)
{
sessionTimer.stopTimer();
failureCounter.increment();
try
{
sendLine(writer, "QUIT");
writer.close();
reader.close();
socket.close();
} catch (IOException ioe2) {}
continue;
}
if (! serverResponse.startsWith("250"))
{
sessionTimer.stopTimer();
failureCounter.increment();
try
{
sendLine(writer, "QUIT");
writer.close();
reader.close();
socket.close();
} catch (IOException ioe) {}
continue;
}
// Specify the from address. The server must provide a response starting
// with "250" for this to be acceptable.
try
{
sendLine(writer, "MAIL FROM:<" + fromAddress + '>');
serverResponse = reader.readLine();
}
catch (IOException ioe)
{
sessionTimer.stopTimer();
failureCounter.increment();
try
{
sendLine(writer, "QUIT");
writer.close();
reader.close();
socket.close();
} catch (IOException ioe2) {}
continue;
}
if (! serverResponse.startsWith("250"))
{
sessionTimer.stopTimer();
failureCounter.increment();
try
{
sendLine(writer, "QUIT");
writer.close();
reader.close();
socket.close();
} catch (IOException ioe) {}
continue;
}
// Specify the recipients. The server should provide a response starting
// with "250" or "251" for each of them.
numAccepted = 0;
numRejected = 0;
for (int i=0; i < recipients.length; i++)
{
try
{
sendLine(writer, "RCPT TO:<" + recipients[i] + '>');
serverResponse = reader.readLine();
}
catch (IOException ioe)
{
sessionTimer.stopTimer();
failureCounter.increment();
try
{
sendLine(writer, "QUIT");
writer.close();
reader.close();
socket.close();
} catch (IOException ioe2) {}
continue mainLoop;
}
if (serverResponse.startsWith("25"))
{
numAccepted++;
}
else
{
numRejected++;
}
}
totalRecipientTracker.addValue(numRecipients);
acceptedRecipientTracker.addValue(numAccepted);
rejectedRecipientTracker.addValue(numRejected);
if (numAccepted == 0)
{
sessionTimer.stopTimer();
failureCounter.increment();
try
{
sendLine(writer, "QUIT");
writer.close();
reader.close();
socket.close();
} catch (IOException ioe) {}
continue;
}
// Send the "DATA" header to the server. The server must provide a
// response starting with "354".
try
{
sendLine(writer, "DATA");
serverResponse = reader.readLine();
}
catch (IOException ioe)
{
sessionTimer.stopTimer();
failureCounter.increment();
try
{
sendLine(writer, "QUIT");
writer.close();
reader.close();
socket.close();
} catch (IOException ioe2) {}
continue;
}
if (! serverResponse.startsWith("354"))
{
sessionTimer.stopTimer();
failureCounter.increment();
try
{
sendLine(writer, "QUIT");
writer.close();
reader.close();
socket.close();
} catch (IOException ioe) {}
continue;
}
// Send the message header. The server will not provide a response to
// this. Also, since we're sending multiple lines at once, there is no
// reason to flush after each one so don't do that.
try
{
writer.write("From: <" + fromAddress + '>' + EOL);
writer.write("MIME-Version: 1.0" + EOL);
writer.write("Content-type: text/plain; charset=us-ascii" + EOL);
writer.write("Date: " + dateFormat.format(new Date()) + EOL);
writer.write("Subject: " + subject + EOL);
for (int i=0; i < recipients.length; i++)
{
writer.write("To: <" + recipients[i] + '>' + EOL);
}
writer.write(EOL);
}
catch (IOException ioe)
{
sessionTimer.stopTimer();
failureCounter.increment();
try
{
sendLine(writer, "QUIT");
writer.close();
reader.close();
socket.close();
} catch (IOException ioe2) {}
continue;
}
// Send the message itself followed by a line containing only a period.
// The server should provide a response starting with "250".
try
{
writer.write(messageBody + EOL);
sendLine(writer, ".");
serverResponse = reader.readLine();
}
catch (IOException ioe)
{
sessionTimer.stopTimer();
failureCounter.increment();
try
{
sendLine(writer, "QUIT");
writer.close();
reader.close();
socket.close();
} catch (IOException ioe2) {}
continue;
}
if (! serverResponse.startsWith("250"))
{
sessionTimer.stopTimer();
failureCounter.increment();
try
{
sendLine(writer, "QUIT");
writer.close();
reader.close();
socket.close();
} catch (IOException ioe) {}
continue;
}
// The message is complete, so end the session with a "QUIT".
try
{
sendLine(writer, "QUIT");
writer.close();
reader.close();
socket.close();
}
catch (IOException ioe)
{
sessionTimer.stopTimer();
failureCounter.increment();
try
{
sendLine(writer, "QUIT");
writer.close();
reader.close();
socket.close();
} catch (IOException ioe2) {}
continue;
}
// If we made it here, then everything was successful.
sessionTimer.stopTimer();
successCounter.increment();
}
sessionCounter.stopTracker();
sessionTimer.stopTracker();
successCounter.stopTracker();
failureCounter.stopTracker();
totalRecipientTracker.stopTracker();
acceptedRecipientTracker.stopTracker();
rejectedRecipientTracker.stopTracker();
}
/**
* Writes the provided line of text to the SMTP server in the appropriate
* format.
*
* @param writer The writer that can be used to communicate with the SMTP
* server.
* @param line The line of text to be written to the server.
*
* @throws IOException If a problem occurs while writing the data to the
* SMTP server.
*/
private static void sendLine(BufferedWriter writer, String line)
throws IOException
{
writer.write(line);
writer.write(EOL);
writer.flush();
}
/**
* Generates a subject for this mail message. It will be between 3 and 7
* "words" in length.
*/
private static void generateSubject()
{
int numWords = (parentRandom.nextInt() & 0x7FFFFFFF) % 5 + 3;
StringBuilder subjectBuffer = new StringBuilder();
String separator = "";
for (int i=0; i < numWords; i++)
{
int wordLength = (parentRandom.nextInt() & 0x7FFFFFFF) % 10 + 3;
subjectBuffer.append(generateWord(wordLength));
subjectBuffer.append(separator);
separator = " ";
}
subject = subjectBuffer.toString();
}
/**
* Creates the e-mail message that will be sent. Although it will not
* contain actual words, it will at least look realistic in terms of
* spacing, word size, punctuation, etc.
*/
private static void generateMessage()
{
int totalSize = 0;
int wordsThisSentence = 0;
int charsThisLine = 0;
StringBuilder messageBuffer = new StringBuilder();
int sentenceSize = (parentRandom.nextInt() & 0x7FFFFFFF) % 11 + 5;
while (totalSize < messageSize)
{
int wordLength = (parentRandom.nextInt() & 0x7FFFFFFF) % 10 + 3;
String word = generateWord(wordLength);
messageBuffer.append(word);
totalSize += wordLength;
charsThisLine += wordLength;
wordsThisSentence++;
if ((wordsThisSentence > sentenceSize) || (totalSize > messageSize))
{
messageBuffer.append(". ");
totalSize += 3;
charsThisLine += 3;
wordsThisSentence = 0;
sentenceSize = (parentRandom.nextInt() & 0x7FFFFFFF) % 11 + 5;
if (charsThisLine > 70)
{
messageBuffer.append(EOL);
totalSize += EOL.length();
charsThisLine = 0;
}
}
else if (charsThisLine > 70)
{
messageBuffer.append(EOL);
totalSize += EOL.length();
charsThisLine = 0;
}
else
{
messageBuffer.append(' ');
totalSize++;
charsThisLine++;
}
}
messageBody = messageBuffer.toString();
}
/**
* Generates a word of the specified length comprised of characters randomly
* chosen from the provided character set.
*
* @param numChars The number of characters to include in the word.
*
* @return A word of the specified length comprised of characters randomly
* chosen from the provided character set.
*/
private static String generateWord(int numChars)
{
char[] chars = new char[numChars];
for (int i=0; i < chars.length; i++)
{
chars[i] = ALPHABET[(parentRandom.nextInt() & 0x7FFFFFFF) %
ALPHABET.length];
}
return new String(chars);
}
}