1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.apache.james.nntpserver.repository;
23
24 import org.apache.avalon.framework.activity.Initializable;
25 import org.apache.avalon.framework.configuration.Configurable;
26 import org.apache.avalon.framework.configuration.Configuration;
27 import org.apache.avalon.framework.configuration.ConfigurationException;
28 import org.apache.avalon.framework.container.ContainerUtil;
29 import org.apache.avalon.framework.logger.AbstractLogEnabled;
30 import org.apache.avalon.framework.service.ServiceException;
31 import org.apache.avalon.framework.service.ServiceManager;
32 import org.apache.avalon.framework.service.Serviceable;
33 import org.apache.james.nntpserver.DateSinceFileFilter;
34 import org.apache.james.nntpserver.NNTPException;
35 import org.apache.james.services.FileSystem;
36 import org.apache.james.util.io.AndFileFilter;
37 import org.apache.james.util.io.DirectoryFileFilter;
38 import org.apache.oro.io.GlobFilenameFilter;
39
40 import java.io.File;
41 import java.io.FileOutputStream;
42 import java.io.InputStream;
43 import java.io.IOException;
44 import java.util.ArrayList;
45 import java.util.Date;
46 import java.util.HashMap;
47 import java.util.Iterator;
48 import java.util.List;
49 import java.util.Set;
50
51
52
53
54 public class NNTPRepositoryImpl extends AbstractLogEnabled
55 implements NNTPRepository, Serviceable, Configurable, Initializable {
56
57
58
59
60 private Configuration configuration;
61
62
63
64
65 private boolean readOnly;
66
67
68
69
70 private File rootPath;
71
72
73
74
75 private File tempPath;
76
77
78
79
80 private NNTPSpooler spool;
81
82
83
84
85 private ArticleIDRepository articleIDRepo;
86
87
88
89
90 private HashMap groupNameMap = null;
91
92
93
94
95 private boolean definedGroupsOnly = false;
96
97
98
99
100 private String rootPathString = null;
101
102
103
104
105 private String tempPathString = null;
106
107
108
109
110 private String articleIdPathString = null;
111
112
113
114
115 private String articleIDDomainSuffix = null;
116
117
118
119
120
121 private String[] overviewFormat = { "Subject:",
122 "From:",
123 "Date:",
124 "Message-ID:",
125 "References:",
126 "Bytes:",
127 "Lines:"
128 };
129
130
131
132
133
134
135 private HashMap repositoryGroups = new HashMap();
136
137
138
139
140 private ServiceManager serviceManager;
141
142
143
144
145 private FileSystem fileSystem;
146
147
148
149
150 public void configure( Configuration aConfiguration ) throws ConfigurationException {
151 configuration = aConfiguration;
152 readOnly = configuration.getChild("readOnly").getValueAsBoolean(false);
153 articleIDDomainSuffix = configuration.getChild("articleIDDomainSuffix")
154 .getValue("foo.bar.sho.boo");
155 rootPathString = configuration.getChild("rootPath").getValue(null);
156 if (rootPathString == null) {
157 throw new ConfigurationException("Root path URL is required.");
158 }
159 tempPathString = configuration.getChild("tempPath").getValue(null);
160 if (tempPathString == null) {
161 throw new ConfigurationException("Temp path URL is required.");
162 }
163 articleIdPathString = configuration.getChild("articleIDPath").getValue(null);
164 if (articleIdPathString == null) {
165 throw new ConfigurationException("Article ID path URL is required.");
166 }
167 if (getLogger().isDebugEnabled()) {
168 if (readOnly) {
169 getLogger().debug("NNTP repository is read only.");
170 } else {
171 getLogger().debug("NNTP repository is writeable.");
172 }
173 getLogger().debug("NNTP repository root path URL is " + rootPathString);
174 getLogger().debug("NNTP repository temp path URL is " + tempPathString);
175 getLogger().debug("NNTP repository article ID path URL is " + articleIdPathString);
176 }
177 Configuration newsgroupConfiguration = configuration.getChild("newsgroups");
178 definedGroupsOnly = newsgroupConfiguration.getAttributeAsBoolean("only", false);
179 groupNameMap = new HashMap();
180 if ( newsgroupConfiguration != null ) {
181 Configuration[] children = newsgroupConfiguration.getChildren("newsgroup");
182 if ( children != null ) {
183 for ( int i = 0 ; i < children.length ; i++ ) {
184 String groupName = children[i].getValue();
185 groupNameMap.put(groupName, groupName);
186 }
187 }
188 }
189 getLogger().debug("Repository configuration done");
190 }
191
192
193
194
195 public void initialize() throws Exception {
196
197 getLogger().debug("Starting initialize");
198 File articleIDPath = null;
199
200 try {
201 rootPath = fileSystem.getFile(rootPathString);
202 tempPath = fileSystem.getFile(tempPathString);
203 articleIDPath = fileSystem.getFile(articleIdPathString);
204 } catch (Exception e) {
205 getLogger().fatalError(e.getMessage(), e);
206 throw e;
207 }
208
209 if ( articleIDPath.exists() == false ) {
210 articleIDPath.mkdirs();
211 }
212
213 articleIDRepo = new ArticleIDRepository(articleIDPath, articleIDDomainSuffix);
214 spool = (NNTPSpooler)createSpooler();
215 spool.setRepository(this);
216 spool.setArticleIDRepository(articleIDRepo);
217 if (getLogger().isDebugEnabled()) {
218 getLogger().debug("repository:readOnly=" + readOnly);
219 getLogger().debug("repository:rootPath=" + rootPath.getAbsolutePath());
220 getLogger().debug("repository:tempPath=" + tempPath.getAbsolutePath());
221 }
222
223 if ( rootPath.exists() == false ) {
224 rootPath.mkdirs();
225 } else if (!(rootPath.isDirectory())) {
226 StringBuffer errorBuffer =
227 new StringBuffer(128)
228 .append("NNTP repository root directory is improperly configured. The specified path ")
229 .append(rootPathString)
230 .append(" is not a directory.");
231 throw new ConfigurationException(errorBuffer.toString());
232 }
233
234 Set groups = groupNameMap.keySet();
235 Iterator groupIterator = groups.iterator();
236 while( groupIterator.hasNext() ) {
237 String groupName = (String)groupIterator.next();
238 File groupFile = new File(rootPath,groupName);
239 if ( groupFile.exists() == false ) {
240 groupFile.mkdirs();
241 } else if (!(groupFile.isDirectory())) {
242 StringBuffer errorBuffer =
243 new StringBuffer(128)
244 .append("A file exists in the NNTP root directory with the same name as a newsgroup. File ")
245 .append(groupName)
246 .append("in directory ")
247 .append(rootPathString)
248 .append(" is not a directory.");
249 throw new ConfigurationException(errorBuffer.toString());
250 }
251 }
252 if ( tempPath.exists() == false ) {
253 tempPath.mkdirs();
254 } else if (!(tempPath.isDirectory())) {
255 StringBuffer errorBuffer =
256 new StringBuffer(128)
257 .append("NNTP repository temp directory is improperly configured. The specified path ")
258 .append(tempPathString)
259 .append(" is not a directory.");
260 throw new ConfigurationException(errorBuffer.toString());
261 }
262
263 getLogger().debug("repository initialization done");
264 }
265
266
267
268
269 public boolean isReadOnly() {
270 return readOnly;
271 }
272
273
274
275
276 public NNTPGroup getGroup(String groupName) {
277 if (definedGroupsOnly && groupNameMap.get(groupName) == null) {
278 if (getLogger().isDebugEnabled()) {
279 getLogger().debug(groupName + " is not a newsgroup hosted on this server.");
280 }
281 return null;
282 }
283 File groupFile = new File(rootPath,groupName);
284 NNTPGroup groupToReturn = null;
285 synchronized(this) {
286 groupToReturn = (NNTPGroup)repositoryGroups.get(groupName);
287 if ((groupToReturn == null) && groupFile.exists() && groupFile.isDirectory() ) {
288 try {
289 groupToReturn = new NNTPGroupImpl(groupFile);
290 ContainerUtil.enableLogging(groupToReturn, getLogger());
291 ContainerUtil.initialize(groupToReturn);
292 repositoryGroups.put(groupName, groupToReturn);
293 } catch (Exception e) {
294 getLogger().error("Couldn't create group object.", e);
295 groupToReturn = null;
296 }
297 }
298 }
299 return groupToReturn;
300 }
301
302
303
304
305 public NNTPArticle getArticleFromID(String id) {
306 try {
307 return articleIDRepo.getArticle(this,id);
308 } catch(Exception ex) {
309 getLogger().error("Couldn't get article " + id + ": ", ex);
310 return null;
311 }
312 }
313
314
315
316
317 public void createArticle(InputStream in) {
318 StringBuffer fileBuffer =
319 new StringBuffer(32)
320 .append(System.currentTimeMillis())
321 .append(".")
322 .append(Math.random());
323 File f = new File(tempPath, fileBuffer.toString());
324 FileOutputStream fout = null;
325 try {
326 fout = new FileOutputStream(f);
327 byte[] readBuffer = new byte[1024];
328 int bytesRead = 0;
329 while ( ( bytesRead = in.read(readBuffer, 0, 1024) ) > 0 ) {
330 fout.write(readBuffer, 0, bytesRead);
331 }
332 fout.flush();
333 fout.close();
334 fout = null;
335 boolean renamed = f.renameTo(new File(spool.getSpoolPath(),f.getName()));
336 if (!renamed) {
337 throw new IOException("Could not create article on the spool.");
338 }
339 } catch(IOException ex) {
340 throw new NNTPException("create article failed",ex);
341 } finally {
342 if (fout != null) {
343 try {
344 fout.close();
345 } catch (IOException ioe) {
346
347 }
348 }
349 }
350 }
351
352 class GroupFilter implements java.io.FilenameFilter {
353 public boolean accept(java.io.File dir, String name) {
354 if (getLogger().isDebugEnabled()) {
355 getLogger().debug(((definedGroupsOnly ? groupNameMap.containsKey(name) : true) ? "Accepting ": "Rejecting") + name);
356 }
357
358 return definedGroupsOnly ? groupNameMap.containsKey(name) : true;
359 }
360 }
361
362
363
364
365 public Iterator getMatchedGroups(String wildmat) {
366 File[] f = rootPath.listFiles(new AndFileFilter(new GroupFilter(), new AndFileFilter
367 (new DirectoryFileFilter(),new GlobFilenameFilter(wildmat))));
368 return getGroups(f);
369 }
370
371
372
373
374
375
376
377
378
379 private Iterator getGroups(File[] f) {
380 List list = new ArrayList();
381 for ( int i = 0 ; i < f.length ; i++ ) {
382 if (f[i] != null) {
383 list.add(getGroup(f[i].getName()));
384 }
385 }
386 return list.iterator();
387 }
388
389
390
391
392 public Iterator getGroupsSince(Date dt) {
393 File[] f = rootPath.listFiles(new AndFileFilter(new GroupFilter(), new AndFileFilter
394 (new DirectoryFileFilter(),new DateSinceFileFilter(dt.getTime()))));
395 return getGroups(f);
396 }
397
398
399
400
401
402
403
404
405 public Iterator getArticlesSince(final Date dt) {
406 final Iterator giter = getGroupsSince(dt);
407 return new Iterator() {
408
409 private Iterator iter = null;
410
411 public boolean hasNext() {
412 if ( iter == null ) {
413 if ( giter.hasNext() ) {
414 NNTPGroup group = (NNTPGroup)giter.next();
415 iter = group.getArticlesSince(dt);
416 }
417 else {
418 return false;
419 }
420 }
421 if ( iter.hasNext() ) {
422 return true;
423 } else {
424 iter = null;
425 return hasNext();
426 }
427 }
428
429 public Object next() {
430 return iter.next();
431 }
432
433 public void remove() {
434 throw new UnsupportedOperationException("remove not supported");
435 }
436 };
437 }
438
439
440
441
442 public void service(ServiceManager serviceManager) throws ServiceException {
443 this.serviceManager = serviceManager;
444 setFileSystem((FileSystem) serviceManager.lookup(FileSystem.ROLE));
445 }
446
447
448
449
450
451 private void setFileSystem(FileSystem system) {
452 this.fileSystem = system;
453 }
454
455
456
457
458 public String[] getOverviewFormat() {
459 return overviewFormat;
460 }
461
462
463
464
465
466
467 private NNTPSpooler createSpooler()
468 throws ConfigurationException {
469 String className = NNTPSpooler.class.getName();
470 Configuration spoolerConfiguration = configuration.getChild("spool");
471 try {
472
473 className = spoolerConfiguration.getAttribute("class");
474 } catch(ConfigurationException ce) {
475
476 }
477 try {
478 Object obj = Thread.currentThread().getContextClassLoader().loadClass(className).newInstance();
479 ContainerUtil.enableLogging(obj, getLogger());
480 ContainerUtil.service(obj, serviceManager);
481 ContainerUtil.configure(obj, spoolerConfiguration.getChild("configuration"));
482 ContainerUtil.initialize(obj);
483 return (NNTPSpooler)obj;
484 } catch(ClassCastException cce) {
485 StringBuffer errorBuffer =
486 new StringBuffer(128)
487 .append("Spooler initialization failed because the spooler class ")
488 .append(className)
489 .append(" was not a subclass of org.apache.james.nntpserver.repository.NNTPSpooler");
490 String errorString = errorBuffer.toString();
491 getLogger().error(errorString, cce);
492 throw new ConfigurationException(errorString, cce);
493 } catch(Exception ex) {
494 getLogger().error("Spooler initialization failed",ex);
495 throw new ConfigurationException("Spooler initialization failed",ex);
496 }
497 }
498 }