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 }