1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20 package org.apache.geronimo.javamail.store.nntp; 21 22 import java.util.ArrayList; 23 import java.util.Iterator; 24 import java.util.List; 25 26 import javax.mail.Folder; 27 import javax.mail.MessagingException; 28 29 import org.apache.geronimo.javamail.store.nntp.newsrc.NNTPNewsrcGroup; 30 import org.apache.geronimo.javamail.transport.nntp.NNTPReply; 31 import org.apache.geronimo.mail.util.SessionUtil; 32 33 /** 34 * The base NNTP implementation of the javax.mail.Folder This is a base class 35 * for both the Root NNTP server and each NNTP group folder. 36 * 37 * @see javax.mail.Folder 38 * 39 * @version $Rev: 437941 $ 40 */ 41 public class NNTPRootFolder extends NNTPFolder { 42 protected static final String NNTP_LISTALL = "mail.nntp.listall"; 43 44 /** 45 * Construct the NNTPRootFolder. 46 * 47 * @param store 48 * The owning Store. 49 * @param name 50 * The folder name (by default, this is the server host name). 51 * @param fullName 52 * The fullName to use for this server (derived from welcome 53 * string). 54 */ 55 protected NNTPRootFolder(NNTPStore store, String name, String fullName) { 56 super(store); 57 58 this.name = name; 59 this.fullName = fullName; 60 } 61 62 /** 63 * List the subfolders. For group folders, this is a meaningless so we throw 64 * a MethodNotSupportedException. 65 * 66 * @param pattern 67 * The folder pattern string. 68 * 69 * @return Never returns. 70 * @exception MessagingException 71 */ 72 public synchronized Folder[] list(String pattern) throws MessagingException { 73 // the pattern specfied for javamail uses two wild card characters, "%" 74 // and "*". The "%" matches 75 // and character except hierarchy separators. Since we have a flag 76 // hierarchy, "%" and "*" are 77 // essentially the same. If we convert the "%" into "*", we can just 78 // treat this as a wildmat 79 // formatted pattern and pass this on to the server rather than having 80 // to read everything and 81 // process the strings on the client side. 82 83 pattern = pattern.replace('%', '*'); 84 85 // if we're not supposed to list everything, then just filter the list 86 // of subscribed groups. 87 if (SessionUtil.getBooleanProperty(NNTP_LISTALL, false)) { 88 return filterActiveGroups(pattern); 89 } else { 90 return filterSubscribedGroups(pattern); 91 } 92 } 93 94 /** 95 * Retrieve the list of subscribed folders that match the given pattern 96 * string. 97 * 98 * @param pattern 99 * The pattern string used for the matching 100 * 101 * @return An array of matching folders from the subscribed list. 102 */ 103 public Folder[] listSubscribed(String pattern) throws MessagingException { 104 // the pattern specfied for javamail uses two wild card characters, "%" 105 // and "*". The "%" matches 106 // and character except hierarchy separators. Since we have a flag 107 // hierarchy, "%" and "*" are 108 // essentially the same. If we convert the "%" into "*", we can just 109 // treat this as a wildmat 110 // formatted pattern and pass this on to the server rather than having 111 // to read everything and 112 // process the strings on the client side. 113 114 pattern = pattern.replace('%', '*'); 115 116 return filterSubscribedGroups(pattern); 117 } 118 119 /** 120 * Retrieve the list of matching groups from the NNTP server using the LIST 121 * ACTIVE command. The server does the wildcard matching for us. 122 * 123 * @param pattern 124 * The pattern string (in wildmat format) used to match. 125 * 126 * @return An array of folders for the matching groups. 127 */ 128 protected Folder[] filterActiveGroups(String pattern) throws MessagingException { 129 NNTPReply reply = connection.sendCommand("LIST ACTIVE " + pattern, NNTPReply.LIST_FOLLOWS); 130 131 // if the LIST ACTIVE command isn't supported, 132 if (reply.getCode() == NNTPReply.COMMAND_NOT_RECOGNIZED) { 133 // only way to list all is to retrieve all and filter. 134 return filterAllGroups(pattern); 135 } else if (reply.getCode() != NNTPReply.LIST_FOLLOWS) { 136 throw new MessagingException("Error retrieving group list from NNTP server: " + reply); 137 } 138 139 // get the response back from the server and process each returned group 140 // name. 141 List groups = reply.getData(); 142 143 Folder[] folders = new Folder[groups.size()]; 144 for (int i = 0; i < groups.size(); i++) { 145 folders[i] = getFolder(getGroupName((String) groups.get(i))); 146 } 147 return folders; 148 } 149 150 /** 151 * Retrieve a list of all groups from the server and filter on the names. 152 * Not recommended for the usenet servers, as there are over 30000 groups to 153 * process. 154 * 155 * @param pattern 156 * The pattern string used for the selection. 157 * 158 * @return The Folders for the matching groups. 159 */ 160 protected Folder[] filterAllGroups(String pattern) throws MessagingException { 161 NNTPReply reply = connection.sendCommand("LIST", NNTPReply.LIST_FOLLOWS); 162 163 if (reply.getCode() != NNTPReply.LIST_FOLLOWS) { 164 throw new MessagingException("Error retrieving group list from NNTP server: " + reply); 165 } 166 167 // get the response back from the server and process each returned group 168 // name. 169 List groups = reply.getData(); 170 171 WildmatMatcher matcher = new WildmatMatcher(pattern); 172 173 List folders = new ArrayList(); 174 for (int i = 0; i < groups.size(); i++) { 175 String name = getGroupName((String) groups.get(i)); 176 // does this match our pattern? Add to the list 177 if (matcher.matches(name)) { 178 folders.add(getFolder(name)); 179 } 180 } 181 return (Folder[]) folders.toArray(new Folder[0]); 182 } 183 184 /** 185 * Return the set of groups from the newsrc subscribed groups list that 186 * match a given filter. 187 * 188 * @param pattern 189 * The selection pattern. 190 * 191 * @return The Folders for the matching groups. 192 */ 193 protected Folder[] filterSubscribedGroups(String pattern) throws MessagingException { 194 Iterator groups = ((NNTPStore) store).getNewsrcGroups(); 195 196 WildmatMatcher matcher = new WildmatMatcher(pattern); 197 198 List folders = new ArrayList(); 199 while (groups.hasNext()) { 200 NNTPNewsrcGroup group = (NNTPNewsrcGroup) groups.next(); 201 if (group.isSubscribed()) { 202 // does this match our pattern? Add to the list 203 if (matcher.matches(group.getName())) { 204 folders.add(getFolder(group.getName())); 205 } 206 } 207 } 208 return (Folder[]) folders.toArray(new Folder[0]); 209 } 210 211 /** 212 * Utility method for extracting a name from a group list response. 213 * 214 * @param response 215 * The response string. 216 * 217 * @return The group name. 218 */ 219 protected String getGroupName(String response) { 220 int blank = response.indexOf(' '); 221 return response.substring(0, blank).trim(); 222 } 223 224 /** 225 * Return whether this folder can hold just messages or also subfolders. 226 * Only the root folder can hold other folders, so it will need to override. 227 * 228 * @return Always returns Folder.HOLDS_FOLDERS. 229 * @exception MessagingException 230 */ 231 public int getType() throws MessagingException { 232 return HOLDS_FOLDERS; 233 } 234 235 /** 236 * Get a new folder from the root folder. This creates a new folder, which 237 * might not actually exist on the server. If the folder doesn't exist, an 238 * error will occur on folder open. 239 * 240 * @param name 241 * The name of the requested folder. 242 * 243 * @return A new folder object for this folder. 244 * @exception MessagingException 245 */ 246 public Folder getFolder(String name) throws MessagingException { 247 // create a new group folder and return 248 return new NNTPGroupFolder(this, (NNTPStore) store, name, ((NNTPStore) store).getNewsrcGroup(name)); 249 } 250 251 /** 252 * Utility class to do Wildmat pattern matching on folder names. 253 */ 254 class WildmatMatcher { 255 // middle match sections...because these are separated by wildcards, if 256 // they appear in 257 // sequence in the string, it is a match. 258 List matchSections = new ArrayList(); 259 260 // just a "*" match, so everything is true 261 boolean matchAny = false; 262 263 // no wildcards, so this must be an exact match. 264 String exactMatch = null; 265 266 // a leading section which must be at the beginning 267 String firstSection = null; 268 269 // a trailing section which must be at the end of the string. 270 String lastSection = null; 271 272 /** 273 * Create a wildmat pattern matcher. 274 * 275 * @param pattern 276 * The wildmat pattern to apply to string matches. 277 */ 278 public WildmatMatcher(String pattern) { 279 int section = 0; 280 281 // handle the easy cases first 282 283 // single wild card? 284 if (pattern.equals("*")) { 285 matchAny = true; 286 return; 287 } 288 289 // find the first wild card 290 int wildcard = pattern.indexOf('*'); 291 292 // no wild card at all? 293 if (wildcard == -1) { 294 exactMatch = pattern; 295 return; 296 } 297 298 // pattern not begin with a wildcard? We need to pull off the 299 // leading section 300 if (!pattern.startsWith("*")) { 301 firstSection = pattern.substring(0, wildcard); 302 section = wildcard + 1; 303 // this could be "yada*", so we could be done. 304 if (section >= pattern.length()) { 305 return; 306 } 307 } 308 309 // now parse off the middle sections, making sure to handle the end 310 // condition correctly. 311 while (section < pattern.length()) { 312 // find the next wildcard position 313 wildcard = pattern.indexOf('*', section); 314 if (wildcard == -1) { 315 // not found, we're at the end of the pattern. We need to 316 // match on the end. 317 lastSection = pattern.substring(section); 318 return; 319 } 320 // we could have a null section, which we'll just ignore. 321 else if (wildcard == section) { 322 // step over the wild card 323 section++; 324 } else { 325 // pluck off the next section 326 matchSections.add(pattern.substring(section, wildcard)); 327 // step over the wild card character and check if we've 328 // reached the end. 329 section = wildcard + 1; 330 } 331 } 332 } 333 334 /** 335 * Test if a name string matches to parsed wildmat pattern. 336 * 337 * @param name 338 * The name to test. 339 * 340 * @return true if the string matches the pattern, false otherwise. 341 */ 342 public boolean matches(String name) { 343 344 // handle the easy cases first 345 346 // full wildcard? Always matches 347 if (matchAny) { 348 return true; 349 } 350 351 // required exact matches are easy. 352 if (exactMatch != null) { 353 return exactMatch.equals(name); 354 } 355 356 int span = 0; 357 358 // must match the beginning? 359 if (firstSection != null) { 360 // if it doesn't start with that, it can't be true. 361 if (!name.startsWith(firstSection)) { 362 return false; 363 } 364 365 // we do all additional matching activity from here. 366 span = firstSection.length(); 367 } 368 369 // scan for each of the sections along the string 370 for (int i = 1; i < matchSections.size(); i++) { 371 // if a section is not found, this is false 372 373 String nextMatch = (String) matchSections.get(i); 374 int nextLocation = name.indexOf(nextMatch, span); 375 if (nextLocation == -1) { 376 return false; 377 } 378 // step over that one 379 span = nextMatch.length() + nextLocation; 380 } 381 382 // we've matched everything up to this point, now check to see if 383 // need an end match 384 if (lastSection != null) { 385 // we need to have at least the number of characters of the end 386 // string left, else this fails. 387 if (name.length() - span < lastSection.length()) { 388 return false; 389 } 390 391 // ok, make sure we end with this string 392 return name.endsWith(lastSection); 393 } 394 395 // no falsies, this must be the truth. 396 return true; 397 } 398 } 399 }