001: /****************************************************************
002: * Licensed to the Apache Software Foundation (ASF) under one *
003: * or more contributor license agreements. See the NOTICE file *
004: * distributed with this work for additional information *
005: * regarding copyright ownership. The ASF licenses this file *
006: * to you under the Apache License, Version 2.0 (the *
007: * "License"); you may not use this file except in compliance *
008: * with the License. You may obtain a copy of the License at *
009: * *
010: * http://www.apache.org/licenses/LICENSE-2.0 *
011: * *
012: * Unless required by applicable law or agreed to in writing, *
013: * software distributed under the License is distributed on an *
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
015: * KIND, either express or implied. See the License for the *
016: * specific language governing permissions and limitations *
017: * under the License. *
018: ****************************************************************/package org.apache.james.nntpserver.repository;
019:
020: import org.apache.james.util.Base64;
021:
022: import java.io.File;
023: import java.io.FileInputStream;
024: import java.io.FileOutputStream;
025: import java.io.IOException;
026: import java.util.Date;
027: import java.util.Enumeration;
028: import java.util.Properties;
029:
030: /**
031: * ArticleIDRepository: contains one file for each article.
032: * the file name is Base64 encoded article ID
033: * The first line of the file is '# <create date of file>
034: * the rest of line have <newsgroup name>=<article number>
035: * Allows fast lookup of a message by message id.
036: *
037: * This class allows a process to iterate and synchronize messages with other NNTP Servers.
038: * This may be inefficient. It may be better to use an alternate, more
039: * efficient process for synchronization and this class for sanity check.
040: *
041: */
042: public class ArticleIDRepository {
043:
044: /**
045: * The root of the repository in the file system
046: */
047: private final File root;
048:
049: /**
050: * The suffix appended to the articleIDs
051: */
052: private final String articleIDDomainSuffix;
053:
054: /**
055: * A counter of the number of article IDs
056: *
057: * TODO: Potentially serious threading problem here
058: */
059: private int counter = 0;
060:
061: ArticleIDRepository(File root, String articleIDDomainSuffix) {
062: this .root = root;
063: this .articleIDDomainSuffix = articleIDDomainSuffix;
064: }
065:
066: /**
067: * Generate a new article ID for use in the repository.
068: */
069: String generateArticleID() {
070: int idx = Math.abs(counter++);
071: StringBuffer idBuffer = new StringBuffer(256).append("<")
072: .append(Thread.currentThread().hashCode()).append(".")
073: .append(System.currentTimeMillis()).append(".").append(
074: idx).append("@").append(articleIDDomainSuffix)
075: .append(">");
076: return idBuffer.toString();
077: }
078:
079: /**
080: * Add the article information to the repository.
081: *
082: * @param prop contains the newsgroup name and article number.
083: */
084: void addArticle(String articleID, Properties prop)
085: throws IOException {
086: if (articleID == null) {
087: articleID = generateArticleID();
088: }
089: FileOutputStream fout = null;
090: try {
091: fout = new FileOutputStream(getFileFromID(articleID));
092: prop.store(fout, new Date().toString());
093: } finally {
094: if (fout != null) {
095: fout.close();
096: }
097: }
098: }
099:
100: /**
101: * Returns the file in the repository corresponding to the specified
102: * article ID.
103: *
104: * @param articleID the article ID
105: *
106: * @return the repository file
107: */
108: File getFileFromID(String articleID) {
109: String b64Id;
110: try {
111: b64Id = removeCRLF(Base64.encodeAsString(articleID));
112: } catch (Exception e) {
113: throw new RuntimeException("This shouldn't happen: " + e);
114: }
115: return new File(root, b64Id);
116: }
117:
118: /**
119: * the base64 encode from javax.mail.internet.MimeUtility adds line
120: * feeds to the encoded stream. This removes them, since we will
121: * use the String as a filename.
122: */
123: private static String removeCRLF(String str) {
124: StringBuffer buffer = new StringBuffer();
125: for (int i = 0; i < str.length(); i++) {
126: char c = str.charAt(i);
127: if (c != '\r' && c != '\n') {
128: buffer.append(c);
129: }
130: }
131: return buffer.toString();
132: }
133:
134: /**
135: * Returns whether the article ID is in the repository
136: *
137: * @param articleID the article ID
138: *
139: * @return whether the article ID is in the repository
140: */
141: boolean isExists(String articleID) {
142: return (articleID == null) ? false : getFileFromID(articleID)
143: .exists();
144: }
145:
146: /**
147: * Get the article from the NNTP respository with the specified id.
148: *
149: * @param repo the NNTP repository where the article is stored
150: * @param id the id of the article to retrieve
151: *
152: * @return the article
153: *
154: * @throws IOException if the ID information cannot be loaded
155: */
156: NNTPArticle getArticle(NNTPRepository repo, String id)
157: throws IOException {
158: File f = getFileFromID(id);
159: if (f.exists() == false) {
160: return null;
161: }
162: FileInputStream fin = null;
163: Properties prop = new Properties();
164: try {
165: fin = new FileInputStream(f);
166: prop.load(fin);
167: } finally {
168: if (fin != null) {
169: fin.close();
170: }
171: }
172: Enumeration enumeration = prop.keys();
173: NNTPArticle article = null;
174: while (article == null && enumeration.hasMoreElements()) {
175: String groupName = (String) enumeration.nextElement();
176: int number = Integer.parseInt(prop.getProperty(groupName));
177: NNTPGroup group = repo.getGroup(groupName);
178: if (group != null) {
179: article = group.getArticle(number);
180: }
181: }
182: return article;
183: }
184: }
|