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 }