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}