001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019
020 package org.apache.geronimo.javamail.store.nntp;
021
022 import java.io.ByteArrayOutputStream;
023 import java.io.IOException;
024 import java.io.InputStream;
025 import java.util.Enumeration;
026
027 import javax.mail.Flags;
028 import javax.mail.IllegalWriteException;
029 import javax.mail.MessagingException;
030 import javax.mail.Session;
031 import javax.mail.internet.InternetHeaders;
032 import javax.mail.internet.MimeMessage;
033
034 import org.apache.geronimo.javamail.transport.nntp.NNTPConnection;
035 import org.apache.geronimo.javamail.transport.nntp.NNTPReply;
036 import org.apache.geronimo.javamail.transport.nntp.StringListInputStream;
037
038 /**
039 * NNTP implementation of javax.mail.internet.MimeMessage
040 *
041 * Only the most basic information is given and Message objects created here is
042 * a light-weight reference to the actual Message As per the JavaMail spec items
043 * from the actual message will get filled up on demand
044 *
045 * If some other items are obtained from the server as a result of one call,
046 * then the other details are also processed and filled in. For ex if RETR is
047 * called then header information will also be processed in addition to the
048 * content
049 *
050 * @version $Rev: 437941 $ $Date: 2006-08-28 23:56:02 -0400 (Mon, 28 Aug 2006) $
051 */
052 public class NNTPMessage extends MimeMessage {
053 // the server message identifer
054 String messageID = null;
055
056 // our attached session
057 protected Session session;
058
059 // the Store we're stored in (which manages the connection and other stuff).
060 protected NNTPStore store;
061
062 // our active connection.
063 protected NNTPConnection connection;
064
065 // used to force loading of headers
066 protected boolean headersLoaded = false;
067
068 // use to force content loading
069 protected boolean contentLoaded = false;
070
071 /**
072 * Contruct an NNTPMessage instance.
073 *
074 * @param folder
075 * The hosting folder for the message.
076 * @param store
077 * The Store owning the article (and folder).
078 * @param msgnum
079 * The article message number.
080 * @param messageID
081 * The article messageID (as assigned by the server).
082 *
083 * @exception MessagingException
084 */
085 NNTPMessage(NNTPFolder folder, NNTPStore store, int msgnum, String messageID) throws MessagingException {
086 super(folder, msgnum);
087 this.messageID = messageID;
088 this.store = store;
089 this.session = ((NNTPStore) store).getSession();
090 // get the active connection from the store...all commands are sent
091 // there
092 this.connection = ((NNTPStore) store).getConnection();
093
094 // get our flag set from the folder.
095 flags = folder.getPermanentFlags();
096 // now check our initial SEEN state and set the flags appropriately
097 if (folder.isSeen(msgnum)) {
098 flags.add(Flags.Flag.SEEN);
099 } else {
100 flags.remove(Flags.Flag.SEEN);
101 }
102 }
103
104 /**
105 * Retrieve the size of the message content. The content will be retrieved
106 * from the server, if necessary.
107 *
108 * @return The size of the content.
109 * @exception MessagingException
110 */
111 public int getSize() throws MessagingException {
112 // make sure we've retrieved the message content and continue with the
113 // superclass version.
114 loadContent();
115 return super.getSize();
116 }
117
118 /**
119 * Get a line count for the NNTP message. This is potentially stored in the
120 * Lines article header. If not there, we return a default of -1.
121 *
122 * @return The header line count estimate, or -1 if not retrieveable.
123 * @exception MessagingException
124 */
125 public int getLineCount() throws MessagingException {
126 String[] headers = getHeader("Lines");
127
128 // hopefully, there's only a single one of these. No sensible way of
129 // interpreting
130 // multiples.
131 if (headers.length == 1) {
132 try {
133 return Integer.parseInt(headers[0].trim());
134
135 } catch (NumberFormatException e) {
136 // ignore
137 }
138 }
139 // dunno...and let them know I don't know.
140 return -1;
141 }
142
143 /**
144 * @see javax.mail.internet.MimeMessage#getContentStream()
145 */
146 protected InputStream getContentStream() throws MessagingException {
147 // get the article information.
148 loadArticle();
149 return super.getContentStream();
150 }
151
152 /***************************************************************************
153 * Following is a set of methods that deal with headers These methods are
154 * just overrides on the superclass methods to allow lazy loading of the
155 * header information.
156 **************************************************************************/
157
158 public String[] getHeader(String name) throws MessagingException {
159 loadHeaders();
160 return headers.getHeader(name);
161 }
162
163 public String getHeader(String name, String delimiter) throws MessagingException {
164 loadHeaders();
165 return headers.getHeader(name, delimiter);
166 }
167
168 public Enumeration getAllHeaders() throws MessagingException {
169 loadHeaders();
170 return headers.getAllHeaders();
171 }
172
173 public Enumeration getMatchingHeaders(String[] names) throws MessagingException {
174 loadHeaders();
175 return headers.getMatchingHeaders(names);
176 }
177
178 public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException {
179 loadHeaders();
180 return headers.getNonMatchingHeaders(names);
181 }
182
183 public Enumeration getAllHeaderLines() throws MessagingException {
184 loadHeaders();
185 return headers.getAllHeaderLines();
186 }
187
188 public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
189 loadHeaders();
190 return headers.getMatchingHeaderLines(names);
191 }
192
193 public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
194 loadHeaders();
195 return headers.getNonMatchingHeaderLines(names);
196 }
197
198 // the following are overrides for header modification methods. These
199 // messages are read only,
200 // so the headers cannot be modified.
201 public void addHeader(String name, String value) throws MessagingException {
202 throw new IllegalWriteException("NNTP messages are read-only");
203 }
204
205 public void setHeader(String name, String value) throws MessagingException {
206 throw new IllegalWriteException("NNTP messages are read-only");
207 }
208
209 public void removeHeader(String name) throws MessagingException {
210 throw new IllegalWriteException("NNTP messages are read-only");
211 }
212
213 public void addHeaderLine(String line) throws MessagingException {
214 throw new IllegalWriteException("IMAP messages are read-only");
215 }
216
217 /**
218 * We cannot modify these messages
219 */
220 public void saveChanges() throws MessagingException {
221 throw new IllegalWriteException("NNTP messages are read-only");
222 }
223
224 /**
225 * Retrieve the message headers from the NNTP server.
226 *
227 * @exception MessagingException
228 */
229 public void loadHeaders() throws MessagingException {
230 // don't retrieve if already loaded.
231 if (headersLoaded) {
232 return;
233 }
234
235 NNTPReply reply = connection.sendCommand("HEAD " + messageID, NNTPReply.HEAD_FOLLOWS);
236
237 if (reply.getCode() == NNTPReply.HEAD_FOLLOWS) {
238 try {
239 // wrap a stream around the reply data and read as headers.
240 updateHeaders(new StringListInputStream(reply.getData()));
241 } catch (IOException e) {
242 throw new MessagingException("Error retrieving article headers from server", e);
243 }
244 } else {
245 throw new MessagingException("Error retrieving article headers from server: " + reply);
246 }
247 }
248
249 /**
250 * Update the message headers from an input stream.
251 *
252 * @param in
253 * The InputStream source for the header information.
254 *
255 * @exception MessagingException
256 */
257 public void updateHeaders(InputStream in) throws MessagingException {
258 // wrap a stream around the reply data and read as headers.
259 headers = new InternetHeaders(in);
260 headersLoaded = true;
261 }
262
263 /**
264 * Load just the message content from the NNTP server.
265 *
266 * @exception MessagingException
267 */
268 public void loadContent() throws MessagingException {
269 if (contentLoaded) {
270 return;
271 }
272
273 NNTPReply reply = connection.sendCommand("BODY " + messageID, NNTPReply.BODY_FOLLOWS);
274
275 if (reply.getCode() == NNTPReply.BODY_FOLLOWS) {
276 try {
277 InputStream in = new StringListInputStream(reply.getData());
278 updateContent(in);
279 } catch (IOException e) {
280 throw new MessagingException("Error retrieving article body from server", e);
281 }
282 } else {
283 throw new MessagingException("Error retrieving article body from server: " + reply);
284 }
285 }
286
287 /**
288 * Load the entire article from the NNTP server. This updates both the
289 * headers and the content.
290 *
291 * @exception MessagingException
292 */
293 public void loadArticle() throws MessagingException {
294 // if the headers are already loaded, retrieve the content portion.
295 if (headersLoaded) {
296 loadContent();
297 return;
298 }
299
300 // we need to retrieve everything.
301 NNTPReply reply = connection.sendCommand("ARTICLE " + messageID, NNTPReply.ARTICLE_FOLLOWS);
302
303 if (reply.getCode() == NNTPReply.ARTICLE_FOLLOWS) {
304 try {
305 InputStream in = new StringListInputStream(reply.getData());
306 // update both the headers and the content.
307 updateHeaders(in);
308 updateContent(in);
309 } catch (IOException e) {
310 throw new MessagingException("Error retrieving article from server", e);
311 }
312 } else {
313 throw new MessagingException("Error retrieving article from server: " + reply);
314 }
315 }
316
317 /**
318 * Update the article content from an input stream.
319 *
320 * @param in
321 * The content data source.
322 *
323 * @exception MessagingException
324 */
325 public void updateContent(InputStream in) throws MessagingException {
326 try {
327 ByteArrayOutputStream out = new ByteArrayOutputStream();
328
329 byte[] buffer = new byte[4096];
330
331 // copy the content data from the stream into a byte buffer for the
332 // content.
333 while (true) {
334 int read = in.read(buffer);
335 if (read == -1) {
336 break;
337 }
338 out.write(buffer, 0, read);
339 }
340
341 content = out.toByteArray();
342 contentLoaded = true;
343 } catch (IOException e) {
344 throw new MessagingException("Error retrieving message body from server", e);
345 }
346 }
347
348 /**
349 * Get the server assigned messageid for the article.
350 *
351 * @return The server assigned message id.
352 */
353 public String getMessageId() {
354 return messageID;
355 }
356
357 /**
358 * Override of setFlags(). We need to ensure that if the SEEN flag is set or
359 * cleared, that the newsrc file correctly reflects the current state.
360 *
361 * @param flag
362 * The flag being set.
363 * @param newvalue
364 * The new flag value.
365 *
366 * @exception MessagingException
367 */
368 public void setFlags(Flags flag, boolean newvalue) throws MessagingException {
369 // if this is the SEEN flag, make sure we shadow this in the newsrc
370 // file.
371 if (flag.contains(Flags.Flag.SEEN)) {
372 ((NNTPFolder) folder).setSeen(msgnum, newvalue);
373 }
374 // have the superclass do the real flag setting.
375 super.setFlags(flag, newvalue);
376 }
377 }