001/* 002 * $Source: /cvsroot2/open/projects/WebARTS/ca/bc/webarts/tools/EasyBak.java,v $ 003 * EasyBak.java - A simple Backup utility with remote archiving capabiity. 004 * 005 * $Revision: 563 $ 006 * $Date: 2012-11-03 19:28:37 -0700 (Sat, 03 Nov 2012) $ 007 * $Locker: $ 008 * 009 * 010 * Written by Tom Gutwin - WebARTS Design. 011 * Copyright (C) 2001 WebARTS Design, North Vancouver Canada 012 * http://www.webarts.bc.ca 013 * 014 * This program is free software; you can redistribute it and/or modify 015 * it under the terms of the GNU General Public License as published by 016 * the Free Software Foundation; either version 2 of the License, or 017 * (at your option) any later version. 018 * 019 * This program is distributed in the hope that it will be useful, 020 * but WITHOUT ANY WARRANTY; without even the implied warranty of 021 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 022 * GNU General Public License for more details. 023 * 024 * You should have received a copy of the GNU General Public License 025 * along with this program; if not, write to the Free Software 026 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 027 */ 028package ca.bc.webarts.tools; 029 030import ca.bc.webarts.widgets.Util; 031 032import cz.dhl.ftp.Ftp; 033import cz.dhl.ftp.FtpConnect; 034import cz.dhl.ftp.FtpFile; 035import cz.dhl.io.CoFile; 036import cz.dhl.io.CoLoad; 037import cz.dhl.io.LocalFile; 038 039import java.io.File; 040import java.io.FileOutputStream; 041import java.io.OutputStream; 042import java.io.IOException; 043//import java.io.SecurityException; 044import java.net.InetAddress; 045import java.net.UnknownHostException; 046import java.util.Iterator; 047import java.util.List; 048import java.util.Vector; 049 050import org.dom4j.Attribute; 051import org.dom4j.Document; 052import org.dom4j.DocumentException; 053import org.dom4j.Element; 054import org.dom4j.Node; 055import org.dom4j.io.SAXReader; 056import org.dom4j.util.XMLErrorHandler; 057 058import org.xml.sax.SAXException; 059 060 061/** 062 * A quick and dirty backup app that zips and sends the zips off to a spec'd 063 * archive location. <br> 064 * <br> 065 * All that is needed is a config file that specifies the files to zip, 066 * resultant zip filename and the archive location and type (ftp, local, none). 067 * <br> 068 * <br> 069 * The config file is in xml format (default filename is 'EasyBak.xml'. You can 070 * spec a different filename as the only param on the commandline<br> 071 * Example configfile:<br> 072 * <pre> 073 * <?xml 074 * version="1.0" 075 * encoding="UTF-8"?> 076 * <?xml-stylesheet 077 * href="http://warp2.webarts.bc.ca/xml/FancyXmlTreeViewer.xsl" 078 * type="text/xsl"?> 079 * <EasyBakPlan> 080 * <Archive 081 * filename="easyBak.zip" /> 082 * <ArchiveLocation 083 * type="ftp" 084 * user="yourFtpUsername" 085 * pass="yourpassword" 086 * server="10.0.0.250" 087 * path="/private/bak" /> 088 * <BackupFile 089 * filename="/usr/local/export/home/tgutwin/testdir" 090 * recurse="true" /> 091 * </EasyBakPlan> </pre> 092 * <br> 093 * <b>This application is released under a GNU license.</b><br> 094 * Author: <a href="mailto:tgutwin@webarts.bc.ca">Tom Gutwin P.Eng.</a><br> 095 * <a href="http://www.webarts.bc.ca">http://www.webarts.bc.ca</a> 096 * @author tgutwin 097 */ 098class EasyBak extends java.lang.Object implements ZipWatcher 099{ 100 /** 101 * Constant holding the users file seperator ("/" or "\"). 102 */ 103 public final static String SYSTEM_FILE_SEPERATOR = File.separator; 104 105 /** 106 * Constant holding the users line seperator. 107 */ 108 public final static String SYSTEM_LINE_SEPERATOR = 109 System.getProperty("line.separator"); 110 111 /** The default config filename. */ 112 public final static String CONFIG_FILENAME = 113 "." + SYSTEM_FILE_SEPERATOR + "EasyBak.xml"; 114 115 /** The XML Plan Document. */ 116 private Document zipPlanDoc_ = null; 117 118 /** Flags if validating docs when read in. 119 **/ 120 private boolean validatingXsd_ = false; 121 122 /** The archive servername(ip address), as read from the config file. */ 123 private String archiveServer_ = ""; 124 125 /** 126 * The archive type, as read from the config file. Must be one of 127 * 'ftp', 'local', or 'none'. 128 */ 129 private String archiveType_ = ""; 130 131 /** The archive ftp server user name, as read from the config file.*/ 132 private String archiveUser_ = ""; 133 134 /** The archive ftp server password, as read from the config file. */ 135 private String archivePass_ = ""; 136 137 /** 138 * The archive path for saving the archive to, as read from the config file. 139 * It is used for local or ftp archive types 140 */ 141 private String archivePath_ = ""; 142 143 /** The archive name to zip to, as read from the config file. */ 144 private String outFileName_ = ""; 145 146 /** The should we add the date to the archivename. */ 147 private String datedArchive_ = ""; 148 149 /** The should we add the currentworkstation name to the archivename. */ 150 private String labelArchive_ = ""; 151 152 /** The filenames to include in the archive as read from the config file. */ 153 private Vector inFilenames_ = new Vector(); 154 155 /** The filenames to EXclude in the archive as read from the config file. */ 156 private Vector excludes_ = new Vector(); 157 158 /** The log output name. */ 159 private static String logOutFileName_ = ""; 160 161 /** The MAX size of the archive zip files in Mb. */ 162 private long maxZipSize_ = 650L ; 163 164 /** 165 * Flag specifying if the zip will go directly to the final local archive 166 * location. It is used only if type is local. 167 **/ 168 boolean zipDirectToLocalArchive_ = true; 169 170 /** A thread mutex to signal that the zip file delivery is still inprocess. **/ 171 protected static boolean deliveryInProcess_ = false; 172 173 174 /** 175 * Archive Log file that lives with the final zip file. This ends up being 176 * a FileOutputStream 177 **/ 178 static OutputStream infoOutStream_ = null; 179 180 181 /** 182 * Constructor for the EasyBak object that uses the default config filename. 183 */ 184 public EasyBak() 185 { 186 this(CONFIG_FILENAME); 187 } 188 189 190 /** 191 * Constructor for the EasyBak object that uses the default config filename. 192 * 193 * @param a new filename to use as the config file. 194 */ 195 public EasyBak(String configName) 196 { 197 if (readZipConfig(configName) && 198 outFileName_ != null && 199 !outFileName_.equals("")) 200 { 201 logOutFileName_ = "."+SYSTEM_FILE_SEPERATOR+ 202 outFileName_.substring(0,outFileName_.length()-4)+"_log.txt"; 203 System.out.println("Log File:" + logOutFileName_); 204 try 205 { 206 infoOutStream_ = new FileOutputStream(logOutFileName_); 207 // check if we can get access to the delivery location 208 // BEFORE we spend the time to do the zip 209 if (checkDeliveryConnection()) 210 { 211 doBak(); 212 // before we leave we have to wait till all zips are delivered 213 waitForDeliveryToFinish(); 214 215 } 216 else 217 { 218 infoOutStream_.write(("ERROR EasyBak aborted.\n"+ 219 "Cannot validate the archive location.").getBytes()); 220 infoOutStream_.write( 221 ((String)"\nEasyBak Zipping Complete.").getBytes()); 222 infoOutStream_.close(); 223 } 224 } 225 catch (IOException ex) 226 { 227 System.out.println("Cannot init File:" + logOutFileName_ + 228 ". \nDetails :\n" + ex.getMessage()); 229 } 230 } 231 } 232 233 234 /** 235 * The main program for the EasyBak class. It creates an instance of itself 236 * and the instance does all the work. only one commandline param is accepted 237 * and is optional... a configfilename to override the default. 238 * 239 * @param args The command line arguments 240 */ 241 public static void main(String[] args) 242 { 243 EasyBak inst = null; 244 if (args.length == 0) 245 inst = new EasyBak(); 246 else if (args.length == 1) 247 inst = new EasyBak(args[0]); 248 else 249 System.out.println("Error: too many commandline args."); 250 } 251 252 253 /** 254 * Abstracts the actual zipping of the files. 255 **/ 256 private void doBak() 257 { 258 Zipper zipper = new Zipper(); 259 zipper.registerAsZipWatcher(this); 260 zipper.setQuiet_(true); 261 for (int i = 0; i < excludes_.size(); i++) 262 { 263 zipper.addToExcludes((String)excludes_.get(i)); 264 } 265 File[] files = new File[inFilenames_.size()]; 266 for (int i = 0; i < inFilenames_.size(); i++) 267 { 268 files[i] = new File((String) inFilenames_.get(i)); 269 } 270 271 if (outFileName_ != null && 272 !outFileName_.equals("")) 273 { 274 // first make sure logname is setup 275 if (logOutFileName_ == null || 276 (logOutFileName_ != null && logOutFileName_.equals("")) ) 277 { 278 logOutFileName_ = 279 outFileName_.substring(0,outFileName_.length()-4)+"_log.txt"; 280 } 281 282 zipper.setMaxZipFileSize_(maxZipSize_); 283 try 284 { 285 infoOutStream_ = new FileOutputStream(logOutFileName_); 286 //zipper.setInfoOutStream(infoOutStream_); 287 System.out.println("\nStarting to Zip files to "+outFileName_+"..."); 288 logInfo("\nStarting to Zip files to "+outFileName_+"..."); 289 zipper.zipFiles(files, new File(outFileName_)); 290 logInfo("\nEasyBak Zipping Complete."); 291 infoOutStream_.close(); 292 } 293 catch (IOException ex) 294 { 295 System.out.println("Cannot init File:" + logOutFileName_ + 296 ". \nDetails :\n" + ex.getMessage()); 297 } 298 } 299 } 300 301 302 /** 303 * Implementation of ZipWatcher. This method gets called when a zip has been 304 * completed and is ready for pickup and delivery ;) 305 * 306 * @param filename Description of the Parameter 307 * @param completed Description of the Parameter 308 */ 309 public void nextZipDone(final String filename, boolean completed) 310 { 311 System.out.println("Ready For Pickup:" + filename); 312 313 // Run in separate thread. 314 Thread ftpThread = 315 new Thread() 316 { 317 public void run() 318 { 319 deliverZipToArchiveLocation(filename); 320 } 321 }; 322 323 ftpThread.start(); 324 //watchFTPActive(ftpThread); 325 } 326 327 328 /** In a separate thread, watch the ftp thread and change the deliveryInProcess_ flag to false when done. **/ 329 private void watchFTPActive(final Thread watchThread) 330 { 331 long timeWatched = 0; 332 int sleepTime = 5000; 333 final long timeToTerminate = 1800000; // 30 minutes 334 335 // inline Thread to do the watching 336 Thread watchdogThread = new Thread (){ 337 long timeWatched = 0; 338 int sleepTime = 500; 339 340 public void run() 341 { 342 while (timeWatched < timeToTerminate && 343 watchThread.isAlive() && 344 !watchThread.isInterrupted() ) 345 { 346 timeWatched += sleepTime; 347 try 348 { 349 sleep(sleepTime); 350 } 351 catch (InterruptedException iex) 352 { 353 } 354 } 355 356 if (timeWatched >= timeToTerminate) 357 { 358 // this throws a security exception If the interrupt is not allowed 359 watchThread.interrupt(); 360 } 361 } 362 }; 363 364 deliveryInProcess_ = true; 365 watchdogThread.start(); 366 deliveryInProcess_ = false; 367 } 368 369 370 /** Blocks the current thread until the FTP Delivery is finished. 371 It watches for deliveryInProcess_ to be false. **/ 372 private void waitForDeliveryToFinish() 373 { 374 long timeWatched = 0; 375 int sleepTime = 5000; 376 377 try 378 { 379 while (deliveryInProcess_) 380 { 381 timeWatched += sleepTime; 382 Util.sleep(sleepTime); 383 } 384 } 385 catch (SecurityException sEx) 386 { 387 } 388 } 389 390 391 /** 392 * Checks that the specified archive location is present and accessible. 393 * 394 * @return a boolean if the connection is usable or not. 395 */ 396 private boolean checkDeliveryConnection() 397 { 398 boolean retVal = false; 399 String testPath = ""; 400 if (archiveType_.equals("ftp")) 401 { 402 logInfo("\nTesting FTP Archive Location."); 403 Ftp ftp = new Ftp(); 404 try 405 { 406 /* 407 * connect & login to host 408 */ 409 logInfo("\n Connecting..."); 410 ftp.connect(archiveServer_, Ftp.PORT); 411 ftp.login(archiveUser_, archivePass_); 412 if (ftp != null && ftp.isConnected()) 413 { 414 logInfo("\n Connected!"); 415 logInfo("\n Checking Final dir: "+ archivePath_); 416 retVal = ftp.cd(archivePath_); 417 logInfo("\n "+(retVal?"success":"faliled")); 418 ftp.disconnect(); 419 } 420 ftp=null; 421 } 422 catch (IOException e) 423 { 424 System.out.println(e); 425 } 426 } 427 else 428 { 429 logInfo("\nTesting Local Archive Location."); 430 // check for the archive path 431 if (archiveType_.equals("local")) 432 testPath = archivePath_; 433 else 434 testPath = System.getProperty("user.dir"); 435 436 if (testPath != null && !testPath.equals("")) 437 { 438 File testLocation = new File(testPath); 439 logInfo("\n"+ testPath); 440 if ( (testLocation != null) && 441 testLocation.exists() && 442 testLocation.isDirectory()) 443 { 444 // do a test write 445 try 446 { 447 logInfo("\nTest Write."+archivePath_+File.separator+"t27a98Y1.ffs"); 448 File testFile = new File(archivePath_+File.separator+ 449 "t27a98Y1.ffs"); 450 testFile.createNewFile(); 451 retVal = (testFile != null) && testFile.canWrite(); 452 logInfo("\n..."+(retVal?" suceeded":" failed.")); 453 if (retVal) testFile.delete(); 454 } 455 catch (IOException ex) 456 { 457 System.out.println("Cannot Test Log Location File:"+ex.getMessage()); 458 } 459 } 460 } 461 } 462 return retVal; 463 } 464 465 466 /** 467 * Sends a string message to the infoOutStream_. 468 * 469 * @param the message to send 470 */ 471 private void logInfo(String logEntry) 472 { 473 if (infoOutStream_ != null && logEntry!= null) 474 try 475 { 476 infoOutStream_.write(logEntry.getBytes()); 477 } 478 catch (IOException ex) 479 { 480 System.out.println("Cannot init File:" + logOutFileName_ + 481 ". \nDetails :\n" + ex.getMessage()); 482 } 483 } 484 485 486 /** 487 * Takes a completed zip file and sends/moves it to the specified archive 488 * location for this instance. 489 * 490 * @param filename Zip Filename to send 491 */ 492 private void deliverZipToArchiveLocation(String filename) 493 { 494 deliveryInProcess_ = true; 495 String shortname = filename.substring(filename.lastIndexOf(File.separator) + 1); 496 if (archiveType_.equals("ftp")) 497 { 498 Ftp ftp = new Ftp(); 499 try 500 { 501 /* 502 * connect & login to host 503 */ 504 ftp.connect(archiveServer_, Ftp.PORT); 505 ftp.login(archiveUser_, archivePass_); 506 507 String uploadPath = archivePath_ + "/" + shortname; 508 509 /* 510 * destination FtpFile remote file 511 */ 512 logInfo("\n"+shortname +" --> Preparing to deliver to " + archiveServer_ + uploadPath); 513 System.out.println(shortname + 514 " --> Preparing to deliver to " + archiveServer_ + uploadPath); 515 //CoFile to = new FtpFile(uploadPath, ftp); 516 CoFile to = new FtpFile(archivePath_, shortname, ftp); 517 518 /* 519 * source LocalFile 520 */ 521 logInfo("\n"+shortname +" --> Preparing to deliver from " + filename); 522 System.out.println(shortname + 523 " --> Preparing to deliver from " + filename); 524 CoFile from = new LocalFile(filename); 525 526 /* 527 * upload 528 */ 529 if (CoLoad.copy(to, from)) 530 { 531 logInfo("\n"+shortname +" --> Delivery Successful to " + archiveServer_ + uploadPath); 532 System.out.println(shortname + 533 " --> Delivery Successful to " + archiveServer_ + uploadPath); 534 // Now delete the local zip file 535 ((LocalFile) from).delete(); 536 } 537 538 /* 539 * upload LOGfile 540 */ 541 try 542 { 543 uploadPath = archivePath_ + "/" + logOutFileName_; 544 to = new FtpFile(uploadPath, ftp); 545 from = new LocalFile(logOutFileName_); 546 if (CoLoad.copy(to, from)) 547 { 548 logInfo("\n"+shortname +" --> Logfile Delivery Successful to " + archiveServer_ + uploadPath); 549 System.out.println(shortname + 550 " --> Logfile Delivery Successful to " + archiveServer_ + uploadPath); 551 ((LocalFile) from).delete(); 552 } 553 } 554 catch (Exception e) 555 { 556 // Any exception here should not halt the uploads 557 System.out.println(e); 558 e.printStackTrace(); 559 logInfo("\nERROR: "+e.getMessage()+"\n"+e.getStackTrace()); 560 } 561 562 ftp.disconnect(); 563 } 564 catch (IOException e) 565 { 566 System.out.println(e); 567 e.printStackTrace(); 568 logInfo("\nERROR: "+e.getMessage()+"\n"+e.getStackTrace()); 569 if (ftp!=null && ftp.isConnected()) 570 ftp.disconnect(); 571 } 572 } 573 else if (archiveType_.equals("local") && !zipDirectToLocalArchive_) 574 { 575 // move the zip file 576 if (Util.moveFile(filename, archivePath_)) 577 { 578 System.out.println(shortname + 579 " Delivery Successful to " + archivePath_); 580 } 581 else 582 { 583 System.out.println(shortname + 584 "\nERROR: Delivery of " + shortname + " to folder "+ 585 archivePath_+" failed.\n" + 586 "Please check permissions and settings."); 587 } 588 589 // move the logfile 590 if (Util.moveFile(logOutFileName_, archivePath_)) 591 { 592 System.out.println(logOutFileName_ + 593 " Delivery Successful to " + archivePath_); 594 } 595 else 596 { 597 System.out.println(logOutFileName_ + 598 "\nERROR: Delivery of " + logOutFileName_ + " to folder "+ 599 archivePath_+" failed.\n" + 600 "Please check permissions and settings."); 601 } 602 } 603 else if (archiveType_.equals("none")) 604 { 605 // do nothing 606 System.out.println(shortname + 607 " is now in current directory."); 608 } 609 deliveryInProcess_ = false; 610 } 611 612 613 /** 614 * Reads the DEFAULT config XML file and parses the required data. 615 * 616 * @return success or failure 617 */ 618 private boolean readZipConfig() 619 { 620 return readZipConfig(CONFIG_FILENAME); 621 } 622 623 624 /** 625 * Reads the passed in config XML file and parses the required data. 626 * 627 * @param configFilename Description of the Parameter 628 * @return success or failure 629 */ 630 private boolean readZipConfig(String configFilename) 631 { 632 boolean retVal = true; 633 634 // validate input params 635 if (configFilename == null || configFilename.equals("")) 636 { 637 configFilename = CONFIG_FILENAME; 638 } 639 640 zipPlanDoc_ = readWithSAX(configFilename); 641 642 if (zipPlanDoc_ != null) 643 { 644 // Get the Zipfile name 645 outFileName_ = zipPlanDoc_.selectSingleNode("//EasyBakPlan/Archive"). 646 valueOf("@filename"); 647 datedArchive_ = zipPlanDoc_.selectSingleNode("//EasyBakPlan/Archive"). 648 valueOf("@dated"); 649 labelArchive_ = zipPlanDoc_.selectSingleNode("//EasyBakPlan/Archive"). 650 valueOf("@serverLabel"); 651 652 String tmpStr = zipPlanDoc_.selectSingleNode("//EasyBakPlan/Archive"). 653 valueOf("@maxSize"); 654 if (tmpStr != null && !tmpStr.equals("")) 655 maxZipSize_ = Long.parseLong(tmpStr); 656 657 // get the files to zip 658 List list = zipPlanDoc_.selectNodes("//EasyBakPlan/BackupFile"); 659 for (Iterator iter = list.iterator(); iter.hasNext(); ) 660 { 661 Node BackupFileNode = (Node) iter.next(); 662 inFilenames_.add(BackupFileNode.valueOf("@filename")); 663 } 664 System.out.println("NumFiles =" + inFilenames_.size()); 665 666 // get the files to EXCLUDE 667 list = zipPlanDoc_.selectNodes("//EasyBakPlan/ExcludedFile"); 668 for (Iterator iter = list.iterator(); iter.hasNext(); ) 669 { 670 Node ExcludedFileNode = (Node) iter.next(); 671 excludes_.add(ExcludedFileNode.valueOf("@endsWith")); 672 } 673 System.out.println("NumFiles =" + inFilenames_.size()); 674 675 // get the archive type and location 676 archiveType_ = 677 zipPlanDoc_.selectSingleNode("//EasyBakPlan/ArchiveLocation"). 678 valueOf("@type"); 679 if (!(archiveType_.toLowerCase().equals("ftp") || 680 archiveType_.toLowerCase().equals("local") || 681 archiveType_.toLowerCase().equals("none"))) 682 { 683 retVal = false; 684 System.out.println("ERROR in EasyBak Config File: ArchiveLocation - " + 685 "type attribute MUST be one of 'ftp' or 'local' or 'none'."); 686 archiveType_ = "none"; 687 } 688 System.out.println("archiveType_ =" + archiveType_); 689 690 if (archiveType_.toLowerCase().equals("ftp")) 691 { 692 archiveServer_ = 693 zipPlanDoc_.selectSingleNode("//EasyBakPlan/ArchiveLocation"). 694 valueOf("@server"); 695 System.out.println("ArchiveServer =" + archiveServer_); 696 archiveUser_ = 697 zipPlanDoc_.selectSingleNode("//EasyBakPlan/ArchiveLocation"). 698 valueOf("@user"); 699 System.out.println("archiveUser_ =" + archiveUser_); 700 archivePass_ = 701 zipPlanDoc_.selectSingleNode("//EasyBakPlan/ArchiveLocation"). 702 valueOf("@pass"); 703 System.out.println("archivePass_ = ****"); 704 } 705 archivePath_ = 706 zipPlanDoc_.selectSingleNode("//EasyBakPlan/ArchiveLocation"). 707 valueOf("@path"); 708 System.out.println("archivePath_ =" + archivePath_); 709 710 // concatenate the FULL archive filename 711 if (labelArchive_ != null && !labelArchive_.equals("") && 712 labelArchive_.toLowerCase().equals("true")) 713 { 714 try 715 { 716 InetAddress localHostAddr = InetAddress.getLocalHost(); 717 if (localHostAddr != null) 718 outFileName_ += (outFileName_ != null && !outFileName_.equals("") ? 719 "-":"") + 720 localHostAddr.getHostName(); 721 } 722 catch (UnknownHostException ex) 723 { 724 System.out.println( 725 "Cannot obtain LocalHostname - NOT labelling archive file." ); 726 } 727 } 728 if (datedArchive_ != null && !datedArchive_.equals("") && 729 datedArchive_.toLowerCase().equals("true")) 730 outFileName_ += (outFileName_ != null && !outFileName_.equals("") ? 731 "-":"") + Util.createCurrentDateStamp(); 732 733 if (archiveType_.toLowerCase().equals("local")&&zipDirectToLocalArchive_) 734 { 735 outFileName_ = archivePath_ + File.separator + outFileName_; 736 } 737 738 if (!outFileName_.toLowerCase().endsWith(".zip")) 739 { 740 outFileName_+=".zip"; 741 } 742 System.out.println("Outname =" + outFileName_); 743 744 } 745 else 746 retVal = false; 747 748 return retVal; 749 } 750 751 752 /** 753 * Reads the input config file with a SAX processor using dom4j. 754 * 755 * @param filename The XML config file to read 756 * @return Description of the Return Value 757 */ 758 private Document readWithSAX(String filename) 759 { 760 Document retVal = null; 761 762 try 763 { 764 retVal = (new SAXReader(validatingXsd_)).read(filename); 765 } 766 catch (DocumentException docEx) 767 { 768 System.out.println("\nERROR: The XML Config file "+filename+ 769 " cannot be read or validated."); 770 System.out.println("Please check file: "+ filename); 771 } 772 catch (Exception ex) 773 { 774 System.out.println("Something crapped Out:\n" + ex.getMessage()); 775 } 776 777 return retVal; 778 } 779 780 781}