View Javadoc

1   /*
2    *  XNap - A P2P framework and client.
3    *
4    *  See the file AUTHORS for copyright information.
5    *
6    *  This program is free software; you can redistribute it and/or modify
7    *  it under the terms of the GNU General Public License as published by
8    *  the Free Software Foundation.
9    *
10   *  This program is distributed in the hope that it will be useful,
11   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   *  GNU General Public License for more details.
14   *
15   *  You should have received a copy of the GNU General Public License
16   *  along with this program; if not, write to the Free Software
17   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18   */
19  
20  package org.xnap.util;
21  
22  import java.io.BufferedReader;
23  import java.io.ByteArrayOutputStream;
24  import java.io.FileInputStream;
25  import java.io.FileOutputStream;
26  import java.io.IOException;
27  import java.io.InputStreamReader;
28  import java.io.ObjectInputStream;
29  import java.io.ObjectOutputStream;
30  import java.io.PrintWriter;
31  import java.net.HttpURLConnection;
32  import java.net.URL;
33  import java.net.URLEncoder;
34  import java.security.MessageDigest;
35  import java.security.NoSuchAlgorithmException;
36  import java.util.HashSet;
37  import java.util.Iterator;
38  import java.util.Locale;
39  import java.util.Properties;
40  import java.util.StringTokenizer;
41  import java.util.Vector;
42  
43  import org.apache.log4j.Logger;
44  import org.xnap.plugin.PluginInfo;
45  import org.xnap.plugin.PluginManager;
46  
47  public class UncaughtExceptionManager {
48  
49  	private static final String BLACKLIST_FILENAME 
50  		= FileHelper.getHomeDir() + "problemreport";
51  	
52  	protected static Logger logger = Logger.getLogger(UncaughtExceptionManager.class);	
53  	private static Preferences prefs = Preferences.getInstance();
54  	private static UncaughtExceptionManager singleton = new UncaughtExceptionManager();
55  	
56  	private Vector listeners = new Vector();
57  	private HashSet blacklist = new HashSet(); 
58  	
59  	private UncaughtExceptionManager() 
60  	{
61  		readBlackList();
62  	}
63  
64  
65  	public static UncaughtExceptionManager getInstance() {
66  		return singleton;
67  	}
68  	
69  	public void addExceptionListener(UncaughtExceptionListener l) {
70  		listeners.add(l);
71  	}
72  	
73  	public void removeExceptionListener(UncaughtExceptionListener l) {
74  		listeners.remove(l);
75  	}
76  
77  	/***
78  	 * Handles e thrown by t. Notifies all listeners in case e is not
79  	 * blacklisted.
80  	 */
81  	public void notify(Thread t, Throwable e) 
82  	{
83  		// if exception on blacklist do nothing!
84  		if (!notifyAgain(e)) {
85  			logger.warn("Blacklisted error occured!");
86  			e.printStackTrace(System.err);
87  			return;
88  		} 
89  		
90  		Object[] l = listeners.toArray();
91  		if (l != null && l.length > 0) {
92  			for (int i = l.length - 1; i >= 0; i--) {
93  				((UncaughtExceptionListener)l[i]).uncaughtException(t, e);
94  			}
95  		} 
96  		else {
97  			e.printStackTrace(System.err);
98  		}
99  	}
100 	
101 	public void notify(Throwable e)
102 	{
103 		notify(Thread.currentThread(), e);
104 	}
105 	
106 	public void dontNotifyAgain(Throwable e) {
107 		blacklist.add(buildMD5Hash(e));
108 		writeBlacklist();
109 	}
110 	
111 
112 	/***
113 	 * Returns stacktrace as String
114 	 * 
115 	 * @param e
116 	 */
117 	public static String getStackTrace(Throwable e) 
118 	{
119 		if (e == null) {
120 			return "";
121 		}
122 			
123 		ByteArrayOutputStream baos = new ByteArrayOutputStream();
124 		PrintWriter printWriter = new PrintWriter(baos);
125 		e.printStackTrace(printWriter);
126 		printWriter.close();
127 
128 		return removeExceptionDescription
129 			(removeExceptionDescription
130 			 (removeExceptionDescription
131 			  (baos.toString(), 
132 			   "java.lang.IndexOutOfBoundsException"),
133 			  "java.lang.ArrayIndexOutOfBoundsException"), 
134 			 "java.lang.RuntimeException");
135 	}
136 
137 	public static String removeExceptionDescription(String trace,
138 													String prefix) 
139 	{
140 		if (trace.startsWith(prefix + ": ")) {
141 			int i = trace.indexOf("\n");
142 			if (i != -1) {
143 				return prefix + trace.substring(i);
144 			}
145 		}
146 		return trace;
147 	}
148 	
149 // 	public static String getLoadedJars() {
150 // 		StringBuffer s = new StringBuffer(); 
151 // 		s.append("\nLoaded jars:\n");
152 // 		URL[] urls = XNapClassLoader.getInstance().getURLs(); 
153 // 		for (int i=0; i < urls.length; i++) {
154 // 			s.append(urls[i].toString()+"\n"); 
155 // 		}
156 // 		return s.toString();
157 // 	}
158 
159 	public static String getPlugin(Throwable e)
160 	{
161 		return getPlugin(getStackTrace(e));
162 	}
163 	
164 	/***
165 	 * Scans the stack trace of e for an plugin packages and returns the plugin 
166 	 * name and version in case of success.
167 	 *   
168 	 * @return null, if no plugin matched; the plugin name and version separated by a space, otherwise
169 	 */
170 	static String getPlugin(String s) 
171 	{
172 		// in case we find no enabled plugin we might return a disabled plugin
173 		// as some plugins could still have "zombie" threads arounds after 
174 		// they were disabled
175 		String fallback = null;
176 		
177 		StringTokenizer t = new StringTokenizer(s, "\n");
178 		while (t.hasMoreTokens()) {
179 			String line = t.nextToken();
180 			if (line.indexOf("org.xnap.plugin") != -1) {
181 				for (Iterator it = PluginManager.getInstance().infos(); it.hasNext();) {
182 					PluginInfo info = (PluginInfo)it.next();
183 					if (info.getClassName() != null && !info.isBase()) {
184 						String packageName = StringHelper.lastPrefix(info.getClassName(), ".");
185 						iftrong> (line.indexOf(packageName) != -1) {
186 							if (info.isEnabled()) {
187 								return info.getName() + " " + info.getVersion();
188 							}
189 							else if (fallback == null) {
190 								fallback = info.getName() + " " + info.getVersion();
191 							}
192 						}
193 					}
194 				}
195 			}
196 		}
197 		return fallback;
198 	}
199 
200 	private static String asHex (byte hash[]) {
201 		StringBuffer buf = new StringBuffer(hash.length * 2);
202 		int i;
203 
204 		for (i = 0; i < hash.length; i++) {
205 			if (((int) hash[i] & 0xff) < 0x10) 
206 				buf.append("0");
207 
208 			buf.append(Long.toString((int) hash[i] & 0xff, 16));
209 		}
210 
211 		return buf.toString();
212 	}
213 	 
214 	private static String buildMD5Hash(Throwable e) 
215 	{
216 		return buildMD5Hash(getStackTrace(e));
217 	}
218 
219 	private static String buildMD5Hash(String s) 
220 	{
221 		MessageDigest md;
222 		try {
223 			md = MessageDigest.getInstance("MD5");
224 			return asHex(md.digest(s.getBytes()));		
225 		} catch (NoSuchAlgorithmException ex) {
226 			ex.printStackTrace(System.err);
227 		}
228 		return "";
229 	}
230 
231 	private boolean notifyAgain(Throwable e) {
232 		return !(blacklist.contains(buildMD5Hash(e)));	 
233 	}
234 
235 	private String encode(Object o)
236 	{
237 		return (o != null) ? URLEncoder.encode(o.toString()) : "";
238 	}
239 
240 	/***
241 	 * @param thread
242 	 * @param throwable
243 	 */
244 	public void sendProblemReport(Thread thread, Throwable throwable) 
245 		throws IOException 
246 	{
247 		Properties p = System.getProperties();
248 		StringBuffer report = new StringBuffer();
249 		report.append("version=" + encode(PluginManager.getCoreVersion()));
250 		report.append("&plugin=" + encode(getPlugin(throwable)));
251 		report.append("&locale=" + encode(Locale.getDefault()));
252 		report.append("&os_name=" + encode(p.get("os.name")));
253 		report.append("&os_version=" + encode(p.get("os.version")));
254 		report.append("&os_arch=" + encode(p.get("os.arch")));
255 		report.append("&java_vendor=" + encode(p.get("java.vendor")));
256 		report.append("&java_version=" + encode(p.get("java.version")));
257 		report.append("&stacktrace=" + encode(getStackTrace(throwable)));
258 		String hash = buildMD5Hash(report.toString());
259 		String problemHash = buildMD5Hash(throwable);
260 		report.append("&hash=" + encode(hash));
261 		report.append("&problem_hash=" + encode(problemHash));
262 
263 		URL url=new URL(prefs.get("problemReportURL"));
264 		HttpURLConnection conn = (HttpURLConnection)(url.openConnection());
265 		conn.setDoOutput(true);
266 		conn.setDoInput(true);
267 		conn.setRequestMethod("POST");
268 		conn.setUseCaches(false);
269 		conn.setAllowUserInteraction(false);
270 
271 		PrintWriter out=new PrintWriter(conn.getOutputStream());
272 		out.println(report);
273 		out.flush();
274 		out.close();
275 
276 		BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
277 		
278 		String s;
279 		while ((s = in.readLine())!=null) {
280 			logger.debug(s);
281 		}
282 		
283 		in.close();
284 		conn.disconnect();
285 
286 		//if(conn.getResponseCode()!=HttpURLConnection.HTTP_OK)
287 		// logger.error("Problem sending problem report");
288 		// throw new IOException(conn.getResponseMessage());
289 	}
290 	
291 	/***
292 	 * 
293 	 */
294 	private void readBlackList() 
295 	{
296 		logger.info("reading problem report blacklist: " 
297 					+ BLACKLIST_FILENAME);
298 		
299 		FileInputStream in = null;
300 		try {
301 			in = new FileInputStream(BLACKLIST_FILENAME);
302 			ObjectInputStream p = new ObjectInputStream(in);
303 			blacklist = (HashSet)p.readObject();
304 		} 
305 		catch (Throwable e) {
306 		}
307 		finally {
308 			try {
309 				if (in != null) {
310 					in.close();
311 				}
312 			}
313 			catch (IOException e) {
314 			}
315 		}
316 	}
317 
318 	/***
319 	 * 
320 	 */
321 	private void writeBlacklist() 
322 	{
323 		logger.info("writing problem report blacklist: " 
324 					+ BLACKLIST_FILENAME);
325 
326 		FileOutputStream out = null;
327 		try {
328 			out = new FileOutputStream(BLACKLIST_FILENAME);
329 			ObjectOutputStream p = new ObjectOutputStream(out);
330 			p.writeObject(blacklist);
331 		} 
332 		catch (IOException e) {
333 		}
334 		finally {
335 			try {
336 				if (out != null) {
337 					out.close();
338 				}
339 			}
340 			catch (IOException e) {
341 			}
342 		}
343 	}
344 
345 }