BugProfile.java
001 /*
002  * This class is part of DocWhatsUp, a bug profiling and -reporting lib.
003  
004  * Copyright (C)
005  
006  * This library is free software; you can redistribute it and/or modify it
007  * under the terms of the GNU General Public License as published by the
008  * Free Software Foundation; either version 2 of the License, or (at your
009  * option) any later version.
010  
011  * This program is distributed in the hope that it will be useful, but
012  * WITHOUT ANY WARRANTY; without even the implied warranty of
013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
014  * Public License for more details.
015  *  
016  * You should have received a copy of the GNU General Public License along
017  * with this program; if not, write to the Free Software Foundation, Inc.,
018  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019  
020  * EXTENSION:
021  * Linking DocWhatsUp statically or dynamically with other modules is making a
022  * combined work based on DocWhatsUp.  Thus, the terms and conditions of the 
023  * GNU General Public License cover the whole combination.
024  *
025  * As a special exception, the copyright holder of DocWhatsUp give you
026  * permission to link DocWhatsUp with independent modules that communicate with
027  * DocWhatsUp solely through the DocWhatsUp.java interface, regardless of the 
028  * license terms of these independent modules, and to copy and distribute the
029  * resulting combined work under terms of your choice, provided that
030  * every copy of the combined work is accompanied by a complete copy of
031  * the source code of DocWhatsUp (the version of DocWhatsUp used to produce the
032  * combined work), being distributed under the terms of the GNU General
033  * Public License plus this exception.  An independent module is a module
034  * which is not derived from or based on DocWhatsUp.
035 
036  * Note that people who make modified versions of DocWhatsUp are not obligated
037  * to grant this special exception for their modified versions; it is
038  * their choice whether to do so.  The GNU General Public License gives
039  * permission to release a modified version without this exception; this
040  * exception also makes it possible to release a modified version which
041  * carries forward this exception.
042  
043  * Author: Philipp Bartsch; codeshaker@gmx.net
044  */
045 
046 package org.shaker.dwu;
047 
048 import java.io.BufferedReader;
049 import java.io.File;
050 import java.io.FileInputStream;
051 import java.io.FileOutputStream;
052 import java.io.IOException;
053 import java.io.InputStream;
054 import java.io.InputStreamReader;
055 import java.util.ArrayList;
056 import java.util.Date;
057 import java.util.Properties;
058 
059 import javax.mail.internet.AddressException;
060 import javax.mail.internet.InternetAddress;
061 
062 
063 /**
064  * Objects of this class describe occured Bugs by collecting specific informations like the
065  * machine/program state, the exception stuff and further more. The profile can be extended with 
066  * formerly collected infos and gets finally exported into a mail. Supported formats:
067  <ul>
068  *  <li>Text (the default if not/wrong specified)</li>
069  *  <li>HTML</li>
070  *  <li>Bugzilla mail submission compatible format</li>
071  </ul>
072  
073  @author <A HREF="mailto:codeshaker@gmx.net">
074  *            Philipp Bartsch (codeshaker@gmx.net)</A>
075  *            <A HREF="../../../../gpl.txt">GPL License</A>
076  */
077 public final class   BugProfile 
078                 extends Properties {
079     
080     /** Stacktrace key*/
081     protected static  final    String    STACK_TRACE     = "Stacktrace";
082     /** Error class key*/
083     public    static  final    String    ERROR_CLASS     = "Error Class";
084     /** User comment key*/
085     public    static  final    String    USER_COMMENT    = "Comments";
086     /** Recipient key*/
087     public    static  final    String    MAIL_TO           = "Recipient";
088     /** Message key*/
089     public    static  final    String    MESSAGE          = "Message";
090     /** Date key*/
091     protected static  final    String    DATE              = "Date";
092     /** (Product) Version key*/
093     public    static  final    String    PRODUCT_VERSION = "Version";
094     /** Product name key*/
095     public    static  final    String    PRODUCT_NAME      = "Product";   
096     /** The mail format*/ 
097     public    static  final    String    SEND_FORMAT       = "format";
098     /** Positive property filter list*/
099     protected static  final    ArrayList propertyFilter     = getPropertyFilter();
100     /**A reference to the settings*/
101     private   static  final    Settings  SETTINGS         = DocWhatsUp.SETTINGS;
102     
103     //
104     /** The physical file representation of this BugProfile*/ 
105     private            final    File            profileFile;
106     /** String representation of the hashvalue*/ 
107     private            final    String          stackHash;      
108     /** */
109     private                     BugMail         bugMail;
110         
111     /**
112      * Creates a BugProfile that is based on a thrown exception.
113      
114      @param  message     the exception message (!null)
115      @param  stackTrace  the StackTrace (!null and not empty)
116      @param  errorclass  the error class identifier
117      @param  mailTo      the recipient
118      @throws Exception   an exception is thrown, if this profile (and its sig
119      *                     is known as already submitted. This prevents further
120      *                     submissions.
121      */
122     BugProfile (final Object message,
123                 final String stackTrace,
124                 final String errorclass,
125                 final String mailTothrows Exception {
126                     
127         profileFile = new File (ToolBox.DWU_PATH
128                                 + stackTrace.hashCode() 
129                                 ".bug");
130         //
131         stackHash = stackTrace.hashCode() "";
132         // perform selfcheck:
133         // -already submitted? 
134         // -already occured, but never sent? 
135         // -unknown, so far?
136         
137         if (validateState()) {                            
138             if (mailTo != null)
139                 super.setProperty(MAIL_TO,
140                                   mailTo);              
141             super.setProperty(DATE,
142                               new Date(System.currentTimeMillis()).toString());                                                             
143             if (stackTrace.length() 0)
144                 super.setProperty(STACK_TRACE,
145                                   stackTrace);
146             super.setProperty(ERROR_CLASS,
147                               errorclass);
148         else 
149             throw new Exception();    
150         // optional properties
151         if (System.getProperty("product.version"!= null)
152             super.setProperty(PRODUCT_VERSION,
153                               System.getProperty("product.version"));
154         if (System.getProperty("product.name"!= null)
155             super.setProperty(PRODUCT_NAME,
156                               System.getProperty("product.name"));
157         if (message.toString().length() 0)    
158             super.setProperty(MESSAGE,
159                               message.toString());           
160         bugMail = getBugMail(DocWhatsUp.SETTINGS.getDefaultSendFormat());                          
161     }
162        
163     /**
164      * Creates a BugProfile based on a error string, using a manually created
165      * hashcode signature.
166      
167      @param  message     the exception message (!null)
168      @param  hashSig     the hashcode signature
169      @param  errorclass  the error class identifier
170      @param  mailTo      the recipient
171      @throws Exception   an exception is thrown, if this profile (and its sig
172      *                     is known as already submitted. This prevents further
173      *                     submissions.
174      */
175     BugProfile (final Object message,
176                 final int    hashSig,
177                 final String errorclass,
178                 final String mailTothrows Exception {
179                 
180         profileFile = new File (ToolBox.DWU_PATH + hashSig + ".bug");
181         //
182         stackHash = hashSig + "";
183         // perform selfcheck:
184         // -already submitted? 
185         // -already occured, but never sent? 
186         // -unknown, so far?
187     
188         if (validateState()) {                            
189             if (mailTo != null)
190                 super.setProperty(MAIL_TO,
191                                   mailTo);              
192             super.setProperty(DATE,
193                               new Date(System.currentTimeMillis()).toString());                                                             
194             super.setProperty(ERROR_CLASS,
195                               errorclass);
196         else 
197             throw new Exception();    
198         // optional properties
199         if (System.getProperty("product.version"!= null)
200             super.setProperty(PRODUCT_VERSION,
201                               System.getProperty("product.version"));
202         if (System.getProperty("product.name"!= null)
203             super.setProperty(PRODUCT_NAME,
204                               System.getProperty("product.name"));
205         if (message.toString().length() 0)    
206             super.setProperty(MESSAGE,
207                               message.toString());           
208         bugMail = getBugMail(DocWhatsUp.SETTINGS.getDefaultSendFormat());                          
209     }
210         
211     /**
212      * Creates a BugProfile by passing a BugProfile file of the queue.
213      
214      @param  fileName     the filename
215      @throws IOException  in case of unexpected filecontent
216      */
217     BugProfile (final String fileNamethrows IOException {             
218         stackHash = fileName.substring(0,
219                                        fileName.length()-4);
220         profileFile  = new File (ToolBox.DWU_PATH + fileName);
221         final FileInputStream instream = new FileInputStream(profileFile);
222         try {                                                                                   
223             load(instream);                                         
224         catch (IOException ioe) {
225             // the provided file seemed to be a valid BugProfile file,
226             // but something is wrong, because it couldn`t be loaded.
227             // deleting file and passing the IOException.
228             instream.close();
229             profileFile.delete();
230             throw ioe;                                                                                      
231         
232         if (containsKey(SEND_FORMAT))
233             bugMail = getBugMail(getProperty(SEND_FORMAT));
234         else
235             bugMail = new PlainTextMail(this);
236         
237     }
238         
239     /**
240      * Validates the worth of this Profile.
241      
242      * This function creates a hash of the current stacktrace and looks into the
243      * dwu-propertyfile (property "submitted") wheter this hash is known or not
244      * (which indicates that this BugProfile has already been submitted). If the
245      * hash is new and unknown, it searches for a file named <i>hashvalue.
246      * bug</i> in the report-queue directory (currently the standard dwu-output
247      * dir). If this file exists, the current Bug already appeared (n times),
248      * but it has never been submitted (thatīs why it has been saved to disk).
249      * Afterwards, the older (proposed to be useful) usercomments get imported.
250      
251      @return BugProfile returns itself if it has never been submitted or null
252      */             
253     private boolean validateState () {
254             
255         // a false return indicates redundancy of this profile...
256         if (DocWhatsUp.SETTINGS.alreadySubmitted(stackHash))
257             return false;                   
258                 
259         // this profile has not been submitted yet: 
260         // search for a queued profile
261         if (profileFile.exists()) {
262             try {   
263                 // queued profile for equal bug found!                          
264                 importOldProfile();
265                 saveProfileToDisk();
266                 return true;
267             catch (IOException ioe) {
268                 // could not import old profile, so forget it and carry on!                             
269                 return true;
270             }
271         else {
272             // this profile/bug is completely new
273             // save it to disk
274             try {
275                 profileFile.createNewFile();
276                 saveProfileToDisk();
277                 return true;
278             catch (IOException ioe) {
279                 // could not save profile                               
280                 return true;
281             }       
282         }               
283     }
284                 
285     /**
286      * This Method imports old BugProfiles and keeps user`s comments in mind.
287      *
288      @throws IOException when the import can`t be performed
289      */
290     private void importOldProfile () throws IOException {
291         final Properties      oldProfile = new Properties();
292         final FileInputStream inStream   = new FileInputStream(profileFile);
293         oldProfile.load(inStream);              
294         if (oldProfile.containsKey(USER_COMMENT))
295             super.setProperty(USER_COMMENT,
296                               oldProfile.getProperty(USER_COMMENT));
297         inStream.close();                       
298     }
299     
300     /**
301      * Saves the profile to disk. The profile contains optional older comments. 
302      
303      @throws IOException  thrown in case of error
304      */
305     private void saveProfileToDisk () throws IOException {
306         final FileOutputStream out = new FileOutputStream(profileFile);         
307         store(out,
308               "Donīt edit ;)");             
309         out.close();
310     }        
311 
312     /**
313      * Deletes the locally stored BugProfile file. When this method is called,
314      * it is already obsolete, because the profile has been reported!
315      */
316     void killPhysically () {
317         profileFile.delete();
318     }
319         
320     /**
321      * This Method returns its unique StackTrace HashCode 
322      
323      @return String hashCode of this StackTrace
324      */
325     String getHashSig () {
326         return stackHash;
327     }
328         
329     /**
330      * Sets a property (each piece of information regarding the error is a
331      * property). Standard properties are:
332      <ul>
333      <li>MAIL_TO      - recipient of the bugreport</li>
334      <li>MESSAGE      - the message of the stacktrace</li>
335      <li>STACK_TRACE  - the stacktrace</li>
336      <li>USER_COMMENT - a usercomment</li>
337      <li>SEND_FORMAT  - the send format (text(default), html or bugzilla)</li>
338      </ul>
339      * If you define own properties: keep in mind that the key string is used as
340      * the value label in the submitted mail. So choose "speaking" keynames!
341      
342      @param key   an int key representation of the current value
343      @param value a string representation of the value
344      @return      the previous value or null
345      */     
346     public Object setProperty (final String key, 
347                                final String value) {
348         if (key.equals(USER_COMMENT)) // keeps the old comment by appending
349             super.setProperty(key, 
350                               "(" new Date(System.currentTimeMillis()) ")"
351                               + BugMail.NEW_LINE
352                               + BugMail.NEW_LINE
353                               + value
354                               + BugMail.NEW_LINE
355                               + BugMail.NEW_LINE
356                               (containsKey(USER_COMMENT)
357                                  ? getProperty(USER_COMMENT)
358                                  ""));
359         else if       (key.equals(SEND_FORMAT)) {             
360             bugMail = getBugMail(value);
361             super.setProperty(key,
362                               value);                       
363         else 
364             super.setProperty(key,
365                               value);        
366         try {        
367             saveProfileToDisk();
368             return null;
369         catch (IOException ioe) {
370             // failed to save
371             return null;
372         }
373     }
374         
375     /**
376      * Returns a InternetAddress object of the recipient
377      
378      @return InternetAddress the address
379      */
380     InternetAddress getRecipient () {
381         try {
382             return new InternetAddress(getProperty(BugProfile.MAIL_TO));
383         catch (AddressException ae) {
384             return null;
385         }
386     }
387         
388     /**
389      * Creates a unique subjectline for mailreports
390      
391      @return String the subject (DWU_stackmessage_date)
392      */     
393     String getSubjectLine () {
394         return bugMail.getSubjectLine();
395     }
396         
397     /**
398      * Returns a mail body formatted on the given custom or default format. If 
399      * the format is not satisfied, this method switches to the default or text 
400      * format and returns its body.
401      
402      @see org.shaker.dwu.BugMail#isSatisfied() what format satisfaction means
403      @return the BugMail
404      */     
405     final String getMailBody () {
406         // read: (not satisfied) && (uses default format 
407         //                             || (custom format == default format))
408         if (!bugMail.isSatisfied()) {    
409             
410             if ((!containsKey(SEND_FORMAT||
411                 (containsKey(SEND_FORMAT)    
412                  && getProperty(SEND_FORMAT)
413                      .equals(SETTINGS.getDefaultSendFormat())))) 
414                 // no way out, set to text format
415                 setProperty(SEND_FORMAT,
416                             "text");   
417             else
418                 setProperty(SEND_FORMAT,
419                             SETTINGS.getDefaultSendFormat());    
420         }
421         return bugMail.getMailBody();                
422     }    
423     
424     /**
425      * Returns the MIME type of the BugMail. This type is requested by the 
426      * internal mail preview pane of DocWhatsUp and the (Mail-)Message object.
427      
428      @return the MIME type of the BugMail object (something like text/plain, 
429      *            text/html ...)
430      */
431     final String getMimeType () {
432         // read: (not satisfied) && (uses default format 
433         //                             || (custom format == default format))
434         if (!bugMail.isSatisfied()) {       
435             if ((!containsKey(SEND_FORMAT||
436                 (containsKey(SEND_FORMAT)    
437                  && getProperty(SEND_FORMAT)
438                     .equals(SETTINGS.getDefaultSendFormat())))) 
439                 // no way out, set to text format
440                 setProperty(SEND_FORMAT,
441                             "text");   
442             else
443                 setProperty(SEND_FORMAT,
444                             SETTINGS.getDefaultSendFormat());    
445         }
446         return bugMail.getMimeType();
447     }
448     
449     /**
450      * Sets the BugMail based on the format argument.
451      
452      @param format the format (html, bugzilla or text expected); text used 
453      *                  on fault
454      @return       the BugMail object
455      */
456     private final BugMail getBugMail (final String format) {
457         if (format.equalsIgnoreCase("html")) 
458             return new HTMLMail(this);
459         else if (format.equalsIgnoreCase("bugzilla"))
460             return new BugzillaMail(this);
461         else
462             return new PlainTextMail(this);
463     }
464         
465     /**
466      * Returns the default system properties filter. It is extracted from
467      * the shipped jar file or created locally in case of an error.
468      
469      @return  the system properties whitelist
470      */
471     private static final ArrayList getPropertyFilter () {              
472         String line;
473         final ArrayList propFilter = new ArrayList ();
474         try {                   
475             InputStream inStream = ClassLoader
476                                    .getSystemClassLoader()
477                                    .getResourceAsStream("filter.properties");                        
478             InputStreamReader stReader = new InputStreamReader(inStream);
479             BufferedReader bufReader = new BufferedReader(stReader);
480             while ((line = bufReader.readLine()) != null) {
481                 propFilter.add(line);
482             }
483             inStream.close();
484             stReader.close();
485             bufReader.close();
486             return propFilter;                      
487         catch (Exception e) {
488             // could not import filter, use default  
489             propFilter.clear();
490             propFilter.add("java.runtime.version");
491             propFilter.add("os.name");            
492             
493             return propFilter;                      
494         }                         
495     }
496 }