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.util.ArrayList;
023 import java.util.Iterator;
024 import java.util.List;
025
026 import javax.mail.Folder;
027 import javax.mail.MessagingException;
028
029 import org.apache.geronimo.javamail.store.nntp.newsrc.NNTPNewsrcGroup;
030 import org.apache.geronimo.javamail.transport.nntp.NNTPReply;
031 import org.apache.geronimo.mail.util.SessionUtil;
032
033 /**
034 * The base NNTP implementation of the javax.mail.Folder This is a base class
035 * for both the Root NNTP server and each NNTP group folder.
036 *
037 * @see javax.mail.Folder
038 *
039 * @version $Rev: 437941 $
040 */
041 public class NNTPRootFolder extends NNTPFolder {
042 protected static final String NNTP_LISTALL = "mail.nntp.listall";
043
044 /**
045 * Construct the NNTPRootFolder.
046 *
047 * @param store
048 * The owning Store.
049 * @param name
050 * The folder name (by default, this is the server host name).
051 * @param fullName
052 * The fullName to use for this server (derived from welcome
053 * string).
054 */
055 protected NNTPRootFolder(NNTPStore store, String name, String fullName) {
056 super(store);
057
058 this.name = name;
059 this.fullName = fullName;
060 }
061
062 /**
063 * List the subfolders. For group folders, this is a meaningless so we throw
064 * a MethodNotSupportedException.
065 *
066 * @param pattern
067 * The folder pattern string.
068 *
069 * @return Never returns.
070 * @exception MessagingException
071 */
072 public synchronized Folder[] list(String pattern) throws MessagingException {
073 // the pattern specfied for javamail uses two wild card characters, "%"
074 // and "*". The "%" matches
075 // and character except hierarchy separators. Since we have a flag
076 // hierarchy, "%" and "*" are
077 // essentially the same. If we convert the "%" into "*", we can just
078 // treat this as a wildmat
079 // formatted pattern and pass this on to the server rather than having
080 // to read everything and
081 // process the strings on the client side.
082
083 pattern = pattern.replace('%', '*');
084
085 // if we're not supposed to list everything, then just filter the list
086 // of subscribed groups.
087 if (SessionUtil.getBooleanProperty(NNTP_LISTALL, false)) {
088 return filterActiveGroups(pattern);
089 } else {
090 return filterSubscribedGroups(pattern);
091 }
092 }
093
094 /**
095 * Retrieve the list of subscribed folders that match the given pattern
096 * string.
097 *
098 * @param pattern
099 * 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 }