1 17 18 43 44 45 package org.apache.james.mailrepository; 46 47 import org.apache.avalon.framework.activity.Initializable; 48 import org.apache.avalon.framework.component.Component; 49 import org.apache.avalon.framework.component.ComponentException; 50 import org.apache.avalon.framework.component.ComponentManager; 51 import org.apache.avalon.framework.component.Composable; 52 import org.apache.avalon.framework.configuration.Configurable; 53 import org.apache.avalon.framework.configuration.Configuration; 54 import org.apache.avalon.framework.configuration.ConfigurationException; 55 import org.apache.avalon.framework.context.Context; 56 import org.apache.avalon.framework.context.ContextException; 57 import org.apache.avalon.framework.context.Contextualizable; 58 import org.apache.avalon.framework.logger.AbstractLogEnabled; 59 import org.apache.james.core.MailImpl; 60 import org.apache.james.services.MailRepository; 61 import org.apache.oro.text.regex.MalformedPatternException; 62 import org.apache.oro.text.regex.Perl5Compiler; 63 import org.apache.oro.text.regex.Pattern; 64 import org.apache.oro.text.regex.Perl5Matcher; 65 66 import javax.mail.MessagingException ; 67 import javax.mail.Session ; 68 import javax.mail.internet.MimeMessage ; 69 import javax.mail.internet.InternetAddress ; 70 import java.util.*; 71 import java.io.*; 72 import java.security.NoSuchAlgorithmException ; 73 import java.security.MessageDigest ; 74 import java.text.SimpleDateFormat ; 75 import java.lang.reflect.Array ; 76 77 104 105 106 public class MBoxMailRepository 107 extends AbstractLogEnabled 108 implements MailRepository, Component, Contextualizable, Composable, Configurable, Initializable { 109 110 111 static final SimpleDateFormat dy = new SimpleDateFormat ("EE MMM dd HH:mm:ss yyyy", Locale.US); 112 static final String LOCKEXT = ".lock"; 113 static final String WORKEXT = ".work"; 114 static final int LOCKSLEEPDELAY = 2000; static final int MAXSLEEPTIMES = 100; static final long MLISTPRESIZEFACTOR = 10 * 1024; static final long DEFAULTMLISTCAPACITY = 20; 119 122 private static boolean BUFFERING = true; 123 124 127 private static final boolean DEEP_DEBUG = true; 128 129 132 private Context context; 133 134 138 private Hashtable mList = null; 139 142 private String mboxFile; 143 144 147 public interface MessageAction { 148 public boolean isComplete(); public MimeMessage messageAction(String messageSeparator, String bodyText, long messageStart); 150 } 151 152 153 160 private String getRawMessage(MimeMessage mc) throws IOException, MessagingException { 161 162 ByteArrayOutputStream rawMessage = new ByteArrayOutputStream(); 163 mc.writeTo(rawMessage); 164 return rawMessage.toString(); 165 } 166 167 171 private MimeMessage convertTextToMimeMessage(String emailBody) { 172 MimeMessage mimeMessage = null; 174 ByteArrayInputStream mb = new ByteArrayInputStream(emailBody.getBytes()); 176 Properties props = System.getProperties(); 177 Session session = Session.getDefaultInstance(props); 178 String toAddr = null; 179 try { 180 mimeMessage = new MimeMessage (session, mb); 181 182 183 } catch (MessagingException e) { 184 getLogger().error("Unable to parse mime message!", e); 185 } 186 187 if (mimeMessage == null && getLogger().isDebugEnabled()) { 188 StringBuffer logBuffer = 189 new StringBuffer (128) 190 .append(this.getClass().getName()) 191 .append(" Mime message is null"); 192 getLogger().debug(logBuffer.toString()); 193 } 194 195 216 return mimeMessage; 217 } 218 219 225 private String generateKeyValue(String emailBody) throws NoSuchAlgorithmException { 226 byte[] digArray = MessageDigest.getInstance("MD5").digest(emailBody.getBytes()); 228 StringBuffer digest = new StringBuffer (); 229 for (int i = 0; i < digArray.length; i++) { 230 digest.append(Integer.toString(digArray[i], Character.MAX_RADIX).toUpperCase()); 231 } 232 return digest.toString(); 233 } 234 235 240 private MimeMessage parseMboxFile(RandomAccessFile ins, MessageAction messAct) { 241 if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) { 242 StringBuffer logBuffer = 243 new StringBuffer (128) 244 .append(this.getClass().getName()) 245 .append(" Start parsing ") 246 .append(mboxFile); 247 248 getLogger().debug(logBuffer.toString()); 249 } 250 try { 251 252 Perl5Compiler sepMatchCompiler = new Perl5Compiler(); 253 Pattern sepMatchPattern = sepMatchCompiler.compile("^From (.*) (.*):(.*):(.*)$"); 254 Perl5Matcher sepMatch = new Perl5Matcher(); 255 256 int c; 257 boolean inMessage = false; 258 StringBuffer messageBuffer = new StringBuffer (); 259 String previousMessageSeparator = null; 260 boolean foundSep = false; 261 262 long prevMessageStart = ins.getFilePointer(); 263 if (BUFFERING) { 264 String line = null; 265 while ((line = ins.readLine()) != null) { 266 foundSep = sepMatch.contains(line + "\n", sepMatchPattern); 267 268 if (foundSep && inMessage) { 269 MimeMessage endResult = messAct.messageAction(previousMessageSeparator, messageBuffer.toString(), prevMessageStart); 273 if (messAct.isComplete()) { 274 return endResult; 276 } 277 previousMessageSeparator = line; 278 prevMessageStart = ins.getFilePointer() - line.length(); 279 messageBuffer = new StringBuffer (); 280 inMessage = true; 281 } 282 if (foundSep && !inMessage) { 284 previousMessageSeparator = line.toString(); 285 inMessage = true; 286 } 287 if (!foundSep && inMessage) { 288 messageBuffer.append(line).append("\n"); 289 } 290 } 291 } else { 292 StringBuffer line = new StringBuffer (); 293 while ((c = ins.read()) != -1) { 294 if (c == 10) { 295 foundSep = sepMatch.contains(line.toString(), sepMatchPattern); 296 if (foundSep && inMessage) { 297 MimeMessage endResult = messAct.messageAction(previousMessageSeparator, messageBuffer.toString(), prevMessageStart); 301 if (messAct.isComplete()) { 302 return endResult; 304 } 305 previousMessageSeparator = line.toString(); 306 prevMessageStart = ins.getFilePointer() - line.length(); 307 messageBuffer = new StringBuffer (); 308 inMessage = true; 309 } 310 if (foundSep && inMessage == false) { 312 previousMessageSeparator = line.toString(); 313 inMessage = true; 314 } 315 if (!foundSep) { 316 messageBuffer.append(line).append((char) c); 317 } 318 line = new StringBuffer (); } else { 320 line.append((char) c); 321 } 322 } 323 } 324 325 if (messageBuffer.length() != 0) { 326 return messAct.messageAction(previousMessageSeparator, messageBuffer.toString(), prevMessageStart); 328 } 329 } catch (IOException ioEx) { 330 getLogger().error("Unable to write file (General I/O problem) " + mboxFile, ioEx); 331 } catch (MalformedPatternException e) { 332 getLogger().error("Bad regex passed " + mboxFile, e); 333 } finally { 334 if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) { 335 StringBuffer logBuffer = 336 new StringBuffer (128) 337 .append(this.getClass().getName()) 338 .append(" Finished parsing ") 339 .append(mboxFile); 340 341 getLogger().debug(logBuffer.toString()); 342 } 343 } 344 return null; 345 } 346 347 354 private MimeMessage findMessage(String key) { 355 MimeMessage foundMessage = null; 356 final String keyValue = key; 357 358 foundMessage = selectMessage(key); 360 if (foundMessage == null) { 361 mList = null; 366 loadKeys(); 367 foundMessage = selectMessage(key); 368 } 369 return foundMessage; 370 } 371 372 376 private MimeMessage selectMessage(final String key) { 377 MimeMessage foundMessage = null; 378 if (mList == null || !mList.containsKey(key)) { 380 if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) { 382 StringBuffer logBuffer = 383 new StringBuffer (128) 384 .append(this.getClass().getName()) 385 .append(" mList - key not found ") 386 .append(mboxFile); 387 388 getLogger().debug(logBuffer.toString()); 389 } 390 return foundMessage; 391 } 392 long messageStart = ((Long ) mList.get(key)).longValue(); 393 if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) { 394 StringBuffer logBuffer = 395 new StringBuffer (128) 396 .append(this.getClass().getName()) 397 .append(" Load message starting at offset ") 398 .append(messageStart) 399 .append(" from file ") 400 .append(mboxFile); 401 402 getLogger().debug(logBuffer.toString()); 403 } 404 RandomAccessFile ins = null; 406 try { 407 ins = new RandomAccessFile(mboxFile, "r"); 408 if (messageStart != 0) { 409 ins.seek(messageStart - 1); 410 } 411 MessageAction op = new MessageAction() { 412 public boolean isComplete() { return true; } 413 public MimeMessage messageAction(String messageSeparator, String bodyText, long messageStart) { 414 try { 415 if (key.equals(generateKeyValue(bodyText))) { 416 getLogger().debug(this.getClass().getName() + " Located message. Returning MIME message"); 417 return convertTextToMimeMessage(bodyText); 418 } 419 } catch (NoSuchAlgorithmException e) { 420 getLogger().error("MD5 not supported! ",e); 421 } 422 return null; 423 } 424 }; 425 foundMessage = this.parseMboxFile(ins, op); 426 } catch (FileNotFoundException e) { 427 getLogger().error("Unable to save(open) file (File not found) " + mboxFile, e); 428 } catch (IOException e) { 429 getLogger().error("Unable to write file (General I/O problem) " + mboxFile, e); 430 } finally { 431 if (foundMessage == null) { 432 if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) { 433 StringBuffer logBuffer = 434 new StringBuffer (128) 435 .append(this.getClass().getName()) 436 .append(" select - message not found ") 437 .append(mboxFile); 438 439 getLogger().debug(logBuffer.toString()); 440 } 441 } 442 if (ins != null) try { ins.close(); } catch (IOException e) { getLogger().error("Unable to close file (General I/O problem) " + mboxFile, e); } 443 return foundMessage; 444 } 445 } 446 447 450 private synchronized void loadKeys() { 451 if (mList!=null) { 452 return; 453 } 454 RandomAccessFile ins = null; 455 try { 456 ins = new RandomAccessFile(mboxFile, "r"); 457 long initialCapacity = (ins.length() > MLISTPRESIZEFACTOR ? ins.length() /MLISTPRESIZEFACTOR : 0); 458 if (initialCapacity < DEFAULTMLISTCAPACITY ) { 459 initialCapacity = DEFAULTMLISTCAPACITY; 460 } 461 if (initialCapacity > Integer.MAX_VALUE) { 462 initialCapacity = Integer.MAX_VALUE - 1; 463 } 464 this.mList = new Hashtable((int)initialCapacity); 465 this.parseMboxFile(ins, new MessageAction() { 466 public boolean isComplete() { return false; } 467 public MimeMessage messageAction(String messageSeparator, String bodyText, long messageStart) { 468 try { 469 String key = generateKeyValue(bodyText); 470 mList.put(key, new Long (messageStart)); 471 if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) { 472 getLogger().debug(this.getClass().getName() + " Key " + key + " at " + messageStart); 473 } 474 475 } catch (NoSuchAlgorithmException e) { 476 getLogger().error("MD5 not supported! ",e); 477 } 478 return null; 479 } 480 }); 481 } catch (FileNotFoundException e) { 483 getLogger().error("Unable to save(open) file (File not found) " + mboxFile, e); 484 } catch (IOException e) { 485 getLogger().error("Unable to write file (General I/O problem) " + mboxFile, e); 486 } finally { 487 if (ins != null) try { ins.close(); } catch (IOException e) { getLogger().error("Unable to close file (General I/O problem) " + mboxFile, e); } 488 } 489 } 490 491 492 495 public void contextualize(final Context context) 496 throws ContextException { 497 this.context = context; 498 } 499 500 504 public void store(MailImpl mc) { 505 506 if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) { 507 StringBuffer logBuffer = 508 new StringBuffer (128) 509 .append(this.getClass().getName()) 510 .append(" Will store message to file ") 511 .append(mboxFile); 512 513 getLogger().debug(logBuffer.toString()); 514 } 515 this.mList = null; 516 String fromHeader = null; 518 String message = null; 519 try { 520 message = getRawMessage(mc.getMessage()); 521 fromHeader = "From " + ((InternetAddress )mc.getMessage().getFrom()[0]).getAddress() + " " + dy.format(Calendar.getInstance().getTime()); 522 } catch (IOException e) { 523 getLogger().error("Unable to parse mime message for " + mboxFile, e); 524 } catch (MessagingException e) { 525 getLogger().error("Unable to parse mime message for " + mboxFile, e); 526 } 527 RandomAccessFile saveFile = null; 529 try { 530 saveFile = new RandomAccessFile(mboxFile, "rw"); 531 saveFile.seek(saveFile.length()); saveFile.writeBytes((fromHeader + "\n")); 533 saveFile.writeBytes((message + "\n")); 534 saveFile.close(); 535 536 } catch (FileNotFoundException e) { 537 getLogger().error("Unable to save(open) file (File not found) " + mboxFile, e); 538 } catch (IOException e) { 539 getLogger().error("Unable to write file (General I/O problem) " + mboxFile, e); 540 } 541 } 542 543 547 public Iterator list() { 548 loadKeys(); 549 findMessage((String ) mList.keySet().iterator().next()); 553 if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) { 554 StringBuffer logBuffer = 555 new StringBuffer (128) 556 .append(this.getClass().getName()) 557 .append(" ") 558 .append(mList.size()) 559 .append(" keys to be iterated over."); 560 561 getLogger().debug(logBuffer.toString()); 562 } 563 return mList.keySet().iterator(); 564 } 565 566 571 public MailImpl retrieve(String key) { 572 573 loadKeys(); 574 MailImpl res = null; 575 try { 576 MimeMessage foundMessage = findMessage(key); 577 if (foundMessage == null) { 578 getLogger().error("found message is null!"); 579 return null; 580 } 581 res = new MailImpl(foundMessage); 582 res.setName(key); 583 if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) { 584 StringBuffer logBuffer = 585 new StringBuffer (128) 586 .append(this.getClass().getName()) 587 .append(" Retrieving entry for key ") 588 .append(key); 589 590 getLogger().debug(logBuffer.toString()); 591 } 592 } catch (MessagingException e) { 593 getLogger().error("Unable to parse mime message for " + mboxFile + "\n" + e.getMessage(), e); 594 } 595 return res; 596 } 597 598 602 public void remove(MailImpl mail) { 603 Vector delVec = new Vector(); 605 delVec.addElement(mail); 606 remove(delVec); 607 } 608 609 614 private void lockMBox() throws Exception { 615 String lockFileName = mboxFile + LOCKEXT; 617 int sleepCount = 0; 618 File mBoxLock = new File(lockFileName); 619 if (!mBoxLock.createNewFile()) { 620 while (!mBoxLock.createNewFile() && sleepCount < MAXSLEEPTIMES) { 623 try { 624 if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) { 625 StringBuffer logBuffer = 626 new StringBuffer (128) 627 .append(this.getClass().getName()) 628 .append(" Waiting for lock on file ") 629 .append(mboxFile); 630 631 getLogger().debug(logBuffer.toString()); 632 } 633 634 Thread.sleep(LOCKSLEEPDELAY); 635 sleepCount++; 636 } catch (InterruptedException e) { 637 getLogger().error("File lock wait for " + mboxFile + " interrupted!",e); 638 639 } 640 } 641 if (sleepCount >= MAXSLEEPTIMES) { 642 throw new Exception ("Unable to get lock on file " + mboxFile); 643 } 644 } 645 } 646 647 650 private void unlockMBox() { 651 String lockFileName = mboxFile + LOCKEXT; 653 File mBoxLock = new File(lockFileName); 654 if (!mBoxLock.delete()) { 655 StringBuffer logBuffer = 656 new StringBuffer (128) 657 .append(this.getClass().getName()) 658 .append(" Failed to delete lock file ") 659 .append(lockFileName); 660 getLogger().error(logBuffer.toString()); 661 } 662 } 663 664 665 666 671 public void remove(final Collection mails) 672 { 673 if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) { 674 StringBuffer logBuffer = 675 new StringBuffer (128) 676 .append(this.getClass().getName()) 677 .append(" Removing entry for key ") 678 .append(mails); 679 680 getLogger().debug(logBuffer.toString()); 681 } 682 try { 687 RandomAccessFile ins = new RandomAccessFile(mboxFile, "r"); final RandomAccessFile outputFile = new RandomAccessFile(mboxFile + WORKEXT, "rw"); parseMboxFile(ins, new MessageAction() { 690 public boolean isComplete() { return false; } 691 public MimeMessage messageAction(String messageSeparator, String bodyText, long messageStart) { 692 try { 694 String currentKey=generateKeyValue(bodyText); 695 boolean foundKey=false; 696 Iterator mailList = mails.iterator(); 697 String key; 698 while (mailList.hasNext()) { 699 key = ((MailImpl)mailList.next()).getName(); 701 if (key.equals(currentKey)) { 702 foundKey = true; 704 break; 705 } 706 } 707 if (foundKey == false) 708 { 709 outputFile.writeBytes(messageSeparator + "\n"); 711 outputFile.writeBytes(bodyText); 712 713 } 714 } catch (NoSuchAlgorithmException e) { 715 getLogger().error("MD5 not supported! ",e); 716 } catch (IOException e) { 717 getLogger().error("Unable to write file (General I/O problem) " + mboxFile, e); 718 } 719 return null; 720 } 721 }); 722 ins.close(); 723 outputFile.close(); 724 File mbox = new File(mboxFile); 726 mbox.delete(); 727 mbox = new File(mboxFile + WORKEXT); 729 if (!mbox.renameTo(new File(mboxFile))) 730 { 731 System.out.println("Failed to rename file!"); 732 } 733 734 Iterator mailList = mails.iterator(); 736 String key; 737 while (mailList.hasNext()) { 738 key = ((MailImpl)mailList.next()).getName(); 740 mList.remove(key); 741 } 742 743 744 } catch (FileNotFoundException e) { 745 getLogger().error("Unable to save(open) file (File not found) " + mboxFile, e); 746 } catch (IOException e) { 747 getLogger().error("Unable to write file (General I/O problem) " + mboxFile, e); 748 } 749 } 750 751 755 public void remove(String key) { 756 loadKeys(); 757 try { 758 lockMBox(); 759 } catch (Exception e) { 760 getLogger().error("Lock failed!",e); 761 return; } 763 ArrayList keys = new ArrayList(); 764 keys.add(key); 765 766 this.remove(keys); 767 unlockMBox(); 768 } 769 770 775 public boolean lock(String key) { 776 return false; 777 } 778 779 784 public boolean unlock(String key) { 785 return false; 786 } 787 788 789 public void compose(ComponentManager componentManager) throws ComponentException { 790 } 791 792 797 public void configure(Configuration conf) throws ConfigurationException { 798 String destination; 799 this.mList = null; 800 BUFFERING = conf.getAttributeAsBoolean("BUFFERING", true); 801 destination = conf.getAttribute("destinationURL"); 802 if (destination.charAt(destination.length() - 1) == '/') { 803 mboxFile = destination.substring("mbox://".length(), destination.lastIndexOf("/")); 805 } else { 806 mboxFile = destination.substring("mbox://".length()); 807 } 808 809 if (getLogger().isDebugEnabled()) { 810 getLogger().debug("MBoxMailRepository.destinationURL: " + destination); 811 } 812 813 String checkType = conf.getAttribute("type"); 814 if (!(checkType.equals("MAIL") || checkType.equals("SPOOL"))) { 815 String exceptionString = "Attempt to configure MboxMailRepository as " + checkType; 816 if (getLogger().isWarnEnabled()) { 817 getLogger().warn(exceptionString); 818 } 819 throw new ConfigurationException(exceptionString); 820 } 821 } 822 823 824 828 public void initialize() throws Exception { 829 } 830 831 832 public static void main(String [] args) { 833 MBoxMailRepository mbx = new MBoxMailRepository(); 835 mbx.mboxFile = "C:\\java\\test\\1998-05.txt"; 836 Iterator mList = mbx.list(); 837 while (mList.hasNext()) { 838 String key = (String ) mList.next(); 839 849 850 } 851 852 853 860 } 861 } 862 | Popular Tags |