View Javadoc
1   package org.andromda.core.common;
2   
3   import java.io.File;
4   import java.io.FileWriter;
5   import java.io.PrintWriter;
6   import java.io.RandomAccessFile;
7   import java.text.SimpleDateFormat;
8   import java.util.Date;
9   import java.util.Random;
10  import org.apache.commons.lang.StringUtils;
11  import org.apache.commons.lang.exception.ExceptionUtils;
12  import org.apache.log4j.Logger;
13  
14  /**
15   * <p>
16   * ExceptionRecorder provides a function to record an exception to a file along with the trace data if active. </p>
17   *
18   * @author Martin West
19   * @author Bob Fields
20   */
21  public final class ExceptionRecorder
22  {
23      private static final Logger logger = Logger.getLogger(ExceptionRecorder.class);
24  
25      /**
26       * File header constant
27       */
28      static final String FILE_HEADER = "------- AndroMDA Exception Recording -------";
29  
30      /**
31       * Run line system constant
32       */
33      static final String RUN_SYSTEM = "Run System .....: ";
34  
35      /**
36       * Run line jdk constant
37       */
38      static final String RUN_JDK = "Run JDK ........: ";
39  
40      /**
41       * Information not available constant
42       */
43      static final String INFORMATION_UNAVAILABLE = " unavailable";
44  
45      /**
46       * The exceptions directory name:exceptions.
47       */
48      private static final String exceptionDirectoryName = ".";
49  
50      /**
51       * The exceptions directory, initialized to exceptions.
52       */
53      private static File exceptionDirectory = null;
54      private static final SimpleDateFormat cvDateFormat = new SimpleDateFormat("yyMMddHHmmss");
55      private static final Random random = new Random();
56  
57      /**
58       * The shared instance.
59       */
60      private static final ExceptionRecorder instance = new ExceptionRecorder();
61  
62      /**
63       * Private constructor, this class is not intended to be instantiated.
64       */
65      private ExceptionRecorder()
66      {
67          // Not intended to be instantiated
68      }
69  
70      /**
71       * Gets the shared instance of the ExceptionRecorder.
72       *
73       * @return the shared ExceptionRecorder instance.
74       */
75      public static ExceptionRecorder instance()
76      {
77          return instance;
78      }
79  
80      /**
81       * <p>
82       * Writes out the exception to a file along with trace data if active. The file name is of the form sYYMMDDHHMMSS
83       * (_nn).exc where YY..SS is the timestamp (_nn) is an ascending sequence number when multiple exceptions occur in
84       * the same second. Returns the filename of the generated exception report. </p>
85       *
86       * @param throwable to record.
87       * @return record("", throwable, "S")
88       */
89      public String record(Throwable throwable)
90      {
91          return record("", throwable, "S");
92      }
93  
94      /**
95       * <p>
96       * Writes out the exception to a file along with trace data if active. The file name is of the form sYYMMDDHHMMSS
97       * (_nn).exc where YY..SS is the timestamp (_nn) is an ascending sequence number when multiple exceptions occur in
98       * the same second. Returns the filename of the generated exception report. </p>
99       *
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 }