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.james.nntpserver.repository;
21  
22  import org.apache.james.util.Base64;
23  
24  import java.io.File;
25  import java.io.FileInputStream;
26  import java.io.FileOutputStream;
27  import java.io.IOException;
28  import java.util.Date;
29  import java.util.Enumeration;
30  import java.util.Properties;
31  
32  /*** 
33   * ArticleIDRepository: contains one file for each article.
34   * the file name is Base64 encoded article ID
35   * The first line of the file is '# <create date of file>
36   * the rest of line have <newsgroup name>=<article number>
37   * Allows fast lookup of a message by message id.
38   *
39   * This class allows a process to iterate and synchronize messages with other NNTP Servers.
40   * This may be inefficient. It may be better to use an alternate, more 
41   * efficient process for synchronization and this class for sanity check.
42   *
43   */
44  public class ArticleIDRepository {
45  
46      /***
47       * The root of the repository in the file system
48       */
49      private final File root;
50  
51      /***
52       * The suffix appended to the articleIDs
53       */
54      private final String articleIDDomainSuffix;
55  
56      /***
57       * A counter of the number of article IDs
58       *
59       * TODO: Potentially serious threading problem here
60       */
61      private int counter = 0;
62  
63      ArticleIDRepository(File root,String articleIDDomainSuffix) {
64          this.root = root;
65          this.articleIDDomainSuffix = articleIDDomainSuffix;
66      }
67  
68      /***
69       * Generate a new article ID for use in the repository.
70       */
71      String generateArticleID() {
72          int idx = Math.abs(counter++);
73          StringBuffer idBuffer =
74              new StringBuffer(256)
75                      .append("<")
76                      .append(Thread.currentThread().hashCode())
77                      .append(".")
78                      .append(System.currentTimeMillis())
79                      .append(".")
80                      .append(idx)
81                      .append("@")
82                      .append(articleIDDomainSuffix)
83                      .append(">");
84          return idBuffer.toString();
85      }
86  
87      /***
88       * Add the article information to the repository.
89       *
90       * @param prop contains the newsgroup name and article number.
91       */
92      void addArticle(String articleID,Properties prop) throws IOException {
93          if ( articleID == null ) {
94              articleID = generateArticleID();
95          }
96          FileOutputStream fout = null;
97          try {
98              fout = new FileOutputStream(getFileFromID(articleID));
99              prop.store(fout,new Date().toString());
100         } finally {
101             if (fout != null) {
102                 fout.close();
103             }
104         }
105     }
106 
107     /***
108      * Returns the file in the repository corresponding to the specified
109      * article ID.
110      *
111      * @param articleID the article ID
112      *
113      * @return the repository file
114      */
115     File getFileFromID(String articleID) {
116         String b64Id;
117         try {
118             b64Id = removeCRLF(Base64.encodeAsString(articleID));
119         } catch (Exception e) {
120             throw new RuntimeException("This shouldn't happen: " + e);
121         }
122         return new File(root, b64Id);
123     }
124 
125     /***
126      * the base64 encode from javax.mail.internet.MimeUtility adds line
127      * feeds to the encoded stream.  This removes them, since we will
128      * use the String as a filename.
129      */
130     private static String removeCRLF(String str) {
131         StringBuffer buffer = new StringBuffer();
132         for (int i = 0; i < str.length(); i++) {
133             char c = str.charAt(i);
134             if (c != '\r' && c != '\n') {
135                 buffer.append(c);
136             }
137         }
138         return buffer.toString();
139     }
140 
141     /***
142      * Returns whether the article ID is in the repository
143      *
144      * @param articleID the article ID
145      *
146      * @return whether the article ID is in the repository
147      */
148     boolean isExists(String articleID) {
149         return ( articleID == null ) ? false : getFileFromID(articleID).exists();
150     }
151 
152     /***
153      * Get the article from the NNTP respository with the specified id.
154      *
155      * @param repo the NNTP repository where the article is stored
156      * @param id the id of the article to retrieve
157      *
158      * @return the article
159      *
160      * @throws IOException if the ID information cannot be loaded
161      */
162     NNTPArticle getArticle(NNTPRepository repo,String id) throws IOException {
163         File f = getFileFromID(id);
164         if ( f.exists() == false ) {
165             return null;
166         }
167         FileInputStream fin = null;
168         Properties prop = new Properties();
169         try {
170             fin = new FileInputStream(f);
171             prop.load(fin);
172         } finally {
173             if (fin != null) {
174                 fin.close();
175             }
176         }
177         Enumeration enumeration = prop.keys();
178         NNTPArticle article = null;
179         while ( article == null && enumeration.hasMoreElements() ) {
180             String groupName = (String)enumeration.nextElement();
181             int number = Integer.parseInt(prop.getProperty(groupName));
182             NNTPGroup group = repo.getGroup(groupName);
183             if ( group != null ) {
184                 article = group.getArticle(number);
185             }
186         }
187         return article;
188     }
189 }