blob: ec399c44029c1cba9fb7a4b3d63903c328038454 [file] [log] [blame]
/*
* 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.
*/
package org.apache.geronimo.javamail.transport.nntp;
import java.io.PrintStream;
import java.util.ArrayList;
import javax.mail.Address;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.URLName;
import javax.mail.event.TransportEvent;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.NewsAddress;
import org.apache.geronimo.javamail.util.ProtocolProperties;
/**
* Simple implementation of NNTP transport. Just does plain RFC977-ish delivery.
* <p/> There is no way to indicate failure for a given recipient (it's possible
* to have a recipient address rejected). The sun impl throws exceptions even if
* others successful), but maybe we do a different way... <p/>
*
* @version $Rev$ $Date$
*/
public class NNTPTransport extends Transport {
/**
* property keys for protocol properties.
*/
protected static final String NNTP_FROM = "from";
protected static final int DEFAULT_NNTP_PORT = 119;
protected static final int DEFAULT_NNTP_SSL_PORT = 563;
// our accessor for protocol properties and the holder of
// protocol-specific information
protected ProtocolProperties props;
// our active connection object (shared code with the NNTPStore).
protected NNTPConnection connection;
/**
* Normal constructor for an NNTPTransport() object. This constructor is
* used to build a transport instance for the "smtp" protocol.
*
* @param session
* The attached session.
* @param name
* An optional URLName object containing target information.
*/
public NNTPTransport(Session session, URLName name) {
this(session, name, "nntp-post", DEFAULT_NNTP_PORT, false);
}
/**
* Common constructor used by the POP3Store and POP3SSLStore classes
* to do common initialization of defaults.
*
* @param session
* The host session instance.
* @param name
* The URLName of the target.
* @param protocol
* The protocol type ("pop3"). This helps us in
* retrieving protocol-specific session properties.
* @param defaultPort
* The default port used by this protocol. For pop3, this will
* be 110. The default for pop3 with ssl is 995.
* @param sslConnection
* Indicates whether an SSL connection should be used to initial
* contact the server. This is different from the STARTTLS
* support, which switches the connection to SSL after the
* initial startup.
*/
protected NNTPTransport(Session session, URLName name, String protocol, int defaultPort, boolean sslConnection) {
super(session, name);
// create the protocol property holder. This gives an abstraction over the different
// flavors of the protocol.
props = new ProtocolProperties(session, protocol, sslConnection, defaultPort);
// the connection manages connection for the transport
connection = new NNTPConnection(props);
}
/**
* Do the protocol connection for an NNTP transport. This handles server
* authentication, if possible. Returns false if unable to connect to the
* server.
*
* @param host
* The target host name.
* @param port
* The server port number.
* @param user
* The authentication user (if any).
* @param password
* The server password. Might not be sent directly if more
* sophisticated authentication is used.
*
* @return true if we were able to connect to the server properly, false for
* any failures.
* @exception MessagingException
*/
protected boolean protocolConnect(String host, int port, String username, String password)
throws MessagingException {
// the connection pool handles all of the details here.
return connection.protocolConnect(host, port, username, password);
}
/**
* Send a message to multiple addressees.
*
* @param message
* The message we're sending.
* @param addresses
* An array of addresses to send to.
*
* @exception MessagingException
*/
public void sendMessage(Message message, Address[] addresses) throws MessagingException {
if (!isConnected()) {
throw new IllegalStateException("Not connected");
}
if (!connection.isPostingAllowed()) {
throw new MessagingException("Posting disabled for host server");
}
// don't bother me w/ null messages or no addreses
if (message == null) {
throw new MessagingException("Null message");
}
// NNTP only handles instances of MimeMessage, not the more general
// message case.
if (!(message instanceof MimeMessage)) {
throw new MessagingException("NNTP can only send MimeMessages");
}
// need to sort the from value out from a variety of sources.
InternetAddress from = null;
Address[] fromAddresses = message.getFrom();
// If the message has a From address set, we just use that. Otherwise,
// we set a From using
// the property version, if available.
if (fromAddresses == null || fromAddresses.length == 0) {
// the from value can be set explicitly as a property
String defaultFrom = props.getProperty(NNTP_FROM);
if (defaultFrom == null) {
message.setFrom(new InternetAddress(defaultFrom));
}
}
// we must have a message list.
if (addresses == null || addresses.length == 0) {
throw new MessagingException("Null or empty address array");
}
boolean haveGroup = false;
// enforce the requirement that all of the targets are NewsAddress
// instances.
for (int i = 0; i < addresses.length; i++) {
if (!(addresses[i] instanceof NewsAddress)) {
throw new MessagingException("Illegal NewsAddress " + addresses[i]);
}
}
// event notifcation requires we send lists of successes and failures
// broken down by category.
// The categories are:
//
// 1) addresses successfully processed.
// 2) addresses deemed valid, but had a processing failure that
// prevented sending.
// 3) addressed deemed invalid (basically all other processing
// failures).
ArrayList sentAddresses = new ArrayList();
ArrayList unsentAddresses = new ArrayList();
ArrayList invalidAddresses = new ArrayList();
boolean sendFailure = false;
// now try to post this message to the different news groups.
for (int i = 0; i < addresses.length; i++) {
try {
// select the target news group
NNTPReply reply = connection.selectGroup(((NewsAddress) addresses[i]).getNewsgroup());
if (reply.getCode() != NNTPReply.GROUP_SELECTED) {
invalidAddresses.add(addresses[i]);
sendFailure = true;
} else {
// send data
connection.sendPost(message);
sentAddresses.add(addresses[i]);
}
} catch (MessagingException e) {
unsentAddresses.add(addresses[i]);
sendFailure = true;
}
}
// create our lists for notification and exception reporting from this
// point on.
Address[] sent = (Address[]) sentAddresses.toArray(new Address[0]);
Address[] unsent = (Address[]) unsentAddresses.toArray(new Address[0]);
Address[] invalid = (Address[]) invalidAddresses.toArray(new Address[0]);
if (sendFailure) {
// did we deliver anything at all?
if (sent.length == 0) {
// notify of the error.
notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED, sent, unsent, invalid, message);
} else {
// notify that we delivered at least part of this
notifyTransportListeners(TransportEvent.MESSAGE_PARTIALLY_DELIVERED, sent, unsent, invalid, message);
}
throw new MessagingException("Error posting NNTP message");
}
// notify our listeners of successful delivery.
notifyTransportListeners(TransportEvent.MESSAGE_DELIVERED, sent, unsent, invalid, message);
}
/**
* Close the connection. On completion, we'll be disconnected from the
* server and unable to send more data.
*
* @exception MessagingException
*/
public void close() throws MessagingException {
// This is done to ensure proper event notification.
super.close();
// NB: We reuse the connection if asked to reconnect
connection.close();
}
}