package de.axone.exception.codify; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.util.Map; import java.util.TreeMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import de.axone.data.Charsets; import de.axone.tools.Mapper; import de.axone.tools.S; import de.axone.tools.Str; import de.axone.tools.Str.MapJoiner; import de.axone.web.SuperURLBuilders; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; // TODO: Registrieren von Packages. Oder macht man das besser online? Oder beides? // Vermutlich beides, da man lokal so auch eine Unterscheidung der eigenen Packages // hinbekommt, online dagegen Package-Listen gepflegt werden können public abstract class Codifier { private static final Logger log = LoggerFactory.getLogger( Codifier.class ); private static volatile String baseUrl = "http://codify.axon-e.de/codify.php/"; private static volatile boolean includeLink = false; private static volatile String project = "Codifier"; private static volatile String version = "1.0"; public static void report( Throwable throwable ) throws IOException { report( throwable, null ); } public static void report( Throwable throwable, Object context ) throws IOException { report( throwable, Mapper.treeMap( "context", context.toString() ) ); } @SuppressFBWarnings( value="RV_DONT_JUST_NULL_CHECK_READLINE", justification="See comment below." ) public static void report( Throwable throwable, Map<String,String> parametersAdd ) throws IOException { if( throwable instanceof Codified ){ report( ((Codified)throwable).getWrapped(), parametersAdd ); return; } Description desc = description( throwable ); URL url = url( throwable ); Map<String,String> parametersUse = new TreeMap<String,String>(); if( parametersAdd != null ) parametersUse.putAll( parametersAdd ); parametersUse.putAll( desc.map() ); parametersUse.put( "action", "report" ); if( log.isDebugEnabled() ){ if( ! log.isTraceEnabled() ){ Map<String,String> parametersLog = new TreeMap<>( parametersUse ); parametersLog.remove( "stack" ); log.debug( "Report: {}", parametersLog ); } else { log.trace( "Report: {}", parametersUse ); } } String parameters = Str.join( URL_JOINER, parametersUse ); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setDoOutput(true); con.setRequestProperty( "User-Agent", "Codify/1.0" ); con.setInstanceFollowRedirects( false ); try( OutputStreamWriter writer = new OutputStreamWriter(con.getOutputStream(), Charsets.UTF8 ) ){ writer.write(parameters); writer.flush(); try( BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream())) ){ // Debugging: if( log.isDebugEnabled() ){ StringBuilder resultText = new StringBuilder(); String line; while( (line=reader.readLine()) != null){ resultText.append( line ); } log.debug( resultText.toString() ); } else { // This is needed. Without reading the answer the request isn't send properly. // Quantum effects? while( reader.readLine() != null ); } } } } private static MapJoiner<String,String> URL_JOINER = new MapJoiner<String,String>() { @Override public String getRecordSeparator() { return "&"; } @Override public String getFieldSeparator() { return "="; } @Override public String keyToString( String nameField, int index ) { try { return URLEncoder.encode( nameField, Charsets.utf8 ); } catch( UnsupportedEncodingException e ) { return "CANNOT_ENCODE_NAME"; } } @Override public String valueToString( String valueField, int index ) { if( valueField == null ) return S.NULL; try { return URLEncoder.encode( valueField, Charsets.utf8 ); } catch( UnsupportedEncodingException e ) { return "CANNOT_ENCODE_VALUE"; } } }; public static String codify( Throwable throwable ){ return new Description( throwable ).code(); } public static Description description( Throwable throwable ){ return new Description( throwable ); } public synchronized static void setBaseUrl( String baseUrl ){ Codifier.baseUrl = baseUrl; } public synchronized static void setIncludeLink( boolean includeLink ){ Codifier.includeLink = includeLink; } public synchronized static void setProject( String project ){ Codifier.project = project; } public synchronized static void setVersion( String version ){ Codifier.version = version; } public static URL url( Throwable throwable ) { return SuperURLBuilders.fromString().build( link( throwable ) ).toURL(); } public static String baseUrl(){ return baseUrl; } public static String link( Throwable throwable ){ return baseUrl() + codify( throwable ); } public static String message( Throwable throwable ){ return throwable.getMessage() + " [" + (includeLink ? link( throwable ) : codify( throwable )) + "]"; } public static String localizedMessage( Throwable throwable ){ return throwable.getLocalizedMessage() + " [" + (includeLink ? link( throwable ) : codify( throwable )) + "]"; } public static class Description { private final Throwable t; private final StackTraceElement e; public Description( Throwable t ){ this.t = t; this.e = t.getStackTrace()[ 0 ]; } public String file(){ return e.getFileName(); } public String method(){ return e.getMethodName(); } public int line(){ return e.getLineNumber(); } public String exception(){ return t.getClass().getSimpleName(); } public String message(){ return t.getMessage(); } /* public String stack(){ StringBuilder result = new StringBuilder(); appendStackTrace( result, t ); return result.toString(); } private void appendStackTrace( StringBuilder b, Throwable t ){ if( t == null ) return; b.append( t.getClass().getCanonicalName() ).append(":\n"); for( StackTraceElement e : t.getStackTrace() ){ b.append( "\t" ).append( e.toString() ).append( S.nl ); } appendStackTrace( b, t.getCause() ); } */ public String stack(){ StringWriter s = new StringWriter(); PrintWriter pw = new PrintWriter( s ); t.printStackTrace( pw ); return s.getBuffer().toString(); } public Map<String,String> map(){ Map<String,String> result = new TreeMap<String,String>(); result.put( "project", project ); result.put( "file", file() ); result.put( "method", method() ); result.put( "line", ""+line() ); result.put( "exception", exception() ); result.put( "message", message() ); result.put( "stack", stack() ); result.put( "version", version ); return result; } public String code(){ int fileCode = Hash.hash( file() ); int methodCode = Hash.hash( method() ); int lineCode = Hash.hash( line() ); int exceptionCode = Hash.hash( exception() ); int textCode = message() != null ? Hash.hash( message() ) : 0; String combinedCode = String.format( "%08x/%08x/%08d/%08x/%08x", fileCode, methodCode, lineCode, exceptionCode, textCode ); return combinedCode; } @Override public String toString(){ return map().toString(); } } }