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