AnnouncementMailMojo.java

package org.apache.maven.plugins.announcement;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.List;

import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;

import org.apache.maven.model.Developer;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Execute;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.announcement.mailsender.ProjectJavamailMailSender;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.logging.Logger;
import org.codehaus.plexus.logging.console.ConsoleLogger;
import org.codehaus.plexus.mailsender.MailMessage;
import org.codehaus.plexus.mailsender.MailSenderException;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.StringUtils;

/**
 * Goal which sends an announcement through email.
 *
 * @author aramirez@exist.com
 * @version $Id$
 * @since 2.0-beta-2
 */
@Mojo( name = "announcement-mail", threadSafe = true )
@Execute( goal = "announcement-generate" )
public class AnnouncementMailMojo extends AbstractAnnouncementMojo
{
    // =========================================
    // announcement-mail goal fields
    // =========================================

    /**
     * Possible senders.
     */
    @Parameter( property = "project.developers", required = true, readonly = true )
    private List<Developer> from;

    /**
     * The id of the developer sending the announcement mail. Only used if the <tt>mailSender</tt> attribute is not set.
     * In this case, this should match the id of one of the developers in the pom. If a matching developer is not found,
     * then the first developer in the pom will be used.
     */
    @Parameter( property = "changes.fromDeveloperId" )
    private String fromDeveloperId;

    /**
     * Mail content type to use.
     *
     * @since 2.1
     */
    @Parameter( defaultValue = "text/plain", required = true )
    private String mailContentType;

    /**
     * Defines the sender of the announcement email. This takes precedence over the list of developers specified in the
     * POM. if the sender is not a member of the development team. Note that since this is a bean type, you cannot
     * specify it from command level with
     * 
     * <pre>
     * -D
     * </pre>
     * 
     * . Use
     * 
     * <pre>
     * -Dchanges.sender='Your Name &lt;you@domain>'
     * </pre>
     * 
     * instead.
     */
    @Parameter( property = "changes.mailSender" )
    private MailSender mailSender;

    /**
     * Defines the sender of the announcement. This takes precedence over both ${changes.mailSender} and the list of
     * developers in the POM.
     * <p/>
     * This parameter parses an email address in standard RFC822 format, e.g.
     * 
     * <pre>
     * -Dchanges.sender='Your Name &lt;you@domain>'
     * </pre>
     * 
     * .
     *
     * @since 2.7
     */
    @Parameter( property = "changes.sender" )
    private String senderString;

    /**
     * The password used to send the email.
     */
    @Parameter( property = "changes.password" )
    private String password;

    /**
     */
    @Parameter( defaultValue = "${project}", readonly = true, required = true )
    private MavenProject project;

    /**
     * Smtp Server.
     */
    @Parameter( property = "changes.smtpHost", required = true )
    private String smtpHost;

    /**
     * Port.
     */
    @Parameter( property = "changes.smtpPort", defaultValue = "25", required = true )
    private int smtpPort;

    /**
     * If the email should be sent in SSL mode.
     */
    @Parameter( property = "changes.sslMode", defaultValue = "false" )
    private boolean sslMode;

    /**
     * If the option startTls should be used.
     *
     * @since 2.10
     */
    @Parameter( property = "changes.startTls", defaultValue = "false" )
    private boolean startTls;

    /**
     * Subject for the email.
     */
    // CHECKSTYLE_OFF: LineLength
    @Parameter( property = "changes.subject", defaultValue = "[ANNOUNCEMENT] - ${project.name} ${project.version} released", required = true )
    private String subject;
    // CHECKSTYLE_ON: LineLength

    /**
     * The file that contains the generated announcement.
     *
     * @since 2.10
     */
    @Parameter( property = "changes.announcementFile", defaultValue = "announcement.vm", required = true )
    private String announcementFile;

    /**
     * Directory where the generated announcement file exists.
     *
     * @since 2.10
     */
    @Parameter( defaultValue = "${project.build.directory}/announcement", required = true )
    private File announcementDirectory;

    /**
     * The encoding used in the announcement template.
     *
     * @since 2.10
     */
    @Parameter( property = "changes.templateEncoding", defaultValue = "${project.build.sourceEncoding}" )
    private String templateEncoding;

    /**
     * Directory which contains the template for announcement email.
     *
     * @deprecated Starting with version 2.10 this parameter is no longer used. You must use
     *             {@link #announcementDirectory} instead.
     */
    @Parameter
    private File templateOutputDirectory;

    /**
     * Recipient email address.
     */
    @Parameter( required = true )
    private List<Object> toAddresses;

    /**
     * Recipient cc email address.
     *
     * @since 2.5
     */
    @Parameter
    private List<Object> ccAddresses;

    /**
     * Recipient bcc email address.
     *
     * @since 2.5
     */
    @Parameter
    private List<Object> bccAddresses;

    /**
     * The username used to send the email.
     */
    @Parameter( property = "changes.username" )
    private String username;

    private ProjectJavamailMailSender mailer = new ProjectJavamailMailSender();

    public void execute() throws MojoExecutionException
    {
        // Fail build fast if it is using deprecated parameters
        if ( templateOutputDirectory != null )
        {
            throw new MojoExecutionException( "You are using the old parameter 'templateOutputDirectory'. "
                + "You must use 'announcementDirectory' instead." );
        }

        // Run only at the execution root
        if ( runOnlyAtExecutionRoot && !isThisTheExecutionRoot() )
        {
            getLog().info( "Skipping the announcement mail in this project because it's not the Execution Root" );
        }
        else
        {
            File file = new File( announcementDirectory, announcementFile );

            ConsoleLogger logger = new ConsoleLogger( Logger.LEVEL_INFO, "base" );

            if ( getLog().isDebugEnabled() )
            {
                logger.setThreshold( Logger.LEVEL_DEBUG );
            }

            mailer.enableLogging( logger );

            mailer.setSmtpHost( getSmtpHost() );

            mailer.setSmtpPort( getSmtpPort() );

            mailer.setSslMode( sslMode, startTls );

            if ( username != null )
            {
                mailer.setUsername( username );
            }

            if ( password != null )
            {
                mailer.setPassword( password );
            }

            mailer.initialize();

            if ( getLog().isDebugEnabled() )
            {
                getLog().debug( "fromDeveloperId: " + getFromDeveloperId() );
            }

            if ( file.isFile() )
            {
                getLog().info( "Connecting to Host: " + getSmtpHost() + ":" + getSmtpPort() );

                sendMessage();
            }
            else
            {
                throw new MojoExecutionException( "Announcement file " + file + " not found..." );
            }
        }
    }

    /**
     * Send the email.
     *
     * @throws MojoExecutionException if the mail could not be sent
     */
    protected void sendMessage()
        throws MojoExecutionException
    {
        File file = new File( announcementDirectory, announcementFile );
        String email = "";
        final MailSender ms = getActualMailSender();
        final String fromName = ms.getName();
        final String fromAddress = ms.getEmail();
        if ( fromAddress == null || fromAddress.equals( "" ) )
        {
            throw new MojoExecutionException( "Invalid mail sender: name and email is mandatory (" + ms + ")." );
        }
        getLog().info( "Using this sender for email announcement: " + fromAddress + " < " + fromName + " > " );
        try
        {
            MailMessage mailMsg = new MailMessage();
            mailMsg.setSubject( getSubject() );
            mailMsg.setContent( readAnnouncement( file ) );
            mailMsg.setContentType( this.mailContentType );
            mailMsg.setFrom( fromAddress, fromName );

            for ( Object o1 : getToAddresses() )
            {
                email = o1.toString();
                getLog().info( "Sending mail to " + email + "..." );
                mailMsg.addTo( email, "" );
            }

            if ( getCcAddresses() != null )
            {
                for ( Object o : getCcAddresses() )
                {
                    email = o.toString();
                    getLog().info( "Sending cc mail to " + email + "..." );
                    mailMsg.addCc( email, "" );
                }
            }

            if ( getBccAddresses() != null )
            {
                for ( Object o : getBccAddresses() )
                {
                    email = o.toString();
                    getLog().info( "Sending bcc mail to " + email + "..." );
                    mailMsg.addBcc( email, "" );
                }
            }

            mailer.send( mailMsg );
            getLog().info( "Sent..." );
        }
        catch ( MailSenderException e )
        {
            throw new MojoExecutionException( "Failed to send email < " + email + " >", e );
        }
    }

    /**
     * Read the content of the generated announcement file.
     *
     * @param file the file to be read
     * @return Return the announcement text
     * @throws MojoExecutionException if the file could not be found, or if the encoding is unsupported
     */
    protected String readAnnouncement( File file )
        throws MojoExecutionException
    {
        InputStreamReader reader = null;
        try
        {
            if ( StringUtils.isEmpty( templateEncoding ) )
            {
                templateEncoding = ReaderFactory.FILE_ENCODING;
                getLog().warn( "File encoding has not been set, using platform encoding '" + templateEncoding
                                   + "', i.e. build is platform dependent!" );

            }

            reader = new InputStreamReader( new FileInputStream( file ), templateEncoding );
            final String announcement = IOUtil.toString( reader );
            reader.close();
            reader = null;
            return announcement;
        }
        catch ( FileNotFoundException fnfe )
        {
            throw new MojoExecutionException( "File not found. " + file );
        }
        catch ( UnsupportedEncodingException uee )
        {
            throw new MojoExecutionException( "Unsupported encoding: '" + templateEncoding + "'" );
        }
        catch ( IOException ioe )
        {
            throw new MojoExecutionException( "Failed to read the announcement file.", ioe );
        }
        finally
        {
            IOUtil.close( reader );
        }
    }

    /**
     * Returns the identify of the mail sender according to the plugin's configuration:
     * <ul>
     * <li>if the <tt>mailSender</tt> parameter is set, it is returned</li>
     * <li>if no <tt>fromDeveloperId</tt> is set, the first developer in the list is returned</li>
     * <li>if a <tt>fromDeveloperId</tt> is set, the developer with that id is returned</li>
     * <li>if the developers list is empty or if the specified id does not exist, an exception is thrown</li>
     * </ul>
     *
     * @return the mail sender to use
     * @throws MojoExecutionException if the mail sender could not be retrieved
     */
    protected MailSender getActualMailSender()
        throws MojoExecutionException
    {
        if ( senderString != null )
        {
            try
            {
                InternetAddress ia = new InternetAddress( senderString, true );
                return new MailSender( ia.getPersonal(), ia.getAddress() );
            }
            catch ( AddressException e )
            {
                throw new MojoExecutionException( "Invalid value for change.sender: ", e );
            }
        }
        if ( mailSender != null && mailSender.getEmail() != null )
        {
            return mailSender;
        }
        else if ( from == null || from.isEmpty() )
        {
            throw new MojoExecutionException( "The <developers> section in your pom should not be empty. "
                + "Add a <developer> entry or set the mailSender parameter." );
        }
        else if ( fromDeveloperId == null )
        {
            final Developer dev = from.get( 0 );
            return new MailSender( dev.getName(), dev.getEmail() );
        }
        else
        {
            for ( Developer developer : from )
            {
                if ( fromDeveloperId.equals( developer.getId() ) )
                {
                    return new MailSender( developer.getName(), developer.getEmail() );
                }
            }
            throw new MojoExecutionException( "Missing developer with id '" + fromDeveloperId
                + "' in the <developers> section in your pom." );
        }
    }

    // ================================
    // announcement-mail accessors
    // ================================

    public List<Object> getBccAddresses()
    {
        return bccAddresses;
    }

    public void setBccAddresses( List<Object> bccAddresses )
    {
        this.bccAddresses = bccAddresses;
    }

    public List<Object> getCcAddresses()
    {
        return ccAddresses;
    }

    public void setCcAddresses( List<Object> ccAddresses )
    {
        this.ccAddresses = ccAddresses;
    }

    public List<Developer> getFrom()
    {
        return from;
    }

    public void setFrom( List<Developer> from )
    {
        this.from = from;
    }

    public String getFromDeveloperId()
    {
        return fromDeveloperId;
    }

    public void setFromDeveloperId( String fromDeveloperId )
    {
        this.fromDeveloperId = fromDeveloperId;
    }

    public MailSender getMailSender()
    {
        return mailSender;
    }

    public void setMailSender( MailSender mailSender )
    {
        this.mailSender = mailSender;
    }

    public String getPassword()
    {
        return password;
    }

    public void setPassword( String password )
    {
        this.password = password;
    }

    public MavenProject getProject()
    {
        return project;
    }

    public void setProject( MavenProject project )
    {
        this.project = project;
    }

    public String getSmtpHost()
    {
        return smtpHost;
    }

    public void setSmtpHost( String smtpHost )
    {
        this.smtpHost = smtpHost;
    }

    public int getSmtpPort()
    {
        return smtpPort;
    }

    public void setSmtpPort( int smtpPort )
    {
        this.smtpPort = smtpPort;
    }

    public boolean isSslMode()
    {
        return sslMode;
    }

    public void setSslMode( boolean sslMode )
    {
        this.sslMode = sslMode;
    }

    public boolean isStartTls()
    {
        return startTls;
    }

    public void setStartTls( boolean startTls )
    {
        this.startTls = startTls;
    }

    public String getSubject()
    {
        return subject;
    }

    public void setSubject( String subject )
    {
        this.subject = subject;
    }

    public String getAnnouncementFile()
    {
        return announcementFile;
    }

    public void setAnnouncementFile( String announcementFile )
    {
        this.announcementFile = announcementFile;
    }

    public File getAnnouncementDirectory()
    {
        return announcementDirectory;
    }

    public void setAnnouncementDirectory( File announcementDirectory )
    {
        this.announcementDirectory = announcementDirectory;
    }

    public List<Object> getToAddresses()
    {
        return toAddresses;
    }

    public void setToAddresses( List<Object> toAddresses )
    {
        this.toAddresses = toAddresses;
    }

    public String getUsername()
    {
        return username;
    }

    public void setUsername( String username )
    {
        this.username = username;
    }
}