View Javadoc

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 }