001package org.andromda.core.common; 002 003import java.io.File; 004import java.io.FileWriter; 005import java.io.PrintWriter; 006import java.io.RandomAccessFile; 007import java.text.SimpleDateFormat; 008import java.util.Date; 009import java.util.Random; 010import org.apache.commons.lang.StringUtils; 011import org.apache.commons.lang.exception.ExceptionUtils; 012import org.apache.log4j.Logger; 013 014/** 015 * <p> 016 * ExceptionRecorder provides a function to record an exception to a file along with the trace data if active. </p> 017 * 018 * @author Martin West 019 * @author Bob Fields 020 */ 021public final class ExceptionRecorder 022{ 023 private static final Logger logger = Logger.getLogger(ExceptionRecorder.class); 024 025 /** 026 * File header constant 027 */ 028 static final String FILE_HEADER = "------- AndroMDA Exception Recording -------"; 029 030 /** 031 * Run line system constant 032 */ 033 static final String RUN_SYSTEM = "Run System .....: "; 034 035 /** 036 * Run line jdk constant 037 */ 038 static final String RUN_JDK = "Run JDK ........: "; 039 040 /** 041 * Information not available constant 042 */ 043 static final String INFORMATION_UNAVAILABLE = " unavailable"; 044 045 /** 046 * The exceptions directory name:exceptions. 047 */ 048 private static final String exceptionDirectoryName = "."; 049 050 /** 051 * The exceptions directory, initialized to exceptions. 052 */ 053 private static File exceptionDirectory = null; 054 private static final SimpleDateFormat cvDateFormat = new SimpleDateFormat("yyMMddHHmmss"); 055 private static final Random random = new Random(); 056 057 /** 058 * The shared instance. 059 */ 060 private static final ExceptionRecorder instance = new ExceptionRecorder(); 061 062 /** 063 * Private constructor, this class is not intended to be instantiated. 064 */ 065 private ExceptionRecorder() 066 { 067 // Not intended to be instantiated 068 } 069 070 /** 071 * Gets the shared instance of the ExceptionRecorder. 072 * 073 * @return the shared ExceptionRecorder instance. 074 */ 075 public static ExceptionRecorder instance() 076 { 077 return instance; 078 } 079 080 /** 081 * <p> 082 * Writes out the exception to a file along with trace data if active. The file name is of the form sYYMMDDHHMMSS 083 * (_nn).exc where YY..SS is the timestamp (_nn) is an ascending sequence number when multiple exceptions occur in 084 * the same second. Returns the filename of the generated exception report. </p> 085 * 086 * @param throwable to record. 087 * @return record("", throwable, "S") 088 */ 089 public String record(Throwable throwable) 090 { 091 return record("", throwable, "S"); 092 } 093 094 /** 095 * <p> 096 * Writes out the exception to a file along with trace data if active. The file name is of the form sYYMMDDHHMMSS 097 * (_nn).exc where YY..SS is the timestamp (_nn) is an ascending sequence number when multiple exceptions occur in 098 * the same second. Returns the filename of the generated exception report. </p> 099 * 100 * @param errorMessage to log with the exception report. 101 * @param throwable to record. 102 * @return record(errorMessage, throwable, "S") 103 */ 104 public String record( 105 String errorMessage, 106 Throwable throwable) 107 { 108 return record(errorMessage, throwable, "S"); 109 } 110 111 /** 112 * The default prefix given, if prefix in {@link #record(String, Throwable, String) is null}. 113 */ 114 private static final String DEFAULT_PREFIX = "andromda"; 115 116 /** 117 * <p> 118 * Writes out the exception to a file along with trace data if active. The file name is of the form sYYMMDDHHMMSS 119 * (_nn).exc where YY..SS is the timestamp <_nn>is an ascending sequence number when multiple exceptions occur in 120 * the same second. </p> 121 * 122 * @param message diagnostic message 123 * @param throwable exception to record. 124 * @param prefix for the file name. 125 * @return result string 126 */ 127 public String record( 128 String message, 129 Throwable throwable, 130 String prefix) 131 { 132 String result = null; 133 if (StringUtils.isEmpty(prefix)) 134 { 135 prefix = DEFAULT_PREFIX; 136 } 137 try 138 { 139 final BuildInformation buildInformation = BuildInformation.instance(); 140 final String uniqueName = getUniqueName(prefix); 141 final File exceptionFile = new File(exceptionDirectory, uniqueName); 142 result = exceptionFile.getCanonicalPath(); 143 final PrintWriter writer = new PrintWriter(new FileWriter(exceptionFile)); 144 writer.println(FILE_HEADER); 145 writer.println("Version ........: " + buildInformation.getBuildVersion()); 146 writer.println("Error ..........: " + message); 147 writer.println("Build ..........: " + buildInformation.getBuildDate()); 148 writer.println("Build System ...: " + buildInformation.getBuildSystem()); 149 writer.println("Build JDK ......: " + buildInformation.getBuildJdk()); 150 writer.println("Build Builder ..: " + buildInformation.getBuildBuilder()); 151 152 // Place in try/catch in case system is protected. 153 try 154 { 155 writer.println(RUN_SYSTEM + System.getProperty("os.name") + System.getProperty("os.version")); 156 writer.println(RUN_JDK + System.getProperty("java.vm.vendor") + System.getProperty("java.vm.version")); 157 } 158 catch (Exception ex) 159 { 160 // ignore 161 writer.println(RUN_SYSTEM + INFORMATION_UNAVAILABLE); 162 writer.println(RUN_JDK + INFORMATION_UNAVAILABLE); 163 } 164 writer.println("Main Exception .: " + throwable.getMessage()); 165 Throwable cause = ExceptionUtils.getRootCause(throwable); 166 if (cause == null) 167 { 168 cause = throwable; 169 } 170 writer.println("Root Exception .: " + cause); 171 cause.printStackTrace(writer); 172 writer.close(); 173 AndroMDALogger.error("Exception recorded in --> '" + result + '\''); 174 } 175 catch (Throwable th) 176 { 177 final String errorMessage = "ExceptionRecorder.record error recording exception --> '" + throwable + '\''; 178 logger.error(errorMessage, th); 179 } // End catch 180 return result; 181 } // end of method record 182 183 /** 184 * The suffix to give the recorded exception files. 185 */ 186 private static final String SUFFIX = ".exc"; 187 188 /** 189 * Gets a unique file name. 190 * @param prefix 191 * @return uniqueName 192 * @concurrency guarded 193 */ 194 protected synchronized String getUniqueName(String prefix) 195 { 196 String uniqueName = prefix + cvDateFormat.format(new Date()) + SUFFIX; 197 int suffix = 0; 198 File exceptionFile = new File(exceptionDirectory, uniqueName); 199 while (exceptionFile.exists()) 200 { 201 uniqueName = prefix + cvDateFormat.format(new Date()) + '_' + suffix++ + SUFFIX; 202 exceptionFile = new File(exceptionDirectory, uniqueName); 203 204 // Give another user an opportunity to 205 // grab a file name. Use a random delay to 206 // introduce variability 207 try 208 { 209 Thread.sleep(Math.abs(random.nextInt() % 100)); 210 } 211 catch (InterruptedException e1) 212 { 213 // ignore 214 } 215 } // end while 216 217 // Grab the file name, there is a window when we 218 // are writing the file, that some one else in 219 // a different VM could get the same file name. 220 try 221 { 222 RandomAccessFile file; 223 file = new RandomAccessFile(exceptionFile, "rw"); 224 file.writeChar('t'); 225 file.close(); 226 } 227 catch (Exception ex) 228 { 229 // ignore 230 } 231 return uniqueName; 232 } // end method getUniqueName 233 234 static 235 { 236 /* initialize the exceptionDirectory */ 237 try 238 { 239 exceptionDirectory = new File(exceptionDirectoryName); 240 if (!exceptionDirectory.exists()) 241 { 242 exceptionDirectory.mkdir(); 243 } 244 } 245 catch (Throwable th) 246 { 247 // ignore 248 } 249 finally 250 { 251 if (exceptionDirectory == null) 252 { 253 exceptionDirectory = new File("."); 254 } 255 } 256 } 257 258 /** 259 * Returns the directory to which the exceptions are written. 260 * 261 * @return the exception directory as a java.io.File instance. 262 */ 263 public File getExceptionDirectory() 264 { 265 return exceptionDirectory; 266 } 267}