View Javadoc

1   /*
2    *  XNap Commons
3    *
4    *  Copyright (C) 2005  Tammo van Lessen
5    *  Copyright (C) 2005  Steffen Pingel
6    *
7    *  This library is free software; you can redistribute it and/or
8    *  modify it under the terms of the GNU Lesser General Public
9    *  License as published by the Free Software Foundation; either
10   *  version 2.1 of the License, or (at your option) any later version.
11   *
12   *  This library is distributed in the hope that it will be useful,
13   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15   *  Lesser General Public License for more details.
16   *
17   *  You should have received a copy of the GNU Lesser General Public
18   *  License along with this library; if not, write to the Free Software
19   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20   */
21  package org.xnap.commons.util;
22  
23  import java.io.BufferedReader;
24  import java.io.ByteArrayOutputStream;
25  import java.io.File;
26  import java.io.FileInputStream;
27  import java.io.FileOutputStream;
28  import java.io.IOException;
29  import java.io.InputStreamReader;
30  import java.io.ObjectInputStream;
31  import java.io.ObjectOutputStream;
32  import java.io.PrintWriter;
33  import java.io.UnsupportedEncodingException;
34  import java.net.HttpURLConnection;
35  import java.net.URL;
36  import java.net.URLEncoder;
37  import java.security.MessageDigest;
38  import java.security.NoSuchAlgorithmException;
39  import java.util.HashSet;
40  import java.util.Locale;
41  import java.util.Properties;
42  import java.util.Vector;
43  import org.apache.commons.logging.Log;
44  import org.apache.commons.logging.LogFactory;
45  
46  /***
47   *
48   * @author Tammo van Lessen
49   * @author Steffen Pingel
50   */
51  public class UncaughtExceptionManager implements UncaughtExceptionListener {
52  
53  	public static String[] DEFAULT_MANGLE_PREFIXES = {
54  		"java.lang.IndexOutOfBoundsException",
55  		"java.lang.ArrayIndexOutOfBoundsException", 
56  		"java.lang.RuntimeException",
57  	};
58  	
59  	private static Log logger = LogFactory.getLog(UncaughtExceptionManager.class);	
60  	
61  	private static UncaughtExceptionListener defaultHandler = new NullExceptionHandler();
62  	
63  	private static String asHex(byte hash[]) {
64  		StringBuilder buf = new StringBuilder(hash.length * 2);
65  		int i;
66  
67  		for (i = 0; i < hash.length; i++) {
68  			if (((int) hash[i] & 0xff) < 0x10) 
69  				buf.append("0");
70  
71  			buf.append(Long.toString((int) hash[i] & 0xff, 16));
72  		}
73  
74  		return buf.toString();
75  	}
76  
77  	public static String removeExceptionDescription(String trace, String prefix) 
78  	{
79  		if (trace.startsWith(prefix + ": ")) {
80  			int i = trace.indexOf("\n");
81  			if (i != -1) {
82  				return prefix + trace.substring(i);
83  			}
84  		}
85  		return trace;
86  	}
87  	
88  	public static UncaughtExceptionListener getDefaultHandler() 
89  	{
90  		return defaultHandler;
91  	}
92  
93  	public static void setDefaultHandler(UncaughtExceptionListener handler) 
94  	{
95  		if (handler == null) {
96  			defaultHandler = new NullExceptionHandler();
97  		}
98  		else {
99  			defaultHandler = handler;
100 		}
101 	}
102 
103 	private HashSet<String> blacklist = new HashSet<String>(); 
104 	private File blacklistFile;
105 
106 	private Vector<UncaughtExceptionListener> listeners = new Vector<UncaughtExceptionListener>();
107 	private String[] manglePrefixes;
108 
109 	public UncaughtExceptionManager(File blacklistFile, String[] manglePrefixes) 
110 	{
111 		this.blacklistFile = blacklistFile;
112 		this.manglePrefixes = manglePrefixes;
113 		
114 		readBlackList();
115 		
116 		setDefaultHandler(this);
117 	}
118 
119 	public UncaughtExceptionManager(File blacklistFile)
120 	{
121 		this(blacklistFile, DEFAULT_MANGLE_PREFIXES);
122 	}
123 	
124 	public UncaughtExceptionManager()
125 	{
126 		this(null, DEFAULT_MANGLE_PREFIXES);
127 	}
128 
129 	public void addExceptionListener(UncaughtExceptionListener l) 
130 	{
131 		listeners.add(l);
132 	}
133 	
134 	public synchronized void addToBlacklist(Throwable e) 
135 	{
136 		blacklist.add(buildMD5Hash(e));
137 		writeBlacklist();
138 	}
139 
140 	private String buildMD5Hash(String s) 
141 	{
142 		MessageDigest md;
143 		try {
144 			md = MessageDigest.getInstance("MD5");
145 			return asHex(md.digest(s.getBytes()));		
146 		} catch (NoSuchAlgorithmException ex) {
147 			ex.printStackTrace(System.err);
148 		}
149 		return "";
150 	}
151 	 
152 	private String buildMD5Hash(Throwable e) 
153 	{
154 		return buildMD5Hash(toString(e));
155 	}
156 
157 	private String encode(Object o)
158 	{
159 		try {
160 			if (o != null) {
161 				return URLEncoder.encode(o.toString(), "UTF-8");
162 			}
163 		}
164 		catch (UnsupportedEncodingException e) { 
165 			// should never happen
166 			logger.error("UTF-8 encoding not supported", e); 
167 		}
168 		return "";
169 	}
170 
171 	private boolean isBlacklisted(Throwable e) 
172 	{
173 		return blacklist.contains(buildMD5Hash(e));	 
174 	}
175 	
176 	/***
177 	 * 
178 	 */
179 	@SuppressWarnings("unchecked")
180 	private void readBlackList() 
181 	{
182 		if (blacklistFile == null) {
183 			return;
184 		}
185 		if (!blacklistFile.exists()) {
186 			return;
187 		}
188 		
189 		FileInputStream in = null;
190 		try {
191 			in = new FileInputStream(blacklistFile);
192 			ObjectInputStream p = new ObjectInputStream(in);
193 			blacklist = (HashSet<String>)p.readObject();
194 		} 
195 		catch (Throwable e) {
196 			logger.debug("Could not read exception blacklist file: " + blacklistFile.getAbsolutePath(), e);
197 		}
198 		finally {
199 			try {
200 				if (in != null) {
201 					in.close();
202 				}
203 			} catch (IOException e) { /* ignore */ }
204 		}
205 	}
206 	
207 	public void removeExceptionListener(UncaughtExceptionListener l) 
208 	{
209 		listeners.remove(l);
210 	}
211 
212 	
213 	/***
214 	 * @param destination
215 	 * @param thread
216 	 * @param throwable
217 	 * @param version
218 	 * @param plugin
219 	 * @throws IOException
220 	 */
221 	public void sendProblemReport(URL destination, Thread thread, Throwable throwable,
222 								  String version, String plugin) 
223 		throws IOException 
224 	{
225 		Properties p = System.getProperties();
226 		StringBuffer report = new StringBuffer();
227 		report.append("version=" + encode(version));
228 		report.append("&plugin=" + encode(plugin));
229 		report.append("&locale=" + encode(Locale.getDefault()));
230 		report.append("&os_name=" + encode(p.get("os.name")));
231 		report.append("&os_version=" + encode(p.get("os.version")));
232 		report.append("&os_arch=" + encode(p.get("os.arch")));
233 		report.append("&java_vendor=" + encode(p.get("java.vendor")));
234 		report.append("&java_version=" + encode(p.get("java.version")));
235 		report.append("&stacktrace=" + encode(toString(throwable)));
236 		String hash = buildMD5Hash(report.toString());
237 		String problemHash = buildMD5Hash(throwable);
238 		report.append("&hash=" + encode(hash));
239 		report.append("&problem_hash=" + encode(problemHash));
240 
241 		HttpURLConnection conn = (HttpURLConnection)destination.openConnection();
242 		try {
243 			// FIX: somehow we should set the encoding to UTF-8
244 			conn.setDoOutput(true);
245 			conn.setDoInput(true);
246 			conn.setRequestMethod("POST");
247 			conn.setUseCaches(false);
248 			conn.setAllowUserInteraction(false);
249 	
250 			PrintWriter out = new PrintWriter(conn.getOutputStream());
251 			out.println(report);
252 			out.flush();
253 			out.close();
254 	
255 			BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
256 			try {
257 				String s;
258 				while ((s = in.readLine()) != null) {
259 					logger.debug("Received: " + s + "\n");
260 				}
261 			}
262 			finally {
263 				try {
264 					in.close();
265 				} 
266 				catch (IOException e) {}
267 			}
268 		}
269 		finally {
270 			conn.disconnect();
271 		}
272 		/*
273 		if (conn.getResponseCode() != HttpURLConnection.HTTP_OK)
274 			logger.error("Problem sending problem report");
275 		 	throw new IOException(conn.getResponseMessage());
276 		}
277 		*/
278 	}
279 	
280 	/***
281 	 * Returns the stacktrace of <code>e</code> as a String.
282 	 * 
283 	 * @param e the exception
284 	 */
285 	public String toString(Throwable e) 
286 	{
287 		ByteArrayOutputStream baos = new ByteArrayOutputStream();
288 		PrintWriter printWriter = new PrintWriter(baos);
289 		e.printStackTrace(printWriter);
290 		printWriter.close();
291 
292 		String trace = baos.toString(); 
293 		for (int i = 0; i < manglePrefixes.length; i++) {
294 			trace = removeExceptionDescription(trace, manglePrefixes[i]);
295 		}
296 		return trace;
297 	}
298 
299 	/***
300 	 * Handles e thrown by t. Notifies all listeners in case e is not
301 	 * blacklisted.
302 	 */
303 	public synchronized void uncaughtException(Thread t, Throwable e) 
304 	{
305 		// if exception is on blacklist do nothing!
306 		if (isBlacklisted(e)) {
307 			logger.debug("Blacklisted uncaught exception occured!", e);
308 			return;
309 		} 
310 		
311 		UncaughtExceptionListener[] l = listeners.toArray(new UncaughtExceptionListener[0]);
312 		if (l != null && l.length > 0) {
313 			for (int i = l.length - 1; i >= 0; i--) {
314 				l[i].uncaughtException(t, e);
315 			}
316 		} 
317 		else {
318 			e.printStackTrace(System.err);
319 		}
320 	}
321 
322 	/***
323 	 * 
324 	 */
325 	private void writeBlacklist() 
326 	{
327 		if (blacklistFile == null) {
328 			return;
329 		}
330 
331 		FileOutputStream out = null;
332 		try {
333 			out = new FileOutputStream(blacklistFile);
334 			ObjectOutputStream p = new ObjectOutputStream(out);
335 			p.writeObject(blacklist);
336 		} 
337 		catch (IOException e) {
338 			logger.debug("Could not write exception blacklist file: " + blacklistFile.getAbsolutePath(), e);
339 		}
340 		finally {
341 			try {
342 				if (out != null) {
343 					out.close();
344 				}
345 			} catch (IOException e) { /* ignore */ }
346 		}
347 	}
348 
349 	private static class NullExceptionHandler implements UncaughtExceptionListener {
350 
351 		public void uncaughtException(Thread t, Throwable e)
352 		{
353 		}
354 		
355 	}
356 	
357 }