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 }