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