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 }