View Javadoc

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          // the pattern specfied for javamail uses two wild card characters, "%"
72          // and "*". The "%" matches
73          // and character except hierarchy separators. Since we have a flag
74          // hierarchy, "%" and "*" are
75          // essentially the same. If we convert the "%" into "*", we can just
76          // treat this as a wildmat
77          // formatted pattern and pass this on to the server rather than having
78          // to read everything and
79          // process the strings on the client side.
80  
81          pattern = pattern.replace('%', '*');
82  
83          // if we're not supposed to list everything, then just filter the list
84          // of subscribed groups.
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         // the pattern specfied for javamail uses two wild card characters, "%"
103         // and "*". The "%" matches
104         // and character except hierarchy separators. Since we have a flag
105         // hierarchy, "%" and "*" are
106         // essentially the same. If we convert the "%" into "*", we can just
107         // treat this as a wildmat
108         // formatted pattern and pass this on to the server rather than having
109         // to read everything and
110         // process the strings on the client side.
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         // if the LIST ACTIVE command isn't supported,
130         if (reply.getCode() == NNTPReply.COMMAND_NOT_RECOGNIZED) {
131             // only way to list all is to retrieve all and filter.
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         // get the response back from the server and process each returned group
138         // name.
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         // get the response back from the server and process each returned group
166         // name.
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             // does this match our pattern? Add to the list
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                 // does this match our pattern? Add to the list
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         // create a new group folder and return
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         // middle match sections...because these are separated by wildcards, if
254         // they appear in
255         // sequence in the string, it is a match.
256         List matchSections = new ArrayList();
257 
258         // just a "*" match, so everything is true
259         boolean matchAny = false;
260 
261         // no wildcards, so this must be an exact match.
262         String exactMatch = null;
263 
264         // a leading section which must be at the beginning
265         String firstSection = null;
266 
267         // a trailing section which must be at the end of the string.
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             // handle the easy cases first
280 
281             // single wild card?
282             if (pattern.equals("*")) {
283                 matchAny = true;
284                 return;
285             }
286 
287             // find the first wild card
288             int wildcard = pattern.indexOf('*');
289 
290             // no wild card at all?
291             if (wildcard == -1) {
292                 exactMatch = pattern;
293                 return;
294             }
295 
296             // pattern not begin with a wildcard? We need to pull off the
297             // leading section
298             if (!pattern.startsWith("*")) {
299                 firstSection = pattern.substring(0, wildcard);
300                 section = wildcard + 1;
301                 // this could be "yada*", so we could be done.
302                 if (section >= pattern.length()) {
303                     return;
304                 }
305             }
306 
307             // now parse off the middle sections, making sure to handle the end
308             // condition correctly.
309             while (section < pattern.length()) {
310                 // find the next wildcard position
311                 wildcard = pattern.indexOf('*', section);
312                 if (wildcard == -1) {
313                     // not found, we're at the end of the pattern. We need to
314                     // match on the end.
315                     lastSection = pattern.substring(section);
316                     return;
317                 }
318                 // we could have a null section, which we'll just ignore.
319                 else if (wildcard == section) {
320                     // step over the wild card
321                     section++;
322                 } else {
323                     // pluck off the next section
324                     matchSections.add(pattern.substring(section, wildcard));
325                     // step over the wild card character and check if we've
326                     // reached the end.
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             // handle the easy cases first
343 
344             // full wildcard? Always matches
345             if (matchAny) {
346                 return true;
347             }
348 
349             // required exact matches are easy.
350             if (exactMatch != null) {
351                 return exactMatch.equals(name);
352             }
353 
354             int span = 0;
355 
356             // must match the beginning?
357             if (firstSection != null) {
358                 // if it doesn't start with that, it can't be true.
359                 if (!name.startsWith(firstSection)) {
360                     return false;
361                 }
362 
363                 // we do all additional matching activity from here.
364                 span = firstSection.length();
365             }
366 
367             // scan for each of the sections along the string
368             for (int i = 1; i < matchSections.size(); i++) {
369                 // if a section is not found, this is false
370 
371                 String nextMatch = (String) matchSections.get(i);
372                 int nextLocation = name.indexOf(nextMatch, span);
373                 if (nextLocation == -1) {
374                     return false;
375                 }
376                 // step over that one
377                 span = nextMatch.length() + nextLocation;
378             }
379 
380             // we've matched everything up to this point, now check to see if
381             // need an end match
382             if (lastSection != null) {
383                 // we need to have at least the number of characters of the end
384                 // string left, else this fails.
385                 if (name.length() - span < lastSection.length()) {
386                     return false;
387                 }
388 
389                 // ok, make sure we end with this string
390                 return name.endsWith(lastSection);
391             }
392 
393             // no falsies, this must be the truth.
394             return true;
395         }
396     }
397 }