Java tutorial
/* * Copyright (c) 2008-2011, Martijn Brinkers, Djigzo. * * This file is part of Djigzo email encryption. * * Djigzo is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License * version 3, 19 November 2007 as published by the Free Software * Foundation. * * Djigzo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public * License along with Djigzo. If not, see <http://www.gnu.org/licenses/> * * Additional permission under GNU AGPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or * combining it with aspectjrt.jar, aspectjweaver.jar, tyrex-1.0.3.jar, * freemarker.jar, dom4j.jar, mx4j-jmx.jar, mx4j-tools.jar, * spice-classman-1.0.jar, spice-loggerstore-0.5.jar, spice-salt-0.8.jar, * spice-xmlpolicy-1.0.jar, saaj-api-1.3.jar, saaj-impl-1.3.jar, * wsdl4j-1.6.1.jar (or modified versions of these libraries), * containing parts covered by the terms of Eclipse Public License, * tyrex license, freemarker license, dom4j license, mx4j license, * Spice Software License, Common Development and Distribution License * (CDDL), Common Public License (CPL) the licensors of this Program grant * you additional permission to convey the resulting work. */ package mitm.common.tools; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Properties; import java.util.StringTokenizer; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import javax.mail.Address; import javax.mail.MessagingException; import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import mitm.common.mail.EmailAddressUtils; import mitm.common.mail.MailTransport; import mitm.common.mail.MailTransportImpl; import mitm.common.mail.MailUtils; import mitm.common.util.DateTimeUtils; import mitm.common.util.ThreadUtils; import org.apache.commons.cli.BasicParser; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.MissingArgumentException; import org.apache.commons.cli.Option; import org.apache.commons.cli.OptionBuilder; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.NullOutputStream; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.text.StrBuilder; import org.apache.log4j.PropertyConfigurator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.subethamail.smtp.MessageContext; import org.subethamail.smtp.MessageHandler; import org.subethamail.smtp.MessageHandlerFactory; import org.subethamail.smtp.RejectException; import org.subethamail.smtp.TooMuchDataException; import org.subethamail.smtp.server.SMTPServer; /** * Tool which can be used to send and receive email for testing purposes. * * @author Martijn Brinkers */ public class SendMail { private final static Logger logger = LoggerFactory.getLogger(SendMail.class); private final String smtpHost; private final Integer smtpPort; private final String username; private final String sender; private final String from; private final String recipients; private final String inFile; private final String subject; private final Integer count; private final Integer threads; private final Long delay; private final Integer serverPort; private final Integer throttle; private final boolean uniqueFrom; private String password; private final AtomicInteger receiveCount = new AtomicInteger(); private final AtomicInteger sentCount = new AtomicInteger(); private final AtomicBoolean forceQuit = new AtomicBoolean(); final Semaphore throtllingSemaphore; /* * MessageHandlerFactory used by the internal SMTP server */ private class MessageHandlerFactoryImpl implements MessageHandlerFactory { @Override public MessageHandler create(MessageContext context) { return new Handler(); } class Handler implements MessageHandler { @Override public void data(InputStream input) throws RejectException, TooMuchDataException, IOException { /* * Drain the input (aka /dev/null) */ IOUtils.copy(input, new NullOutputStream()); int received = receiveCount.incrementAndGet(); logger.info("Message\t" + received + "\trec.\tdifference\t\t" + (sentCount.intValue() - received)); if (throtllingSemaphore != null) { /* for throttling the sending of emails */ throtllingSemaphore.release(); } } @Override public void done() { // ignore } @Override public void from(String from) throws RejectException { } @Override public void recipient(String recipient) throws RejectException { } } } public SendMail(String[] args) throws ParseException, IOException, MessagingException, InterruptedException { CommandLineParser parser = new BasicParser(); Options options = createCommandLineOptions(); HelpFormatter formatter = new HelpFormatter(); formatter.printHelp("SendMail", options, true); CommandLine commandLine = parser.parse(options, args); smtpHost = commandLine.getOptionValue("h"); smtpPort = optionAsInteger(commandLine, "p", 25); username = commandLine.getOptionValue("u"); password = commandLine.getOptionValue("password"); sender = commandLine.getOptionValue("s"); from = commandLine.getOptionValue("f"); recipients = commandLine.getOptionValue("r"); inFile = commandLine.getOptionValue("in"); subject = commandLine.getOptionValue("su"); count = optionAsInteger(commandLine, "c", 1); threads = optionAsInteger(commandLine, "t", 1); delay = optionAsLong(commandLine, "d", 0L); serverPort = optionAsInteger(commandLine, "sp", 2525); throttle = optionAsInteger(commandLine, "throttle", null); uniqueFrom = commandLine.hasOption("uf"); throtllingSemaphore = throttle != null ? new Semaphore(throttle, true) : null; if (commandLine.hasOption("pwd")) { System.out.println("Please enter your password: "); password = new jline.ConsoleReader().readLine(new Character('*')); } if (commandLine.hasOption("l")) { startSMTPServer(); /* allow the SMTP server to settle */ ThreadUtils.sleepQuietly(1000); } Address[] recipientsAddresses = getRecipients(recipients); if (recipientsAddresses != null) { MimeMessage message = commandLine.hasOption("in") ? loadMessage(inFile) : MailUtils.loadMessage(System.in); sendMessage(recipientsAddresses, message); } } private void sendMessage(Address[] recipients, MimeMessage message) throws MissingArgumentException, MessagingException, InterruptedException { Properties properties = System.getProperties(); if (StringUtils.isBlank(smtpHost)) { throw new MissingArgumentException("<smtp host> is missing"); } MailTransport mailSender = new MailTransportImpl(smtpHost, smtpPort, sender, properties, username, password); prepareMessage(message, from, subject); sendMultiThreaded(mailSender, message, recipients); } private void sendMultiThreaded(final MailTransport mailSender, final MimeMessage message, final Address[] recipients) throws InterruptedException { ExecutorService threadPool = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threads, true); final long startTime = System.currentTimeMillis(); for (int i = 1; i <= count; i++) { long threadStart = System.currentTimeMillis(); semaphore.acquireUninterruptibly(); threadPool.execute(new Runnable() { @Override public void run() { try { MimeMessage clone = MailUtils.cloneMessage(message); int sent = sentCount.incrementAndGet(); if (uniqueFrom) { Address[] froms = clone.getFrom(); if (froms != null && froms.length > 0) { clone.setFrom( new InternetAddress(sent + EmailAddressUtils.getEmailAddress(froms[0]))); } } mailSender.sendMessage(clone, recipients); long timePassed = DateTimeUtils .millisecondsToSeconds(System.currentTimeMillis() - startTime); StrBuilder sb = new StrBuilder(); sb.append("Message\t" + sent + "\tsent."); if (timePassed > 0) { float msgPerSec = (float) sent / timePassed; sb.append("\tmessages/second\t" + String.format("%.2f", msgPerSec)); } logger.info(sb.toString()); } catch (MessagingException e) { logger.error("Error sending message.", e); } finally { semaphore.release(); } } }); if (forceQuit.get()) { break; } if (throtllingSemaphore != null) { /* for throttling the sending of emails */ throtllingSemaphore.acquire(); } else { /* no throttling so use delay */ long sleepTime = delay - (System.currentTimeMillis() - threadStart); if (sleepTime > 0) { Thread.sleep(sleepTime); } } } threadPool.shutdown(); threadPool.awaitTermination(30, TimeUnit.SECONDS); waitForReceiveThreads(); logger.info("Total sent: " + sentCount.intValue() + ". Total time: " + DateTimeUtils.millisecondsToSeconds(System.currentTimeMillis() - startTime) + " (sec.)"); } private void waitForReceiveThreads() throws InterruptedException { // if (throtllingSemaphore != null) { // throtllingSemaphore.tryAcquire(throttle, 120, TimeUnit.SECONDS); // } } private void startSMTPServer() { MessageHandlerFactoryImpl factory = new MessageHandlerFactoryImpl(); SMTPServer smtpServer = new SMTPServer(factory); smtpServer.setPort(serverPort); smtpServer.start(); /* * Add a shutdown hook which waits a few seconds before shutting down. This * gives the SMTP server some time to receive all messages */ Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { /* * Make sure that no more messages are sent */ forceQuit.set(true); try { waitForReceiveThreads(); } catch (InterruptedException e) { // ignore } ThreadUtils.sleepQuietly(5000); } }); } @SuppressWarnings("static-access") private static Options createCommandLineOptions() { Options options = new Options(); Option hostOption = OptionBuilder.withArgName("host").hasArg().withDescription("smtp host").create("h"); hostOption.setRequired(false); options.addOption(hostOption); Option portOption = OptionBuilder.withArgName("port").hasArg().withDescription("smtp port").create("p"); portOption.setRequired(false); options.addOption(portOption); Option usernameOption = OptionBuilder.withArgName("username").hasArg().withDescription("username") .create("u"); usernameOption.setRequired(false); options.addOption(usernameOption); Option passwordPromptOption = OptionBuilder.withArgName("p").withDescription("ask for password") .create("pwd"); passwordPromptOption.setRequired(false); options.addOption(passwordPromptOption); Option passwordOption = OptionBuilder.withArgName("password").hasArg().withDescription("password") .create("password"); passwordOption.setRequired(false); options.addOption(passwordOption); Option senderOption = OptionBuilder.withArgName("sender").hasArg() .withDescription("enveloped sender (MAIL FROM)").create("s"); senderOption.setRequired(false); options.addOption(senderOption); Option subjectOption = OptionBuilder.withArgName("subject").hasArg().withDescription("subject") .create("su"); subjectOption.setRequired(false); options.addOption(subjectOption); Option fromOption = OptionBuilder.withArgName("from").hasArg().withDescription("from (header)").create("f"); fromOption.setRequired(false); options.addOption(fromOption); Option recipientsOption = OptionBuilder.withArgName("recipients").hasArg() .withDescription("comma separated recipients").create("r"); recipientsOption.setRequired(false); options.addOption(recipientsOption); Option inOption = OptionBuilder.withArgName("email file").hasArg().withDescription("input file (rfc 2822)") .create("in"); inOption.setRequired(false); options.addOption(inOption); Option countOption = OptionBuilder.withArgName("count").hasArg().withDescription("count").create("c"); countOption.setRequired(false); options.addOption(countOption); Option threadsOption = OptionBuilder.withArgName("threads").hasArg().withDescription("number of threads") .create("t"); threadsOption.setRequired(false); options.addOption(threadsOption); Option delayOption = OptionBuilder.withArgName("delay").hasArg().withDescription("delay in milliseconds") .create("d"); delayOption.setRequired(false); options.addOption(delayOption); Option listenOption = OptionBuilder.withArgName("listen").withDescription("listen for incoming SMTP") .create("l"); listenOption.setRequired(false); options.addOption(listenOption); Option throttlingOption = OptionBuilder.withArgName("throttling").hasArg().withDescription("throttle send") .create("throttle"); throttlingOption.setRequired(false); options.addOption(throttlingOption); Option serverPortOption = OptionBuilder.withArgName("port").hasArg().withDescription("smtp server port") .create("serverPort"); serverPortOption.setRequired(false); options.addOption(serverPortOption); Option uniqueFromOption = OptionBuilder.withDescription("prefix the from with an increasing counter") .create("uf"); uniqueFromOption.setRequired(false); options.addOption(uniqueFromOption); return options; } private static Address[] getRecipients(String recipients) throws AddressException { if (StringUtils.isBlank(recipients)) { return null; } List<Address> addresses = new LinkedList<Address>(); StringTokenizer tokenizer = new StringTokenizer(recipients, ","); while (tokenizer.hasMoreTokens()) { String recipient = tokenizer.nextToken(); addresses.add(new InternetAddress(recipient)); } return (Address[]) addresses.toArray(new Address[0]); } private static MimeMessage loadMessage(String filename) throws FileNotFoundException, MessagingException { File mail = new File(filename); mail = mail.getAbsoluteFile(); MimeMessage message = MailUtils.loadMessage(mail); return message; } private static void prepareMessage(MimeMessage message, String from, String subject) throws MessagingException { message.setSentDate(new Date()); if (from != null) { message.setFrom(new InternetAddress(from)); } if (subject != null) { message.setSubject(subject); } } private static Integer optionAsInteger(CommandLine commandLine, String option, Integer defaultIfNull) { String value = commandLine.getOptionValue(option); if (value == null) { return defaultIfNull; } return Integer.parseInt(value); } private static Long optionAsLong(CommandLine commandLine, String option, Long defaultIfNull) { String value = commandLine.getOptionValue(option); if (value == null) { return defaultIfNull; } return Long.parseLong(value); } public static void main(String[] args) { PropertyConfigurator.configure("conf/tools.log4j.properties"); try { new SendMail(args); } catch (MessagingException e) { e.printStackTrace(); } catch (MissingArgumentException e) { logger.warn("Not all required parameters are specified. " + e); } catch (ParseException e) { logger.error("Command line parsing error. " + e); } catch (FileNotFoundException e) { logger.error("File cannot be found. " + e); } catch (Exception e) { logger.error("Unknown error.", e); } } }