blob: e57d146800d3fc3602d5ff68dc09e3f88b5cde58 [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.store.nntp;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import javax.mail.Flags;
import javax.mail.IllegalWriteException;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.InternetHeaders;
import javax.mail.internet.MimeMessage;
import org.apache.geronimo.javamail.transport.nntp.NNTPConnection;
import org.apache.geronimo.javamail.transport.nntp.NNTPReply;
import org.apache.geronimo.javamail.transport.nntp.StringListInputStream;
/**
* NNTP implementation of javax.mail.internet.MimeMessage
*
* Only the most basic information is given and Message objects created here is
* a light-weight reference to the actual Message As per the JavaMail spec items
* from the actual message will get filled up on demand
*
* If some other items are obtained from the server as a result of one call,
* then the other details are also processed and filled in. For ex if RETR is
* called then header information will also be processed in addition to the
* content
*
* @version $Rev$ $Date$
*/
public class NNTPMessage extends MimeMessage {
// the server message identifer
String messageID = null;
// our attached session
protected Session session;
// the Store we're stored in (which manages the connection and other stuff).
protected NNTPStore store;
// our active connection.
protected NNTPConnection connection;
// used to force loading of headers
protected boolean headersLoaded = false;
// use to force content loading
protected boolean contentLoaded = false;
/**
* Contruct an NNTPMessage instance.
*
* @param folder
* The hosting folder for the message.
* @param store
* The Store owning the article (and folder).
* @param msgnum
* The article message number.
* @param messageID
* The article messageID (as assigned by the server).
*
* @exception MessagingException
*/
NNTPMessage(NNTPFolder folder, NNTPStore store, int msgnum, String messageID) throws MessagingException {
super(folder, msgnum);
this.messageID = messageID;
this.store = store;
this.session = ((NNTPStore) store).getSession();
// get the active connection from the store...all commands are sent
// there
this.connection = ((NNTPStore) store).getConnection();
// get our flag set from the folder.
flags = folder.getPermanentFlags();
// now check our initial SEEN state and set the flags appropriately
if (folder.isSeen(msgnum)) {
flags.add(Flags.Flag.SEEN);
} else {
flags.remove(Flags.Flag.SEEN);
}
}
/**
* Retrieve the size of the message content. The content will be retrieved
* from the server, if necessary.
*
* @return The size of the content.
* @exception MessagingException
*/
public int getSize() throws MessagingException {
// make sure we've retrieved the message content and continue with the
// superclass version.
loadContent();
return super.getSize();
}
/**
* Get a line count for the NNTP message. This is potentially stored in the
* Lines article header. If not there, we return a default of -1.
*
* @return The header line count estimate, or -1 if not retrieveable.
* @exception MessagingException
*/
public int getLineCount() throws MessagingException {
String[] headers = getHeader("Lines");
// hopefully, there's only a single one of these. No sensible way of
// interpreting
// multiples.
if (headers.length == 1) {
try {
return Integer.parseInt(headers[0].trim());
} catch (NumberFormatException e) {
// ignore
}
}
// dunno...and let them know I don't know.
return -1;
}
/**
* @see javax.mail.internet.MimeMessage#getContentStream()
*/
protected InputStream getContentStream() throws MessagingException {
// get the article information.
loadArticle();
return super.getContentStream();
}
/***************************************************************************
* Following is a set of methods that deal with headers These methods are
* just overrides on the superclass methods to allow lazy loading of the
* header information.
**************************************************************************/
public String[] getHeader(String name) throws MessagingException {
loadHeaders();
return headers.getHeader(name);
}
public String getHeader(String name, String delimiter) throws MessagingException {
loadHeaders();
return headers.getHeader(name, delimiter);
}
public Enumeration getAllHeaders() throws MessagingException {
loadHeaders();
return headers.getAllHeaders();
}
public Enumeration getMatchingHeaders(String[] names) throws MessagingException {
loadHeaders();
return headers.getMatchingHeaders(names);
}
public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException {
loadHeaders();
return headers.getNonMatchingHeaders(names);
}
public Enumeration getAllHeaderLines() throws MessagingException {
loadHeaders();
return headers.getAllHeaderLines();
}
public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
loadHeaders();
return headers.getMatchingHeaderLines(names);
}
public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
loadHeaders();
return headers.getNonMatchingHeaderLines(names);
}
// the following are overrides for header modification methods. These
// messages are read only,
// so the headers cannot be modified.
public void addHeader(String name, String value) throws MessagingException {
throw new IllegalWriteException("NNTP messages are read-only");
}
public void setHeader(String name, String value) throws MessagingException {
throw new IllegalWriteException("NNTP messages are read-only");
}
public void removeHeader(String name) throws MessagingException {
throw new IllegalWriteException("NNTP messages are read-only");
}
public void addHeaderLine(String line) throws MessagingException {
throw new IllegalWriteException("IMAP messages are read-only");
}
/**
* We cannot modify these messages
*/
public void saveChanges() throws MessagingException {
throw new IllegalWriteException("NNTP messages are read-only");
}
/**
* Retrieve the message headers from the NNTP server.
*
* @exception MessagingException
*/
public void loadHeaders() throws MessagingException {
// don't retrieve if already loaded.
if (headersLoaded) {
return;
}
NNTPReply reply = connection.sendCommand("HEAD " + messageID, NNTPReply.HEAD_FOLLOWS);
if (reply.getCode() == NNTPReply.HEAD_FOLLOWS) {
try {
// wrap a stream around the reply data and read as headers.
updateHeaders(new StringListInputStream(reply.getData()));
} catch (IOException e) {
throw new MessagingException("Error retrieving article headers from server", e);
}
} else {
throw new MessagingException("Error retrieving article headers from server: " + reply);
}
}
/**
* Update the message headers from an input stream.
*
* @param in
* The InputStream source for the header information.
*
* @exception MessagingException
*/
public void updateHeaders(InputStream in) throws MessagingException {
// wrap a stream around the reply data and read as headers.
headers = new InternetHeaders(in);
headersLoaded = true;
}
/**
* Load just the message content from the NNTP server.
*
* @exception MessagingException
*/
public void loadContent() throws MessagingException {
if (contentLoaded) {
return;
}
NNTPReply reply = connection.sendCommand("BODY " + messageID, NNTPReply.BODY_FOLLOWS);
if (reply.getCode() == NNTPReply.BODY_FOLLOWS) {
try {
InputStream in = new StringListInputStream(reply.getData());
updateContent(in);
} catch (IOException e) {
throw new MessagingException("Error retrieving article body from server", e);
}
} else {
throw new MessagingException("Error retrieving article body from server: " + reply);
}
}
/**
* Load the entire article from the NNTP server. This updates both the
* headers and the content.
*
* @exception MessagingException
*/
public void loadArticle() throws MessagingException {
// if the headers are already loaded, retrieve the content portion.
if (headersLoaded) {
loadContent();
return;
}
// we need to retrieve everything.
NNTPReply reply = connection.sendCommand("ARTICLE " + messageID, NNTPReply.ARTICLE_FOLLOWS);
if (reply.getCode() == NNTPReply.ARTICLE_FOLLOWS) {
try {
InputStream in = new StringListInputStream(reply.getData());
// update both the headers and the content.
updateHeaders(in);
updateContent(in);
} catch (IOException e) {
throw new MessagingException("Error retrieving article from server", e);
}
} else {
throw new MessagingException("Error retrieving article from server: " + reply);
}
}
/**
* Update the article content from an input stream.
*
* @param in
* The content data source.
*
* @exception MessagingException
*/
public void updateContent(InputStream in) throws MessagingException {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
// copy the content data from the stream into a byte buffer for the
// content.
while (true) {
int read = in.read(buffer);
if (read == -1) {
break;
}
out.write(buffer, 0, read);
}
content = out.toByteArray();
contentLoaded = true;
} catch (IOException e) {
throw new MessagingException("Error retrieving message body from server", e);
}
}
/**
* Get the server assigned messageid for the article.
*
* @return The server assigned message id.
*/
public String getMessageId() {
return messageID;
}
/**
* Override of setFlags(). We need to ensure that if the SEEN flag is set or
* cleared, that the newsrc file correctly reflects the current state.
*
* @param flag
* The flag being set.
* @param newvalue
* The new flag value.
*
* @exception MessagingException
*/
public void setFlags(Flags flag, boolean newvalue) throws MessagingException {
// if this is the SEEN flag, make sure we shadow this in the newsrc
// file.
if (flag.contains(Flags.Flag.SEEN)) {
((NNTPFolder) folder).setSeen(msgnum, newvalue);
}
// have the superclass do the real flag setting.
super.setFlags(flag, newvalue);
}
}