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