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 }