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 mailTo) throws 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 mailTo) throws 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 fileName) throws 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 }
|