View Javadoc

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