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 }