You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by ch...@apache.org on 2001/05/15 16:42:46 UTC
cvs commit: jakarta-james/src/org/apache/james/nntpserver/repository ArticleIDRepository.java NNTPArticle.java NNTPArticleImpl.java NNTPGroup.java NNTPGroupImpl.java NNTPLineReader.java NNTPLineReaderImpl.java NNTPRepository.java NNTPRepositoryImpl.java NNTPSpooler.java NNTPUtil.java
charlesb 01/05/15 07:42:45
Added: src/java/org/apache/james/nntpserver ArticleWriter.java
AuthState.java DateSinceFileFilter.java
LISTGroup.java NNTPException.java NNTPHandler.java
NNTPServer.java NNTPServer.xinfo
src/java/org/apache/james/nntpserver/repository
ArticleIDRepository.java NNTPArticle.java
NNTPArticleImpl.java NNTPGroup.java
NNTPGroupImpl.java NNTPLineReader.java
NNTPLineReaderImpl.java NNTPRepository.java
NNTPRepositoryImpl.java NNTPSpooler.java
NNTPUtil.java
Removed: src/org/apache/james/nntpserver ArticleWriter.java
AuthState.java DateSinceFileFilter.java
LISTGroup.java NNTPException.java NNTPHandler.java
NNTPServer.java NNTPServer.xinfo
src/org/apache/james/nntpserver/repository
ArticleIDRepository.java NNTPArticle.java
NNTPArticleImpl.java NNTPGroup.java
NNTPGroupImpl.java NNTPLineReader.java
NNTPLineReaderImpl.java NNTPRepository.java
NNTPRepositoryImpl.java NNTPSpooler.java
NNTPUtil.java
Log:
Moving from src/org to src/java/org
Revision Changes Path
1.1 jakarta-james/src/java/org/apache/james/nntpserver/ArticleWriter.java
Index: ArticleWriter.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.nntpserver;
import java.io.*;
import org.apache.james.nntpserver.repository.NNTPArticle;
// this is used by ARTICLE, HEAD, BODY, STAT command.
// these commands are identical except in the writing the Article header
// and body to the response stream
// ARTICLE - writes header and body
// HEAD - writes headers
// BODY - writes body
// STAT - does not write anything
interface ArticleWriter {
void write(NNTPArticle article);
class Factory {
static ArticleWriter ARTICLE(final PrintWriter prt) {
return new ArticleWriter() {
public void write(NNTPArticle article) {
article.writeArticle(prt);
prt.println(".");
}
};
}
static ArticleWriter HEAD(final PrintWriter prt) {
return new ArticleWriter() {
public void write(NNTPArticle article) {
article.writeHead(prt);
prt.println(".");
}
};
}
static ArticleWriter BODY(final PrintWriter prt) {
return new ArticleWriter() {
public void write(NNTPArticle article) {
article.writeBody(prt);
prt.println(".");
}
};
}
static ArticleWriter STAT(final PrintWriter prt) {
return new ArticleWriter() {
public void write(NNTPArticle article) { }
};
}
static ArticleWriter OVER(final PrintWriter prt) {
return new ArticleWriter() {
public void write(NNTPArticle article) {
article.writeOverview(prt);
}
};
}
} // class Factory
}
1.1 jakarta-james/src/java/org/apache/james/nntpserver/AuthState.java
Index: AuthState.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.nntpserver;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import org.apache.james.services.UsersRepository;
/**
* @version 1.0.0, 31/03/2001
* @author Harmeet
*
* implements the authentication state. Should this be moved to a more common place ??
* Should there be an authenication service, that manufactures and hands to the different
* protocol servers AuthState objects.
*/
public class AuthState {
private final boolean requiredAuth;
private final UsersRepository repo;
private String user;
private String password;
public AuthState(boolean requiredAuth,UsersRepository repo) {
this.requiredAuth = requiredAuth;
this.repo = repo;
}
public boolean isAuthenticated() {
if ( requiredAuth )
return repo.test(user,password);
else
return true;
}
public void setUser(String user) {
this.user = user;
this.password = null;
}
public void setPassword(String password) {
this.password = password;
}
}
1.1 jakarta-james/src/java/org/apache/james/nntpserver/DateSinceFileFilter.java
Index: DateSinceFileFilter.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.nntpserver;
import java.io.File;
import java.io.FilenameFilter;
/**
* filters files according to their date of last modification
*
* @author Harmeet Bedi <ha...@kodemuse.com>
*/
public class DateSinceFileFilter implements FilenameFilter
{
private final long m_date;
public DateSinceFileFilter( long date )
{
m_date = date;
}
public boolean accept( final File file, final String name )
{
return (new File(file,name).lastModified() >= m_date);
}
}
1.1 jakarta-james/src/java/org/apache/james/nntpserver/LISTGroup.java
Index: LISTGroup.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.nntpserver;
import java.io.*;
import org.apache.james.nntpserver.repository.NNTPGroup;
// group information would be displayed differently depending on the
// LIST command paramter
interface LISTGroup {
void show(NNTPGroup group);
class Factory {
static LISTGroup ACTIVE(final PrintWriter prt) {
return new LISTGroup() {
public void show(NNTPGroup group) {
prt.println(group.getName()+" "+group.getFirstArticleNumber()+" "+
group.getLastArticleNumber()+" "+
(group.isPostAllowed()?"y":"n"));
}
};
}
static LISTGroup NEWSGROUPS(final PrintWriter prt) {
return new LISTGroup() {
public void show(NNTPGroup group) {
prt.println(group.getName()+" "+group.getDescription());
}
};
}
} // class Factory
}
1.1 jakarta-james/src/java/org/apache/james/nntpserver/NNTPException.java
Index: NNTPException.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.nntpserver;
public class NNTPException extends RuntimeException {
private final Throwable t;
public NNTPException(String msg) {
super(msg);
this.t = null;
}
public NNTPException(String msg,Throwable t) {
super(msg+((t!=null)?": "+t.toString():""));
this.t = t;
}
public NNTPException(Throwable t) {
super(t.toString());
this.t = t;
}
}
1.1 jakarta-james/src/java/org/apache/james/nntpserver/NNTPHandler.java
Index: NNTPHandler.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.nntpserver;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Vector;
import org.apache.avalon.framework.activity.Initializable;
import org.apache.avalon.framework.component.ComponentException;
import org.apache.avalon.framework.component.ComponentManager;
import org.apache.avalon.framework.component.Composable;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.context.Context;
import org.apache.avalon.framework.context.ContextException;
import org.apache.avalon.framework.context.Contextualizable;
import org.apache.avalon.framework.logger.AbstractLoggable;
import org.apache.avalon.cornerstone.services.connection.ConnectionHandler;
import org.apache.avalon.cornerstone.services.scheduler.PeriodicTimeTrigger;
import org.apache.avalon.cornerstone.services.scheduler.Target;
import org.apache.avalon.cornerstone.services.scheduler.TimeScheduler;
import org.apache.avalon.excalibur.collections.ListUtils;
import org.apache.james.Constants;
import org.apache.james.BaseConnectionHandler;
import org.apache.james.nntpserver.repository.NNTPArticle;
import org.apache.james.nntpserver.repository.NNTPGroup;
import org.apache.james.nntpserver.repository.NNTPLineReaderImpl;
import org.apache.james.nntpserver.repository.NNTPRepository;
import org.apache.james.nntpserver.repository.NNTPUtil;
import org.apache.james.services.UsersRepository;
import org.apache.james.services.UsersStore;
/**
* The NNTP protocol is defined by RFC 977.
* This implementation is based on IETF draft 13, posted on 2nd April '2001.
* URL: http://www.ietf.org/internet-drafts/draft-ietf-nntpext-base-13.txt
*
* Common NNTP extensions are in RFC 2980.
*
* @author Fedor Karpelevitch
* @author Harmeet <hb...@apache.org>
*/
public class NNTPHandler extends BaseConnectionHandler
implements ConnectionHandler, Composable, Configurable, Target {
// timeout controllers
private TimeScheduler scheduler;
// communciation.
private Socket socket;
private BufferedReader reader;
private PrintWriter writer;
// authentication.
private AuthState authState;
private boolean authRequired = false;
private UsersRepository users;
// data abstractions.
private NNTPGroup group;
private NNTPRepository repo;
public void configure( Configuration configuration ) throws ConfigurationException {
super.configure(configuration);
authRequired=configuration.getChild("authRequired").getValueAsBoolean(false);
authState = new AuthState(authRequired,users);
}
public void compose( final ComponentManager componentManager )
throws ComponentException
{
//System.out.println(getClass().getName()+": compose - "+authRequired);
UsersStore usersStore = (UsersStore)componentManager.
lookup( "org.apache.james.services.UsersStore" );
users = usersStore.getRepository("LocalUsers");
scheduler = (TimeScheduler)componentManager.
lookup( "org.apache.avalon.cornerstone.services.scheduler.TimeScheduler" );
repo = (NNTPRepository)componentManager
.lookup("org.apache.james.nntpserver.repository.NNTPRepository");
}
public void handleConnection( Socket connection ) throws IOException {
try {
this.socket = connection;
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
writer = new PrintWriter(socket.getOutputStream()) {
public void println() {
print("\r\n");
flush();
}
};
getLogger().info( "Connection from " + socket.getInetAddress());
} catch (Exception e) {
getLogger().error( "Cannot open connection from: " + e.getMessage(), e );
}
try {
final PeriodicTimeTrigger trigger = new PeriodicTimeTrigger( timeout, -1 );
scheduler.addTrigger( this.toString(), trigger, this );
// section 7.1
writer.println("200 "+helloName+
(repo.isReadOnly()?" Posting Not Premitted":" Posting Permitted"));
while (parseCommand(reader.readLine()))
scheduler.resetTrigger(this.toString());
reader.close();
writer.close();
socket.close();
scheduler.removeTrigger(this.toString());
getLogger().info("Connection closed");
} catch (Exception e) {
doQUIT();
//writer.println("502 Error closing connection.");
//writer.flush();
getLogger().error( "Exception during connection:" + e.getMessage(), e );
try { socket.close(); } catch (IOException ioe) { }
}
}
public void targetTriggered( final String triggerName ) {
getLogger().error("Connection timeout on socket");
try {
writer.println("Connection timeout. Closing connection");
socket.close();
} catch (IOException e) { }
}
// checks if a command is allowed. The authentication state is validated.
private boolean isAllowed(String command) {
boolean allowed = authState.isAuthenticated();
// some commads are allowed, even if the user is not authenticated
allowed = allowed || command.equalsIgnoreCase("AUTHINFO");
allowed = allowed || command.equalsIgnoreCase("AUTHINFO");
allowed = allowed || command.equalsIgnoreCase("MODE");
allowed = allowed || command.equalsIgnoreCase("QUIT");
if ( allowed == false )
writer.println("502 User is not authenticated");
return allowed;
}
private boolean parseCommand(String commandRaw) {
if (commandRaw == null)
return false;
getLogger().info("Command received: " + commandRaw);
//System.out.println("NNTPHandler> "+commandRaw);
StringTokenizer tokens = new StringTokenizer(commandRaw);
if (!tokens.hasMoreTokens())
return false;
final String command = tokens.nextToken();
//System.out.println("allowed="+isAllowed(command)+","+authState.isAuthenticated());
if ( isAllowed(command) == false )
return true;
if ( command.equalsIgnoreCase("MODE") && tokens.hasMoreTokens() &&
tokens.nextToken().equalsIgnoreCase("READER") )
doMODEREADER();
else if ( command.equalsIgnoreCase("LIST") && tokens.hasMoreTokens() &&
tokens.nextToken().equalsIgnoreCase("EXTENSIONS") )
doLISTEXTENSIONS();
else if ( command.equalsIgnoreCase("LIST") && tokens.hasMoreTokens() &&
tokens.nextToken().equalsIgnoreCase("OVERVIEW.FMT") )
doLISTOVERVIEWFMT();
else if ( command.equalsIgnoreCase("GROUP") )
doGROUP(tokens.hasMoreTokens()?tokens.nextToken():null);
else if ( command.equalsIgnoreCase("LAST") )
doLAST();
else if ( command.equalsIgnoreCase("ARTICLE") )
doARTICLE(tokens.hasMoreTokens()?tokens.nextToken():null);
else if ( command.equalsIgnoreCase("HEAD") )
doHEAD(tokens.hasMoreTokens()?tokens.nextToken():null);
else if ( command.equalsIgnoreCase("BODY") )
doBODY(tokens.hasMoreTokens()?tokens.nextToken():null);
else if ( command.equalsIgnoreCase("STAT") )
doSTAT(tokens.hasMoreTokens()?tokens.nextToken():null);
else if ( command.equalsIgnoreCase("POST") )
doPOST();
else if ( command.equalsIgnoreCase("IHAVE") )
doIHAVE(tokens.hasMoreTokens()?tokens.nextToken():null);
else if ( command.equalsIgnoreCase("LIST") )
doLIST(tokens);
else if ( command.equalsIgnoreCase("QUIT") )
doQUIT();
else if ( command.equalsIgnoreCase("DATE") )
doDATE();
else if ( command.equalsIgnoreCase("HELP") )
doHELP();
else if ( command.equalsIgnoreCase("NEWNEWS") )
doNEWSGROUPS(tokens);
else if ( command.equalsIgnoreCase("NEWNEWS") )
doNEWNEWS(tokens);
else if ( command.equalsIgnoreCase("LISTGROUP") )
doLISTGROUP(tokens.hasMoreTokens()?tokens.nextToken():null);
else if ( command.equalsIgnoreCase("OVER") )
doOVER(tokens.hasMoreTokens()?tokens.nextToken():null);
else if ( command.equalsIgnoreCase("XOVER") )
doXOVER(tokens.hasMoreTokens()?tokens.nextToken():null);
else if ( command.equalsIgnoreCase("PAT") )
doPAT();
else if ( command.equalsIgnoreCase("HDR") )
doHDR(tokens);
else if ( command.equalsIgnoreCase("XHDR") )
doXHDR(tokens);
else if ( command.equalsIgnoreCase("AUTHINFO") )
doAUTHINFO(tokens);
else
writer.println("501 Syntax error");
return (command.equalsIgnoreCase("QUIT") == false);
}
// implements only the originnal AUTHINFO
// for simple and generic AUTHINFO, 501 is sent back. This is as
// per article 3.1.3 of RFC 2980
private void doAUTHINFO(StringTokenizer tok) {
String command = tok.nextToken();
if ( command.equalsIgnoreCase("USER") ) {
authState.setUser(tok.nextToken());
writer.println("381 More authentication information required");
} else if ( command.equalsIgnoreCase("PASS") ) {
authState.setPassword(tok.nextToken());
if ( authState.isAuthenticated() )
writer.println("281 Authentication accepted");
else
writer.println("482 Authentication rejected");
}
}
private void doNEWNEWS(StringTokenizer tok) {
writer.println("230 list of new articles by message-id follows");
Iterator iter = repo.getArticlesSince(getDateFrom(tok));
while ( iter.hasNext() )
writer.println("<"+((NNTPArticle)iter.next()).getUniqueID()+">");
writer.println(".");
}
private void doNEWSGROUPS(StringTokenizer tok) {
writer.println("230 list of new newsgroups follows");
Iterator iter = repo.getGroupsSince(getDateFrom(tok));
while ( iter.hasNext() )
writer.println(((NNTPGroup)iter.next()).getName());
writer.println(".");
}
// returns the date from @param input.
// The input tokens are assumed to be in format date time [GMT|UTC] .
// 'date' is in format [XX]YYMMDD. 'time' is in format 'HHMMSS'
// NOTE: This routine would do with some format checks.
private Date getDateFrom(StringTokenizer tok) {
String date = tok.nextToken();
String time = tok.nextToken();
boolean utc = ( tok.hasMoreTokens() );
Date d = new Date();
DateFormat df = ( date.length() == 8 ) ? DF_DATEFROM_LONG : DF_DATEFROM_SHORT;
try {
Date dt = df.parse(date+" "+time);
if ( utc )
dt = new Date(dt.getTime()+UTC_OFFSET);
return dt;
} catch ( ParseException pe ) {
throw new NNTPException("date extraction failed: "+date+","+time+","+utc);
}
}
private void doHELP() {
writer.println("100 Help text follows");
writer.println(".");
}
// used to calculate DATE from - see 11.3
public static final DateFormat DF_DATEFROM_LONG = new SimpleDateFormat("yyyyMMdd HHmmss");
public static final DateFormat DF_DATEFROM_SHORT = new SimpleDateFormat("yyMMdd HHmmss");
// Date format for the DATE keyword - see 11.1.1
public static final DateFormat DF_DATE = new SimpleDateFormat("yyyyMMddHHmmss");
public static final long UTC_OFFSET = Calendar.getInstance().get(Calendar.ZONE_OFFSET);
private void doDATE() {
//Calendar c = Calendar.getInstance();
//long UTC_OFFSET = c.get(c.ZONE_OFFSET) + c.get(c.DST_OFFSET);
Date dt = new Date(System.currentTimeMillis()-UTC_OFFSET);
String dtStr = DF_DATE.format(new Date(dt.getTime() - UTC_OFFSET));
writer.println("111 "+dtStr);
}
private void doQUIT() {
writer.println("205 closing connection");
}
private void doLIST(StringTokenizer tok) {
// see section 9.4.1
String wildmat = "*";
LISTGroup output = LISTGroup.Factory.ACTIVE(writer);
if ( tok.hasMoreTokens() ) {
String param = tok.nextToken();
// list of variations not supported - 9.4.2.1, 9.4.3.1, 9.4.4.1
String[] notSupported = { "ACTIVE.TIMES", "DISTRIBUTIONS", "DISTRIB.PATS" };
for ( int i = 0 ; i < notSupported.length ; i++ ) {
if ( param.equalsIgnoreCase("ACTIVE.TIMES") ) {
writer.println("503 program error, function not performed");
return;
}
}
if ( param.equalsIgnoreCase("NEWSGROUPS") )
output = LISTGroup.Factory.NEWSGROUPS(writer);
else
assert(param,param.equalsIgnoreCase("ACTIVE"));
if ( tok.hasMoreTokens() )
wildmat = tok.nextToken();
}
Iterator iter = repo.getMatchedGroups(wildmat);
writer.println("215 list of newsgroups follows");
while ( iter.hasNext() )
output.show((NNTPGroup)iter.next());
writer.println(".");
}
private void assert(String id,boolean b) {
if ( b == false )
throw new RuntimeException(id);
}
private void doIHAVE(String id) {
// see section 9.3.2.1
assert(id,id.startsWith("<") && id.endsWith(">"));
NNTPArticle article = repo.getArticleFromID(id);
if ( article != null )
writer.println("435 article not wanted - do not send it");
else {
writer.println("335 send article to be transferred. End with <CR-LF>.<CR-LF>");
createArticle();
writer.println("235 article received ok");
}
}
private void doPOST() {
// see section 9.3.1.1
writer.println("340 send article to be posted. End with <CR-LF>.<CR-LF>");
createArticle();
writer.println("240 article received ok");
}
private void createArticle() {
repo.createArticle(new NNTPLineReaderImpl(reader));
}
private void doSTAT(String param) {
doARTICLE(param,ArticleWriter.Factory.STAT(writer));
}
private void doBODY(String param) {
doARTICLE(param,ArticleWriter.Factory.BODY(writer));
}
private void doHEAD(String param) {
doARTICLE(param,ArticleWriter.Factory.HEAD(writer));
}
private void doARTICLE(String param) {
doARTICLE(param,ArticleWriter.Factory.ARTICLE(writer));
}
private void doARTICLE(String param,ArticleWriter articleWriter) {
// section 9.2.1
NNTPArticle article = null;
if ( (param != null) && param.startsWith("<") && param.endsWith(">") ) {
article = repo.getArticleFromID(param.substring(1,param.length()-2));
if ( article == null )
writer.println("430 no such article");
else
writer.println("220 0 "+param+" article retrieved and follows");
}
else {
if ( group == null )
writer.println("412 no newsgroup selected");
else {
if ( param == null ) {
if ( group.getCurrentArticleNumber() < 0 )
writer.println("420 no current article selected");
else
article = group.getCurrentArticle();
}
else
article = group.getArticle(Integer.parseInt(param));
if ( article == null )
writer.println("423 no such article number in this group");
else
writer.println("220 "+article.getArticleNumber()+" "+
article.getUniqueID()+" article retrieved and follows");
}
}
if ( article != null )
articleWriter.write(article);
}
private void doNEXT() {
// section 9.1.1.3.1
if ( group == null )
writer.println("412 no newsgroup selected");
else if ( group.getCurrentArticleNumber() < 0 )
writer.println("420 no current article has been selected");
else if ( group.getCurrentArticleNumber() >= group.getLastArticleNumber() )
writer.println("421 no next article in this group");
else {
NNTPArticle article = group.getCurrentArticle();
group.setCurrentArticleNumber(group.getCurrentArticleNumber()+1);
writer.println("223 "+article.getArticleNumber()+" "+article.getUniqueID());
}
}
private void doLAST() {
// section 9.1.1.2.1
if ( group == null )
writer.println("412 no newsgroup selected");
else if ( group.getCurrentArticleNumber() < 0 )
writer.println("420 no current article has been selected");
else if ( group.getCurrentArticleNumber() <= group.getFirstArticleNumber() )
writer.println("422 no previous article in this group");
else {
NNTPArticle article = group.getCurrentArticle();
group.setCurrentArticleNumber(group.getCurrentArticleNumber()-1);
writer.println("223 "+article.getArticleNumber()+" "+article.getUniqueID());
}
}
private void doGROUP(String groupName) {
group = repo.getGroup(groupName);
// section 9.1.1.1
if ( group == null )
writer.println("411 no such newsgroup");
else
writer.println("211 "+group.getNumberOfArticles()+" "+
group.getFirstArticleNumber()+" "+
group.getLastArticleNumber()+" "+
group.getName()+" group selected");
}
private void doLISTEXTENSIONS() {
// 8.1.1
writer.println("202 Extensions supported:");
writer.println("LISTGROUP");
writer.println("AUTHINFO");
writer.println("OVER");
writer.println("XOVER");
writer.println("HDR");
writer.println("XHDR");
writer.println(".");
}
private void doMODEREADER() {
// 7.2
writer.println(repo.isReadOnly()
? "201 Posting Not Permitted" : "200 Posting Permitted");
}
private void doLISTGROUP(String groupName) {
// 9.5.1.1.1
NNTPGroup group = null;
if (groupName==null) {
if ( group == null )
writer.println("412 not currently in newsgroup");
}
else {
group = repo.getGroup(groupName);
if ( group == null )
writer.println("411 no such newsgroup");
}
if ( group != null ) {
writer.println("211 list of article numbers follow");
for (Iterator iter = group.getArticles();iter.hasNext();) {
NNTPArticle article = (NNTPArticle)iter.next();
writer.println(article.getArticleNumber());
}
writer.println(".");
this.group = group;
group.setCurrentArticleNumber(group.getFirstArticleNumber());
}
}
private void doLISTOVERVIEWFMT() {
// 9.5.3.1.1
// 503 means information is not available as per 9.5.2.1
writer.println("503 program error, function not performed");
}
private void doPAT() {
// 9.5.3.1.1 in draft-12
writer.println("500 Command not recognized");
}
private void doXHDR(StringTokenizer tok) {
doHDR(tok);
}
private void doHDR(StringTokenizer tok) {
// 9.5.3
writer.println("500 Command not recognized");
String hdr = tok.nextToken();
String range = tok.hasMoreTokens() ? tok.nextToken() : null;
NNTPArticle[] article = getRange(range);
if ( article == null )
writer.println("412 no newsgroup selected");
else if ( article.length == 0 )
writer.println("430 no such article");
else {
writer.println("221 Header follows");
for ( int i = 0 ; i < article.length ; i++ ) {
String val = article[i].getHeader(hdr);
if ( val == null )
val = "";
writer.println(article[i].getArticleNumber()+" "+val);
}
writer.println(".");
}
}
// returns the list of articles that match the range.
// @return null indicates insufficient information to
// fetch the list of articles
private NNTPArticle[] getRange(String range) {
// check for msg id
if ( range != null && range.startsWith("<") ) {
NNTPArticle article = repo.getArticleFromID(range);
return ( article == null )
? new NNTPArticle[0] : new NNTPArticle[] { article };
}
if ( group == null )
return null;
if ( range == null )
range = ""+group.getCurrentArticleNumber();
int start = -1;
int end = -1;
int idx = range.indexOf('-');
if ( idx == -1 ) {
start = end = Integer.parseInt(range);
} else {
start = Integer.parseInt(range.substring(0,idx));
if ( idx+1 == range.length() )
end = group.getLastArticleNumber();
else
end = Integer.parseInt(range.substring(idx+1));
}
List list = new ArrayList();
for ( int i = start ; i <= end ; i++ ) {
NNTPArticle article = group.getArticle(i);
if ( article != null )
list.add(article);
}
return (NNTPArticle[])list.toArray(new NNTPArticle[0]);
}
private void doXOVER(String range) {
doOVER(range);
}
private void doOVER(String range) {
// 9.5.2.2.1
if ( group == null ) {
writer.println("412 No newsgroup selected");
return;
}
NNTPArticle[] article = getRange(range);
ArticleWriter articleWriter = ArticleWriter.Factory.OVER(writer);
if ( article.length == 0 )
writer.println("420 No article(s) selected");
else {
writer.println("224 Overview information follows");
for ( int i = 0 ; i < article.length ; i++ )
articleWriter.write(article[i]);
writer.println(".");
}
}
}
1.1 jakarta-james/src/java/org/apache/james/nntpserver/NNTPServer.java
Index: NNTPServer.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.nntpserver;
import java.net.InetAddress;
import java.net.UnknownHostException;
import org.apache.avalon.framework.activity.Initializable;
import org.apache.avalon.framework.component.Component;
import org.apache.avalon.framework.component.ComponentException;
import org.apache.avalon.framework.component.ComponentManager;
import org.apache.avalon.framework.component.DefaultComponentManager;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.cornerstone.services.connection.AbstractService;
import org.apache.avalon.cornerstone.services.connection.ConnectionHandlerFactory;
import org.apache.avalon.cornerstone.services.connection.DefaultHandlerFactory;
/**
* @author Harmeet <hb...@apache.org>
*/
public class NNTPServer extends AbstractService {
protected ConnectionHandlerFactory createFactory() {
return new DefaultHandlerFactory( NNTPHandler.class );
}
public void configure( final Configuration configuration )
throws ConfigurationException
{
//System.out.println(getClass().getName()+": configure");
m_port = configuration.getChild( "port" ).getValueAsInteger( 119 );
try {
String bindAddress = configuration.getChild( "bind" ).getValue( null );
if( null != bindAddress )
m_bindTo = InetAddress.getByName( bindAddress );
} catch( final UnknownHostException unhe ) {
throw new ConfigurationException( "Malformed bind parameter", unhe );
}
final String useTLS = configuration.getChild("useTLS").getValue( "" );
if( useTLS.equals( "TRUE" ) )
m_serverSocketType = "ssl";
super.configure( configuration.getChild( "nntphandler" ) );
getLogger().info("configured NNTPServer to run at : "+m_port);
}
public void initialize() throws Exception {
//System.out.println(getClass().getName()+": init");
super.initialize();
System.out.println("Started NNTP Server "+m_connectionName);
}
}
1.1 jakarta-james/src/java/org/apache/james/nntpserver/NNTPServer.xinfo
Index: NNTPServer.xinfo
===================================================================
<?xml version="1.0"?>
<blockinfo>
<services>
<service name="org.apache.avalon.framework.component.Component" version="1.0"/>
</services>
<dependencies>
<dependency>
<role>org.apache.james.services.MailStore</role>
<service name="org.apache.james.services.MailStore" version="1.0"/>
</dependency>
<dependency>
<role>org.apache.james.services.UsersStore</role>
<service name="org.apache.james.services.UsersStore" version="1.0"/>
</dependency>
<dependency>
<role>org.apache.avalon.cornerstone.services.connection.ConnectionManager</role>
<service name="org.apache.avalon.cornerstone.services.connection.ConnectionManager"
version="1.0"/>
</dependency>
<dependency>
<role>org.apache.avalon.cornerstone.services.sockets.SocketManager</role>
<service name="org.apache.avalon.cornerstone.services.sockets.SocketManager" version="1.0"/>
</dependency>
<dependency>
<role>org.apache.avalon.cornerstone.services.scheduler.TimeScheduler</role>
<service name="org.apache.avalon.cornerstone.services.scheduler.TimeScheduler" version="1.0"/>
</dependency>
<dependency>
<role>org.apache.james.nntpserver.repository.NNTPRepository</role>
<service name="org.apache.james.nntpserver.repository.NNTPRepository" version="1.0"/>
</dependency>
</dependencies>
</blockinfo>
1.1 jakarta-james/src/java/org/apache/james/nntpserver/repository/ArticleIDRepository.java
Index: ArticleIDRepository.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.nntpserver.repository;
import java.util.*;
import java.io.*;
import org.apache.avalon.excalibur.io.ExtensionFileFilter;
import org.apache.avalon.excalibur.io.InvertedFileFilter;
import org.apache.avalon.excalibur.io.AndFileFilter;
import org.apache.james.nntpserver.NNTPException;
import org.apache.james.nntpserver.DateSinceFileFilter;
import sun.misc.BASE64Encoder;
/**
* ArticleIDRepository: contains one file for each article.
* the file name is Base64 encoded article ID
* The first line of the file is '# <create date of file>
* the rest of line have <newsgroup name>=<article number>
*
* this would allow fast lookup of a message by message id.
* allow a process to iterate and sycnhronize messages with other NNTP Servers.
* this may be inefficient, so could be used for sanity checks and an alternate
* more efficient process could be used for synchronization.
*/
public class ArticleIDRepository {
private final File root;
private final String articleIDDomainSuffix;
private int counter = 0;
ArticleIDRepository(File root,String articleIDDomainSuffix) {
this.root = root;
this.articleIDDomainSuffix = articleIDDomainSuffix;
}
public File getPath() {
return root;
}
String generateArticleID() {
int idx = Math.abs(counter++);
String unique = Thread.currentThread().hashCode()+"."+
System.currentTimeMillis()+"."+idx;
return "<"+unique+"@"+articleIDDomainSuffix+">";
}
/** @param prop contains the newsgroup name and article number */
void addArticle(String articleID,Properties prop) throws IOException {
if ( articleID == null )
articleID = generateArticleID();
FileOutputStream fout = new FileOutputStream(getFileFromID(articleID));
prop.store(fout,new Date().toString());
fout.close();
}
File getFileFromID(String articleID) {
return new File(root,new BASE64Encoder().encode(articleID.getBytes()));
}
boolean isExists(String articleID) {
return ( articleID == null ) ? false : getFileFromID(articleID).exists();
}
NNTPArticle getArticle(NNTPRepository repo,String id) throws IOException {
File f = getFileFromID(id);
if ( f.exists() == false )
return null;
FileInputStream fin = new FileInputStream(f);
Properties prop = new Properties();
prop.load(fin);
fin.close();
Enumeration enum = prop.keys();
NNTPArticle article = null;
while ( article == null && enum.hasMoreElements() ) {
String groupName = (String)enum.nextElement();
int number = Integer.parseInt(prop.getProperty(groupName));
NNTPGroup group = repo.getGroup(groupName);
if ( group != null )
article = group.getArticle(number);
}
return article;
}
}
1.1 jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPArticle.java
Index: NNTPArticle.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.nntpserver.repository;
import java.io.*;
import javax.mail.internet.InternetHeaders;
import org.apache.james.nntpserver.NNTPException;
public interface NNTPArticle {
NNTPGroup getGroup();
int getArticleNumber();
String getUniqueID();
void writeArticle(PrintWriter wrt);
void writeHead(PrintWriter wrt);
void writeBody(PrintWriter wrt);
void writeOverview(PrintWriter wrt);
String getHeader(String header);
}
1.1 jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPArticleImpl.java
Index: NNTPArticleImpl.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.nntpserver.repository;
import java.io.*;
import javax.mail.internet.InternetHeaders;
import org.apache.james.nntpserver.NNTPException;
class NNTPArticleImpl implements NNTPArticle {
private final File f;
NNTPArticleImpl(File f) {
this.f = f;
}
public NNTPGroup getGroup() {
return new NNTPGroupImpl(f.getParentFile());
}
public int getArticleNumber() {
return Integer.parseInt(f.getName());
}
public String getUniqueID() {
try {
FileInputStream fin = new FileInputStream(f);
InternetHeaders headers = new InternetHeaders(fin);
String[] idheader = headers.getHeader("Message-Id");
fin.close();
return ( idheader.length > 0 ) ? idheader[0] : null;
} catch(Exception ex) { throw new NNTPException(ex); }
}
public void writeArticle(PrintWriter prt) {
try {
BufferedReader reader = new BufferedReader(new FileReader(f));
String line = null;
while ( ( line = reader.readLine() ) != null )
prt.println(line);
reader.close();
} catch(IOException ex) { throw new NNTPException(ex); }
}
public void writeHead(PrintWriter prt) {
try {
BufferedReader reader = new BufferedReader(new FileReader(f));
String line = null;
while ( ( line = reader.readLine() ) != null ) {
if ( line.trim().length() == 0 )
break;
prt.println(line);
}
reader.close();
} catch(IOException ex) { throw new NNTPException(ex); }
}
public void writeBody(PrintWriter prt) {
try {
BufferedReader reader = new BufferedReader(new FileReader(f));
String line = null;
boolean startWriting = false;
while ( ( line = reader.readLine() ) != null ) {
if ( startWriting )
prt.println(line);
else
startWriting = ( line.trim().length() == 0 );
}
reader.close();
} catch(IOException ex) { throw new NNTPException(ex); }
}
public void writeOverview(PrintWriter prt) {
try {
FileInputStream fin = new FileInputStream(f);
InternetHeaders hdr = new InternetHeaders(fin);
fin.close();
int articleNumber = getArticleNumber();
String subject = hdr.getHeader("Subject",null);
String author = hdr.getHeader("From",null);
String date = hdr.getHeader("Date",null);
String msgId = hdr.getHeader("Message-Id",null);
String references = hdr.getHeader("References",null);
long byteCount = f.length();
long lineCount = -1;
prt.print(articleNumber+"\t");
prt.print((subject==null?"":subject)+"\t");
prt.print((author==null?"":author)+"\t");
prt.print((date==null?"":date)+"\t");
prt.print((msgId==null?"":msgId)+"\t");
prt.print((references==null?"":references)+"\t");
prt.print(byteCount+"\t");
prt.println(lineCount+"");
} catch(Exception ex) { throw new NNTPException(ex); }
}
public String getHeader(String header) {
try {
FileInputStream fin = new FileInputStream(f);
InternetHeaders hdr = new InternetHeaders(fin);
fin.close();
return hdr.getHeader(header,null);
} catch(Exception ex) { throw new NNTPException(ex); }
}
}
1.1 jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPGroup.java
Index: NNTPGroup.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.nntpserver.repository;
import java.util.*;
import java.io.*;
import org.apache.avalon.excalibur.io.ExtensionFileFilter;
import org.apache.avalon.excalibur.io.InvertedFileFilter;
import org.apache.avalon.excalibur.io.AndFileFilter;
import org.apache.james.nntpserver.NNTPException;
import org.apache.james.nntpserver.DateSinceFileFilter;
public interface NNTPGroup {
String getName();
String getDescription();
boolean isPostAllowed();
// the current article pointer. <0 indicates invalid/unknown value
int getCurrentArticleNumber();
void setCurrentArticleNumber(int articleNumber);
int getNumberOfArticles();
int getFirstArticleNumber();
int getLastArticleNumber();
NNTPArticle getCurrentArticle();
NNTPArticle getArticle(int number);
//NNTPArticle getArticleFromID(String id);
Iterator getArticlesSince(Date dt);
Iterator getArticles();
Object getPath();
}
1.1 jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPGroupImpl.java
Index: NNTPGroupImpl.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.nntpserver.repository;
import java.util.*;
import java.io.*;
import org.apache.avalon.excalibur.io.ExtensionFileFilter;
import org.apache.avalon.excalibur.io.InvertedFileFilter;
import org.apache.avalon.excalibur.io.AndFileFilter;
import org.apache.james.nntpserver.NNTPException;
import org.apache.james.nntpserver.DateSinceFileFilter;
// group is reprensted by a directory.
// articles are stored in files with the name of file == article number
class NNTPGroupImpl implements NNTPGroup {
private final File root;
private int currentArticle = -1;
private int lastArticle;
private int firstArticle;
// an instance may collect range info once. This involves disk I/O
private boolean articleRangeInfoCollected = false;
NNTPGroupImpl(File root) {
this.root = root;
}
public String getName() {
return root.getName();
}
public String getDescription() {
return getName();
}
public boolean isPostAllowed() {
return true;
}
private void collectArticleRangeInfo() {
if ( articleRangeInfoCollected )
return;
String[] list = root.list();
//new InvertedFileFilter(new ExtensionFileFilter(".id")));
int first = -1;
int last = -1;
for ( int i = 0 ; i < list.length ; i++ ) {
int num = Integer.parseInt(list[i]);
if ( first == -1 || num < first )
first = num;
if ( num > last )
last = num;
}
firstArticle = Math.max(first,0);
lastArticle = Math.max(last,0);
articleRangeInfoCollected = true;
}
public int getNumberOfArticles() {
return getLastArticleNumber() - getFirstArticleNumber();
}
public int getFirstArticleNumber() {
collectArticleRangeInfo();
return firstArticle;
}
public int getLastArticleNumber() {
collectArticleRangeInfo();
return lastArticle;
}
public int getCurrentArticleNumber() {
collectArticleRangeInfo();
// this is not as per RFC, but this is not significant.
if ( currentArticle == -1 && firstArticle > 0 )
currentArticle = firstArticle;
return currentArticle;
}
public void setCurrentArticleNumber(int articleNumber) {
this.currentArticle = articleNumber;
}
public NNTPArticle getCurrentArticle() {
return getArticle(getCurrentArticleNumber());
}
public NNTPArticle getArticle(int number) {
File f = new File(root,number+"");
return f.exists() ? new NNTPArticleImpl(f) : null;
}
// public NNTPArticle getArticleFromID(String id) {
// if ( id == null )
// return null;
// int idx = id.indexOf('@');
// if ( idx != -1 )
// id = id.substring(0,idx);
// File f = new File(root,id+".id");
// if ( f.exists() == false )
// return null;
// try {
// FileInputStream fin = new FileInputStream(f);
// int count = fin.available();
// byte[] ba = new byte[count];
// fin.read(ba);
// fin.close();
// String str = new String(ba);
// int num = Integer.parseInt(str);
// return getArticle(num);
// } catch(IOException ioe) {
// throw new NNTPException("could not fectch article: "+id,ioe);
// }
// }
public Iterator getArticlesSince(Date dt) {
File[] f = root.listFiles(new AndFileFilter
(new DateSinceFileFilter(dt.getTime()),
new InvertedFileFilter(new ExtensionFileFilter(".id"))));
List list = new ArrayList();
for ( int i = 0 ; i < f.length ; i++ )
list.add(new NNTPArticleImpl(f[i]));
return list.iterator();
}
public Iterator getArticles() {
File[] f = root.listFiles();
//(new InvertedFileFilter(new ExtensionFileFilter(".id")));
List list = new ArrayList();
for ( int i = 0 ; i < f.length ; i++ )
list.add(new NNTPArticleImpl(f[i]));
return list.iterator();
}
public Object getPath() {
return root;
}
}
1.1 jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPLineReader.java
Index: NNTPLineReader.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.nntpserver.repository;
import java.io.*;
// this interface is used to read the data from client and stream it
// into server repository
public interface NNTPLineReader {
// reads a line of data.
// @return null indicates end of data
String readLine();
}
1.1 jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPLineReaderImpl.java
Index: NNTPLineReaderImpl.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.nntpserver.repository;
import java.io.*;
import org.apache.james.nntpserver.NNTPException;
public class NNTPLineReaderImpl implements NNTPLineReader {
private final BufferedReader reader;
public NNTPLineReaderImpl(BufferedReader reader) {
this.reader = reader;
}
public String readLine() {
try {
String line = reader.readLine();
// check for end of article.
if ( line.equals(".") )
line = null;
else if ( line.startsWith(".") )
line = line.substring(1,line.length());
return line;
} catch(IOException ioe) {
throw new NNTPException("could not create article",ioe);
}
}
}
1.1 jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPRepository.java
Index: NNTPRepository.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.nntpserver.repository;
import java.io.*;
import java.util.*;
import org.apache.avalon.framework.activity.Initializable;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.logger.AbstractLoggable;
import org.apache.avalon.excalibur.io.AndFileFilter;
import org.apache.avalon.excalibur.io.DirectoryFileFilter;
import org.apache.oro.io.GlobFilenameFilter;
public interface NNTPRepository {
NNTPGroup getGroup(String groupName);
NNTPArticle getArticleFromID(String id);
void createArticle(NNTPLineReader reader);
Iterator getMatchedGroups(String wildmat);
Iterator getGroupsSince(Date dt);
Iterator getArticlesSince(Date dt);
boolean isReadOnly();
}
1.1 jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPRepositoryImpl.java
Index: NNTPRepositoryImpl.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.nntpserver.repository;
import java.io.*;
import java.util.*;
import org.apache.avalon.framework.activity.Initializable;
import org.apache.avalon.framework.component.Component;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.logger.AbstractLoggable;
import org.apache.avalon.excalibur.io.AndFileFilter;
import org.apache.avalon.excalibur.io.DirectoryFileFilter;
import org.apache.james.nntpserver.DateSinceFileFilter;
import org.apache.james.nntpserver.NNTPException;
import org.apache.oro.io.GlobFilenameFilter;
import org.apache.avalon.phoenix.Block;
public class NNTPRepositoryImpl extends AbstractLoggable
implements NNTPRepository, Configurable, Initializable, Block
{
private boolean readOnly;
// the groups are located under this path.
private File rootPath;
// articles are temprorily written here and then sent to the spooler.
private File tempPath;
private NNTPSpooler spool;
private ArticleIDRepository articleIDRepo;
private String[] addGroups = null;
public void configure( Configuration configuration ) throws ConfigurationException {
//System.out.println(getClass().getName()+": configure");
//NNTPUtil.show(configuration,System.out);
readOnly = configuration.getChild("readOnly").getValueAsBoolean(false);
rootPath = NNTPUtil.getDirectory(configuration,"rootPath");
tempPath = NNTPUtil.getDirectory(configuration,"tempPath");
File articleIDPath = NNTPUtil.getDirectory(configuration,"articleIDPath");
String articleIDDomainSuffix = configuration.getChild("articleIDDomainSuffix")
.getValue("foo.bar.sho.boo");
articleIDRepo = new ArticleIDRepository(articleIDPath,articleIDDomainSuffix);
spool = (NNTPSpooler)NNTPUtil.createInstance
(configuration.getChild("spool"),getLogger(),
"org.apache.james.nntpserver.repository.NNTPSpooler");
spool.setRepository(this);
spool.setArticleIDRepository(articleIDRepo);
getLogger().debug("repository:readOnly="+readOnly);
getLogger().debug("repository:rootPath="+rootPath.getAbsolutePath());
getLogger().debug("repository:tempPath="+tempPath.getAbsolutePath());
configuration = configuration.getChild("newsgroups");
List addGroupsList = new ArrayList();
if ( configuration != null ) {
Configuration[] children = configuration.getChildren("newsgroup");
if ( children != null )
for ( int i = 0 ; i < children.length ; i++ )
addGroupsList.add(children[i].getValue());
}
addGroups = (String[])addGroupsList.toArray(new String[0]);
getLogger().debug("repository configuration done");
}
public void initialize() throws Exception {
//System.out.println(getClass().getName()+": init");
if ( rootPath.exists() == false )
rootPath.mkdirs();
for ( int i = 0 ; i < addGroups.length ; i++ ) {
File groupF = new File(rootPath,addGroups[i]);
if ( groupF.exists() == false )
groupF.mkdirs();
}
if ( tempPath.exists() == false )
tempPath.mkdirs();
File articleIDPath = articleIDRepo.getPath();
if ( articleIDPath.exists() == false )
articleIDPath.mkdirs();
if ( spool instanceof Initializable )
((Initializable)spool).initialize();
getLogger().debug("repository initialization done");
}
public boolean isReadOnly() {
return readOnly;
}
public NNTPGroup getGroup(String groupName) {
File f = new File(rootPath,groupName);
return ( f.exists() && f.isDirectory() ) ? new NNTPGroupImpl(f) : null;
}
public NNTPArticle getArticleFromID(String id) {
try {
return articleIDRepo.getArticle(this,id);
} catch(Exception ex) {
ex.printStackTrace();
return null;
}
// int idx = id.indexOf('@');
// String name = id.substring(0,idx);
// String groupname = id.substring(idx+1);
// NNTPGroup group = getGroup(groupname);
// return ( group == null ) ? null : group.getArticleFromID(name);
}
public void createArticle(NNTPLineReader reader) {
File f = new File(tempPath,System.currentTimeMillis()+"."+Math.random());
try {
FileOutputStream fout = new FileOutputStream(f);
PrintStream prt = new PrintStream(fout,true);
String line;
while ( ( line = reader.readLine() ) != null )
prt.println(line);
prt.close();
f.renameTo(new File(spool.getSpoolPath(),f.getName()));
} catch(IOException ex) {
throw new NNTPException("create article failed",ex);
}
}
public Iterator getMatchedGroups(String wildmat) {
File[] f = rootPath.listFiles(new AndFileFilter
(new DirectoryFileFilter(),new GlobFilenameFilter(wildmat)));
return getGroups(f);
}
private Iterator getGroups(File[] f) {
List list = new ArrayList();
for ( int i = 0 ; i < f.length ; i++ )
list.add(new NNTPGroupImpl(f[i]));
return list.iterator();
}
public Iterator getGroupsSince(Date dt) {
File[] f = rootPath.listFiles(new AndFileFilter
(new DirectoryFileFilter(),new DateSinceFileFilter(dt.getTime())));
return getGroups(f);
}
// gets the list of groups.
// creates iterator that concatenates the article iterators in the list of groups.
// there is at most one article iterator reference for all the groups
public Iterator getArticlesSince(final Date dt) {
final Iterator giter = getGroupsSince(dt);
return new Iterator() {
private Iterator iter = null;
public boolean hasNext() {
if ( iter == null ) {
if ( giter.hasNext() ) {
NNTPGroup group = (NNTPGroup)giter.next();
iter = group.getArticlesSince(dt);
}
else
return false;
}
if ( iter.hasNext() )
return true;
else {
iter = null;
return hasNext();
}
}
public Object next() {
return iter.next();
}
public void remove() {
throw new UnsupportedOperationException("remove not supported");
}
};
}
}
1.1 jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPSpooler.java
Index: NNTPSpooler.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.nntpserver.repository;
import java.io.*;
import java.util.*;
import javax.mail.internet.MimeMessage;
import org.apache.avalon.framework.activity.Initializable;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.logger.AbstractLoggable;
import org.apache.avalon.framework.logger.Loggable;
import org.apache.avalon.excalibur.io.IOUtil;
import org.apache.james.util.Lock;
// processes entries and sends to appropriate groups.
// eats up inappropriate entries.
class NNTPSpooler extends AbstractLoggable implements Configurable, Initializable {
private Worker[] worker;
private File spoolPath;
public void configure( Configuration configuration ) throws ConfigurationException {
//System.out.println(getClass().getName()+": configure");
//NNTPUtil.show(configuration,System.out);
spoolPath = NNTPUtil.getDirectory(configuration,"spoolPath");
int threadCount = configuration.getChild("threadCount").getValueAsInteger(1);
int threadIdleTime = configuration.getChild("threadIdleTime").getValueAsInteger(1000);
//String tgName=configuration.getChild("threadGroupName").getValue("NNTPSpooler");
worker = new Worker[threadCount];
for ( int i = 0 ; i < worker.length ; i++ ) {
worker[i] = new Worker(threadIdleTime,spoolPath);
if ( worker[i] instanceof Loggable )
((Loggable)worker[i]).setLogger(getLogger());
}
}
void setRepository(NNTPRepository repo) {
for ( int i = 0 ; i < worker.length ; i++ )
worker[i].setRepository(repo);
}
void setArticleIDRepository(ArticleIDRepository articleIDRepo) {
for ( int i = 0 ; i < worker.length ; i++ )
worker[i].setArticleIDRepository(articleIDRepo);
}
File getSpoolPath() {
if ( spoolPath.exists() == false )
spoolPath.mkdirs();
return spoolPath;
}
public void initialize() throws Exception {
//System.out.println(getClass().getName()+": init");
for ( int i = 0 ; i < worker.length ; i++ )
new Thread(worker[i],"NNTPSpool-"+i).start();
}
static class Worker extends AbstractLoggable implements Runnable {
private static final Lock lock = new Lock();
private final File spoolPath;
private final int threadIdleTime;
private ArticleIDRepository articleIDRepo;
private NNTPRepository repo;
Worker(int threadIdleTime,File spoolPath) {
this.threadIdleTime = threadIdleTime;
this.spoolPath = spoolPath;
}
void setArticleIDRepository(ArticleIDRepository articleIDRepo) {
this.articleIDRepo = articleIDRepo;
}
void setRepository(NNTPRepository repo) {
this.repo = repo;
}
// the threads race to grab a lock. if a thread wins it processes the article,
// if it loses it tries to lock and process the next article
public void run() {
getLogger().debug("in spool thread");
while ( Thread.currentThread().isInterrupted() == false ) {
String[] list = spoolPath.list();
for ( int i = 0 ; i < list.length ; i++ ) {
getLogger().debug("Files to process: "+list.length);
if ( lock.lock(list[i]) ) {
File f = new File(spoolPath,list[i]).getAbsoluteFile();
getLogger().debug("processing file: "+f.getAbsolutePath());
try {
process(f);
} catch(Exception ex) {
getLogger().debug("exception occured in processing file: "+
f.getAbsolutePath(),ex);
} finally {
lock.unlock(list[i]);
}
}
}
// this is good for other non idle threads
try { Thread.currentThread().sleep(threadIdleTime);
} catch(InterruptedException ex) { }
}
}
private void process(File f) throws Exception {
getLogger().debug("process: "+f.getAbsolutePath()+","+f.getCanonicalPath());
final MimeMessage msg;
String articleID;
{ // get the message for copying to destination groups.
FileInputStream fin = new FileInputStream(f);
msg = new MimeMessage(null,fin);
fin.close();
// ensure no duplicates exist.
String[] idheader = msg.getHeader("Message-Id");
articleID = (idheader!=null && idheader.length>0?idheader[0]:null);
if ( articleIDRepo.isExists(articleID) ) {
getLogger().debug("message already exists: "+articleID);
f.delete();
return;
}
if ( articleID == null ) {
articleID = articleIDRepo.generateArticleID();
msg.setHeader("Message-Id", articleID);
FileOutputStream fout = new FileOutputStream(f);
msg.writeTo(fout);
fout.close();
}
}
String[] headers = msg.getHeader("Newsgroups");
Properties prop = new Properties();
for ( int i = 0 ; i < headers.length ; i++ ) {
getLogger().debug("copying message to group: "+headers[i]);
NNTPGroup group = repo.getGroup(headers[i]);
if ( group == null ) {
getLogger().debug("group not found: "+headers[i]);
continue;
}
int artNum = group.getLastArticleNumber();
File root = (File)group.getPath();
File articleFile = null;
// this ensures that different threads do not create articles with
// same number
while( true ) {
articleFile = new File(root,(artNum+1)+"");
if (articleFile.createNewFile())
break;
}
getLogger().debug("copying message to: "+articleFile.getAbsolutePath());
prop.setProperty(group.getName(),articleFile.getName());
FileInputStream fin = new FileInputStream(f);
FileOutputStream fout = new FileOutputStream(articleFile);
IOUtil.copy(fin,fout);
fin.close();
fout.close();
}
articleIDRepo.addArticle(articleID,prop);
boolean delSuccess = f.delete();
if ( delSuccess == false )
getLogger().error("could not delete file: "+f.getAbsolutePath());
}
} // class Worker
}
1.1 jakarta-james/src/java/org/apache/james/nntpserver/repository/NNTPUtil.java
Index: NNTPUtil.java
===================================================================
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.james.nntpserver.repository;
import java.io.*;
import org.apache.avalon.framework.activity.Initializable;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.logger.Loggable;
import org.apache.james.nntpserver.NNTPException;
import org.apache.log.Logger;
// processes entries and sends to appropriate groups.
// eats up inappropriate entries.
public class NNTPUtil {
static File getDirectory(Configuration configuration,String child)
throws ConfigurationException
{
String str = configuration.getChild(child).getValue();
if ( str.toLowerCase().startsWith("file://") )
str = str.substring("file://".length());
File f = new File(str);
if ( f.exists() && f.isFile() )
throw new NNTPException("Expecting '"+f.getAbsolutePath()+"' directory");
if ( f.exists() == false )
f.mkdirs();
return f;
}
public static Object createInstance(Configuration configuration,Logger logger,
String clsName) throws ConfigurationException
{
try { clsName = configuration.getAttribute("class");
} catch(ConfigurationException ce) { }
try {
Object obj = Class.forName(clsName).newInstance();
if ( obj instanceof Loggable )
((Loggable)obj).setLogger( logger );
if ( obj instanceof Configurable )
((Configurable)obj).configure(configuration.getChild("configuration"));
return obj;
} catch(Exception ex) {
ex.printStackTrace();
throw new ConfigurationException("spooler initialization failed",ex);
}
}
public static void show(Configuration conf,PrintStream prt) {
prt.println("conf.getClass="+conf.getClass().getName());
prt.println("name="+conf.getName());
Configuration[] children = conf.getChildren();
for ( int i = 0 ; i < children.length ; i++ )
prt.println(i+". "+children[i].getName());
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: james-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: james-dev-help@jakarta.apache.org