package org.gjt.bugrat.mail;
import java.io.*;
import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;
import javax.activation.*;
import com.ice.syslog.*;
import org.gjt.bugrat.db.*;
import org.gjt.bugrat.dbi.*;
import org.gjt.mail.EnhancedMimeMsg;
import org.gjt.bugrat.util.BugRatLogger;
public
class MailReader
extends Object
implements BugRatConstants, BugRatLogger
{
public static final String DEFAULT_ADMIN_ADDRESS = "bugadmin";
public static final String DEFAULT_ROBOT_ADDRESS = "bugreport";
private static MailReader instance;
protected String jdbcUrl = null;
protected Properties jdbcProps = null;
protected String jdbcDriverClassName = null;
protected Session session;
protected boolean useSyslog = false;
protected int syslogFac = SyslogDefs.LOG_LOCAL0;
protected String syslogName = "bugratmail";
protected String syslogHostname = "localhost";
protected String mainMessageStr = null;
public static void
main( String[] argv )
{
MailReader.instance = new MailReader();
MailReader.instance.instanceMain( argv );
}
public void
instanceMain( String[] argv )
{
this.jdbcProps = new Properties();
this.processArguments( argv );
if ( this.getUseSyslog() )
{
try {
Syslog.open
( syslogHostname, syslogName, SyslogDefs.LOG_START );
}
catch ( SyslogException ex )
{
System.err.println( "error opening Syslog" );
ex.printStackTrace( System.err );
this.useSyslog = false;
}
}
MimeMessage msg = null;
Address fromAddr = null;
InternetAddress adminAddr = null;
try {
adminAddr = new InternetAddress( this.getAdminAddress() );
}
catch ( AddressException ex )
{
ex.printStackTrace( System.err );
}
try {
DBIManager dbiMgr = DBIManager.getInstance();
dbiMgr.setJDBCParameters
( this.jdbcUrl, this.jdbcDriverClassName,
this.jdbcProps, this );
dbiMgr.setCurrentDBIManager( DBIManager.JDBC );
DBConfig config = DBConfig.getInstance();
this.session =
Session.getInstance
( System.getProperties(), null );
msg = new MimeMessage( this.session, System.in );
Address[] addrs = msg.getFrom();
if ( addrs == null || addrs.length < 1 || addrs[0] == null )
{
// Alas, there is no 'From' to send help to...
this.forwardToAdmin
( this.session, "No From Address", msg );
throw new MessagingException
( "error parsing message, no From address" );
}
fromAddr = addrs[0];
this.processIncomingMessage( msg, fromAddr );
}
catch ( DBIException ex )
{
this.logMessage( SyslogDefs.LOG_ERR,
"error could not establish DBI, "
+ ex.getMessage() );
if ( fromAddr == null )
fromAddr = adminAddr;
// UNDONE make this a property!
String msgStr =
"An error occurred attempting to access the BugRat\n" +
"database. Therefore, your bug report could not be\n" +
"processed. You should resubmit your bug report at a\n" +
"later time in the event the database comes back online.\n" +
"\n" +
"If this problem persists, you should contact the BugRat\n" +
"administrator. You can do this by replying to this message,\n" +
"and your mail program should address the reply properly.\n";
this.sendErrorReport
( this.session, fromAddr, msg, msgStr, ex );
}
catch ( MessagingException ex )
{
this.logMessage( SyslogDefs.LOG_ERR,
"error parsing message from System.in, "
+ ex.getMessage() );
if ( fromAddr == null )
fromAddr = adminAddr;
String msgStr =
"An error occurred attempting to process the report\n" +
"that you emailed. Either the mail message was in a format\n" +
"that could not be parsed, or an error occurred reading the\n" +
"email's contents.\n" +
"\n" +
"Your email should either be a simple text message, or a\n" +
"MIME message. If it is a MIME message, then the report should\n" +
"be included in the main part, as opposed to an attachment.\n" +
"\n" +
"You may wish to resubmit the report with a different email\n" +
"program to see if that helps.\n" +
"\n" +
"If this problem persists, you should contact the BugRat\n" +
"administrator. You can do this by replying to this message,\n" +
"and your mail program should address the reply properly.\n";
this.sendErrorReport
( this.session, fromAddr, msg, msgStr, ex );
}
}
private void
processIncomingMessage( MimeMessage msg, Address fromAddress )
throws MessagingException, DBIException
{
this.mainMessageStr = null;
this.parseMessagePart( msg );
if ( this.mainMessageStr == null )
{
this.forwardToAdmin
( this.session, "Empty BugRat Email Report", msg );
this.sendHelpInformation( this.session, fromAddress, msg );
}
this.logMessage( SyslogDefs.LOG_INFO,
"Have Main Message String length="
+ this.mainMessageStr.length()
+ ", From '"
+ fromAddress.toString() + "'" );
try {
Date submitDate = msg.getReceivedDate();
if ( submitDate == null )
submitDate = new Date();
Report rep =
this.parseReportText
( fromAddress, submitDate, this.mainMessageStr );
if ( rep != null )
{
//
// File the email message
//
EmailMsg email = EmailMsg.getNewEmailMsg();
Address[]addrs;
addrs = msg.getFrom();
String fromStr = InternetAddress.toString( addrs );
addrs = msg.getAllRecipients();
String toStr = InternetAddress.toString( addrs );
email.setTo( toStr );
email.setFrom( fromStr );
email.setSubject( msg.getSubject() );
email.setMessageID( msg.getMessageID() );
email.setReceiveDate( msg.getReceivedDate() );
EmailContent content = new EmailContent( email.getId() );
try {
ByteArrayOutputStream os =
new ByteArrayOutputStream
( msg.getSize() + 2048 );
msg.writeTo( os );
content.setContent( os.toByteArray() );
email.setContent( content );
}
catch ( java.io.IOException ex )
{
throw new DBIException
( "email content IOException: "
+ ex.getMessage() );
}
catch ( MessagingException ex )
{
throw new DBIException
( "email content MessagingException: "
+ ex.getMessage() );
}
rep.getDescription().setEmail( email );
rep.commit(); // commits the email, which commits content
this.sendResults( this.session, fromAddress, rep );
}
else
{
throw new MessagingException
( "could not parse the report (null)" );
}
}
catch ( IOException ex )
{
throw new MessagingException
( "IOException parsing message, " + ex.getMessage() );
}
catch ( DBIException ex )
{
throw new MessagingException
( "DBIException storing report, " + ex.getMessage() );
}
}
private Report
parseReportText( Address from, Date submitDate, String msgText )
throws IOException, MessagingException, DBIException
{
Report rep = null;
BufferedReader rdr =
new BufferedReader( new StringReader( msgText ) );
Hashtable fields = new Hashtable();
String multiFieldName = null;
StringBuffer multiBuf = new StringBuffer();
boolean readingMultiLine = false;
for ( ; ; )
{
String line = rdr.readLine();
if ( line == null )
{
if ( readingMultiLine )
{
readingMultiLine = false;
fields.put( multiFieldName, multiBuf.toString() );
}
break;
}
// parts[0] if fieldname, or null
// parts[1] is value, or line (if fieldname == null)
String[] parts = this.parseFieldLine( line );
if ( parts[0] != null )
{
if ( readingMultiLine )
{
readingMultiLine = false;
fields.put( multiFieldName, multiBuf.toString() );
}
if ( this.isMultiLineField( parts[0] ) )
{
readingMultiLine = true;
multiBuf.setLength( 0 );
multiFieldName = parts[0];
multiBuf.append( parts[1] );
}
else
{
fields.put( parts[0], parts[1] );
}
}
else if ( readingMultiLine )
{
if ( multiBuf.length() > 0 )
multiBuf.append( "\n" );
multiBuf.append( parts[1] );
}
}
if ( fields.get( "Project" ) == null )
throw new MessagingException
( "missing the 'Project' field" );
if ( fields.get( "Category" ) == null )
throw new MessagingException
( "missing the 'Category' field" );
if ( fields.get( "SubCategory" ) == null )
throw new MessagingException
( "missing the 'SubCategory' field" );
if ( fields.get( "Class" ) == null )
throw new MessagingException
( "missing the 'Class' field" );
if ( fields.get( "Priority" ) == null )
throw new MessagingException
( "missing the 'Priority' field" );
if ( fields.get( "Severity" ) == null )
throw new MessagingException
( "missing the 'Severity' field" );
if ( fields.get( "Synopsis" ) == null )
throw new MessagingException
( "missing the 'Synopsis' field" );
if ( fields.get( "Release" ) == null )
throw new MessagingException
( "missing the 'Release' field" );
if ( fields.get( "EnvJVM" ) == null )
throw new MessagingException
( "missing the 'EnvJVM' field" );
if ( fields.get( "EnvOS" ) == null )
throw new MessagingException
( "missing the 'EnvOS' field" );
if ( fields.get( "EnvOSRel" ) == null )
throw new MessagingException
( "missing the 'EnvOSRel' field" );
if ( fields.get( "EnvPlatform" ) == null )
throw new MessagingException
( "missing the 'EnvPlatform' field" );
DBConfig config = DBConfig.getInstance();
if ( fields.get( "State" ) == null )
fields.put( "State",
config.getDefaultReportState() );
if ( fields.get( "Confidence" ) == null )
fields.put( "Confidence",
config.getDefaultConfidence() );
String fromStr =
((InternetAddress) from).getAddress();
String nameStr =
((InternetAddress) from).getPersonal();
if ( nameStr == null )
nameStr = "(unknown)";
Person person = Person.getPerson( fromStr );
if ( person == null )
{
person =
Person.getNewPerson
( PERSON_ANON, fromStr, nameStr );
}
rep = Report.getNewReport();
rep.setSource( SOURCE_EMAIL );
rep.setSubmitter( person.getId() );
rep.setSubmitted( submitDate );
rep.setProject( (String)fields.get( "Project" ) );
rep.setCategory( (String)fields.get( "Category" ) );
rep.setSubCategory( (String)fields.get( "SubCategory" ) );
rep.setReportClass( (String)fields.get( "Class" ) );
rep.setState( (String)fields.get( "State" ) );
rep.setSeverity( (String)fields.get( "Severity" ) );
rep.setPriority( (String)fields.get( "Priority" ) );
rep.setConfidence( (String)fields.get( "Confidence" ) );
Description desc = rep.getDescription();
desc.setSynopsis( (String)fields.get( "Synopsis" ) );
String descText = (String) fields.get( "Description" );
desc.setMimeType( "text/plain" );
if ( descText != null )
{
if ( config.isTextHTML( descText ) )
desc.setMimeType( "text/html" );
desc.setDescription( descText );
}
EnvDescription eDesc = rep.getEnvDescription();
eDesc.setRelease( (String)fields.get( "Release" ) );
eDesc.setJVM( (String)fields.get( "EnvJVM" ) );
eDesc.setOS( (String)fields.get( "EnvOS" ) );
eDesc.setOSRelease( (String)fields.get( "EnvOSRel" ) );
eDesc.setPlatform( (String)fields.get( "EnvPlatform" ) );
descText = (String) fields.get( "EnvDescription" );
if ( descText != null )
eDesc.setDescription( descText );
return rep;
}
private String[]
parseFieldLine( String line )
{
String[] result = new String[2];
result[0] = null;
result[1] = line;
if ( line.startsWith( "> " ) )
{
int index = line.indexOf( ": ", 2 );
if ( index > 2 )
{
result[0] = line.substring( 2, index );
result[1] = line.substring( index + 2 );
}
}
return result;
}
private boolean
isMultiLineField( String field )
{
if ( field.equalsIgnoreCase( "Description" ) )
return true;
else if ( field.equalsIgnoreCase( "Reproduction" ) )
return true;
else if ( field.equalsIgnoreCase( "WorkAround" ) )
return true;
else if ( field.equalsIgnoreCase( "EnvDescription" ) )
return true;
return false;
}
private void
parseMessagePart( Part bodyPart )
{
if ( this.mainMessageStr != null )
return;
if ( bodyPart == null )
return;
//------------
// now get a content viewer for the main type...
//------------
try {
if ( false )
{
System.out.println
("----------------------------------------");
System.out.println
( "FileName: " + bodyPart.getFileName() );
System.out.println
( "Content-Type: " + bodyPart.getContentType() );
System.out.println
( "Size: " + bodyPart.getSize() );
System.out.println
( "Class: " + bodyPart.getClass().toString() );
}
int partSize = bodyPart.getSize();
String contentType = bodyPart.getContentType();
String partName = bodyPart.getFileName();
// -----------------------------------------------------
// ---------- M U L T I P A R T
// -----------------------------------------------------
if ( bodyPart.isMimeType( "multipart/*" ) )
{
Multipart mPart = (Multipart) bodyPart.getContent();
int partCount = mPart.getCount();
for ( int i = 0, row = 0
; i < partCount && this.mainMessageStr == null
; i++ )
{
this.parseMessagePart( mPart.getBodyPart(i) );
}
}
// -----------------------------------------------------
// ---------- T E X T / *
// -----------------------------------------------------
else if ( bodyPart.isMimeType( "text/*" ) )
{
this.mainMessageStr =
(String) bodyPart.getContent();
}
// -----------------------------------------------------
// ---------- O T H E R
// -----------------------------------------------------
else
{
// Sorry, we only accept 'text/*'...
}
}
catch ( Exception ex )
{
if ( true )
ex.printStackTrace( System.err );
}
}
private String
getRobotAddress()
{
String adminAddr = null;
try {
DBConfig config = DBConfig.getInstance();
adminAddr = config.getProperty( "bratm.robotAddress" );
}
catch ( DBIException ex )
{
adminAddr = DEFAULT_ROBOT_ADDRESS;
}
return adminAddr;
}
private String
getAdminAddress()
{
String adminAddr = null;
try {
DBConfig config = DBConfig.getInstance();
adminAddr = config.getProperty( "bratm.adminAddress" );
}
catch ( DBIException ex )
{
adminAddr = DEFAULT_ADMIN_ADDRESS;
}
return adminAddr;
}
private void
forwardToAdmin( Session session, String subject, MimeMessage msg )
throws MessagingException, DBIException
{
String botAddr = this.getRobotAddress();
String adminAddr = this.getAdminAddress();
try {
Address from = new InternetAddress( botAddr );
Address to = new InternetAddress( adminAddr );
Address[] toAddrs = new Address[1];
toAddrs[0] = to;
StringBuffer msgText = new StringBuffer();
msgText.append( "BugRat could not process the attached message.\n" );
EnhancedMimeMsg email = new EnhancedMimeMsg( session );
Vector attach = null;
if ( msg != null )
{
attach = new Vector();
attach.addElement( msg );
}
email.buildMimeMessage
( from, from, toAddrs, null, null,
subject, msgText.toString(), attach );
Transport.send( email );
}
catch ( MessagingException ex )
{
// UNDONE
System.err.print
( "ERROR SENDING ERROR REPORT: " + ex.getMessage() );
System.exit(2);
}
}
private void
sendErrorReport(
Session session, Address to,
MimeMessage msg, String msgStr, Exception errEx )
{
String adminAddr = this.getAdminAddress();
try {
Address from = new InternetAddress( adminAddr );
Address[] toAddrs = new Address[1];
toAddrs[0] = to;
StringBuffer msgText = new StringBuffer( msgStr );
if ( errEx != null )
{
msgText.append( "\n" );
msgText.append
( "NOTE The error generated the following message:\n" );
msgText.append( " " );
msgText.append( errEx.getMessage() );
msgText.append( "\n" );
}
EnhancedMimeMsg email = new EnhancedMimeMsg( session );
Vector attach = null;
if ( msg != null )
{
attach = new Vector();
attach.addElement( msg );
}
email.buildMimeMessage
( from, from, toAddrs, null, null,
"BugRat Report Error",
msgText.toString(), attach );
Transport.send( email );
}
catch ( MessagingException ex )
{
// UNDONE
System.err.print
( "ERROR SENDING ERROR REPORT: " + ex.getMessage() );
System.exit(2);
}
}
private void
sendResults( Session session, Address to, Report rep )
throws MessagingException, DBIException
{
String adminAddr = this.getAdminAddress();
EnhancedMimeMsg msg = new EnhancedMimeMsg( session );
Address from = new InternetAddress( adminAddr );
Address[] toAddrs = new Address[1];
toAddrs[0] = to;
StringBuffer msgText = new StringBuffer();
msgText.append(
"Your bug report has been successfully processed by the\n" +
"BugRat automated email reporting system.\n\n" +
"Your report was assigned ID #" + rep.getId() + ".\n" +
"You can review your report online at this address:\n" +
// UNDONE this url MUST be a property!!!
" <http://www.gjt.org/servlets" +
"/BugRatViewer/ShowReport.html/" +
rep.getId() + ">\n\n" );
msg.buildMimeMessage
( from, from, toAddrs, null, null,
"BugRat Report Results (Subject:)",
msgText.toString(), null );
Transport.send( msg );
}
private void
sendHelpInformation( Session session, Address to, MimeMessage msg )
throws MessagingException, DBIException
{
DBConfig config = DBConfig.getInstance();
String adminAddr = this.getAdminAddress();
String robotAddr = this.getRobotAddress();
EnhancedMimeMsg helpMsg = new EnhancedMimeMsg( session );
Address from = new InternetAddress( adminAddr );
Address[] toAddrs = new Address[1];
toAddrs[0] = to;
StringBuffer msgText = new StringBuffer();
String introText = config.getProperty( "bratm.helpIntro" );
if ( introText == null )
{
msgText.append(
"*** ERROR, getting help intro from database.\n" +
"*** Property 'bratm.helpIntro' could not be found.\n\n" );
}
else
{
msgText.append( introText );
}
msgText.append
( " ================= INSTRUCTIONS =================\n\n" );
msgText.append
( "To report bugs via email, you send email to: " );
msgText.append( robotAddr );
msgText.append( "\n\n" );
String instrText =
config.getProperty( "bratm.helpInstruct" );
if ( instrText == null )
{
msgText.append(
"*** ERROR, getting help instructions from database.\n" +
"*** Property 'bratm.helpInstruct' could not be found.\n\n" );
}
else
{
msgText.append( instrText );
}
this.appendFieldValues( msgText );
Vector attach = null;
if ( msg != null )
{
attach = new Vector();
attach.addElement( msg );
}
helpMsg.buildMimeMessage
( from, from, toAddrs, null, null,
"BugRat Email Help", msgText.toString(), attach );
Transport.send( helpMsg );
}
private void
appendFieldValues( StringBuffer msg )
throws DBIException
{
DBConfig config = DBConfig.getInstance();
msg.append( "The following is a hierarchical listing of the\n" );
msg.append( "possible values for Project, Category, and SubCategory.\n\n" );
Vector pros = config.getProjects();
for ( int i = 0, isz = pros.size() ; i < isz ; ++i )
{
Category project = (Category) pros.elementAt(i);
Vector cats =
config.getProjectCategories( project.getProject() );
msg.append( " Project '" );
msg.append( project.getProject() );
msg.append( "' (" );
msg.append( project.getName() );
msg.append( ")\n\n" );
for ( int j = 0, jsz = cats.size() ; j < jsz ; ++j )
{
Category category = (Category) cats.elementAt(j);
Vector subs =
config.getCategorySubCats
( project.getProject(), category.getCategory() );
msg.append( " Category '" );
msg.append( category.getCategory() );
msg.append( "' (" );
msg.append( category.getName() );
msg.append( ")\n\n" );
for ( int k = 0, ksz = subs.size() ; k < ksz ; ++k )
{
Category subcat = (Category) subs.elementAt(k);
msg.append( " SubCategory '" );
msg.append( subcat.getSubCategory() );
msg.append( "' (" );
msg.append( subcat.getName() );
msg.append( ")\n" );
}
msg.append( "\n" );
}
msg.append( "\n" );
}
msg.append( "\n" );
this.appendLevelParamList
( msg, "Priorities", config.getPriorities() );
msg.append( "\n" );
this.appendLevelParamList
( msg, "Severities", config.getSeverities() );
msg.append( "\n" );
this.appendLevelParamList
( msg, "Classes", config.getClasses() );
msg.append( "\n" );
this.appendLevelParamList
( msg, "Confidences", config.getConfidences() );
}
private void
appendLevelParamList( StringBuffer buf, String table, Vector lpV )
{
buf.append( "The following is a listing of valid '" );
buf.append( table );
buf.append( "'.\n\n" );
for ( int i = 0, sz = lpV.size() ; i < sz ; ++i )
{
LevelParam lp = (LevelParam) lpV.elementAt(i);
buf.append( " " );
buf.append( lp.getName() );
buf.append( "\n" );
}
}
public boolean
getUseSyslog()
{
return this.useSyslog;
}
public void
logMessage( int level, String msg )
{
if ( this.getUseSyslog() )
Syslog.log( this.syslogFac, level, msg );
else
System.err.println( msg );
}
public void
log( String msg )
{
this.logMessage( SyslogDefs.LOG_INFO, msg );
}
public void
log( String msg, Throwable ex )
{
this.logMessage( SyslogDefs.LOG_ERR, msg );
this.logMessage( SyslogDefs.LOG_ERR, ex.getMessage() );
StringWriter sW = new StringWriter();
ex.printStackTrace( new PrintWriter( sW ) );
this.logMessage( SyslogDefs.LOG_ERR, sW.toString() );
}
private void
processArguments( String[] argv )
{
for ( int i = 0 ; i < argv.length ; ++i )
{
if ( argv[i].equals( "-?" ) || argv[i].equals( "--help" ) )
{
this.printUsage();
System.exit(1);
}
else if ( argv[i].equals( "--jdbcUrl" ) )
{
this.jdbcUrl = argv[++i];
}
else if ( argv[i].equals( "--jdbcProp" ) )
{
int index = argv[++i].indexOf( "=" );
if ( index != -1 )
{
this.jdbcProps.put
( argv[++i].substring( 0, index ),
argv[++i].substring( index + 1 ) );
}
}
else if ( argv[i].equals( "--jdbcDriverClass" ) )
{
this.jdbcDriverClassName = argv[++i];
}
else if ( argv[i].equals( "--syslog" ) )
{
this.useSyslog = true;
}
else if ( argv[i].equals( "--syslog_name" ) )
{
this.syslogName = argv[++i];
}
else if ( argv[i].equals( "--syslog_host" ) )
{
this.syslogHostname = argv[++i];
}
else
{
System.err.println( "ERROR" );
System.err.println( "ERROR unknown argument: " + argv[i] );
System.err.println( "ERROR" );
this.printUsage();
System.exit(2);
}
}
}
public void
printUsage()
{
System.err.println( "usage: java org.gjt.bugrat.mail.MailReader [options]" );
System.err.println( "options:" );
System.err.println( " --jcvsUrl jdbc_driver_url" );
System.err.println( " This is the JDBC URL that describes to your JDBC" );
System.err.println( " driver how to connect to the BugRat database." );
System.err.println( "" );
System.err.println( " --jdbcProp name=value" );
System.err.println( " This adds a property to the Properties that are" );
System.err.println( " passed to the JDBC driver when the connection is" );
System.err.println( " opened. Use this to pass any properties that you" );
System.err.println( " can not specify in the JDBC URL." );
System.err.println( "" );
System.err.println( " --jdbcDriverClass jdbc.driver.class.name." );
System.err.println( " The fully qualified name of the JDBC driver to use." );
System.err.println( " Note that the driver must be located on the CLASSPATH." );
System.err.println( "" );
System.err.println( " --syslog" );
System.err.println( " Uses Syslog for logging messages" );
System.err.println( "" );
System.err.println( " --syslog_name" );
System.err.println( " The application name that will be used for Syslog logging." );
System.err.println( "" );
System.err.println( " --syslog_host" );
System.err.println( " The Syslog host that will be logged to." );
System.err.println( "" );
System.err.println( " The reader expects a MIME compliant email message" );
System.err.println( " on stdin, including the message's headers." );
}
}