Sunday, November 02, 2008

JDBC debugging

Recently I had to muck through a whole bunch of code to fix a connection pooling issue because of which some partial results were getting committed into the database. While the actual problem turned out to be a commit getting issued deep in the custom framework code while querying for the nextval in an oracle sequence. As a by product of that investigation I wrote the following proxies to see which queries where executed by which connection. Usage is pretty simple. Just add the ConnectionInvocationHandler.createProxy(<your raw connection>) where ever you are getting the actual connection. The following classes only handle the most frequently occurring cases for me (for oracle).
package org.foo.sql;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;

public class ConnectionInvocationHandler implements InvocationHandler {
  
  Connection delegate = null;
  
  public static Connection createProxy(Connection conn) {
    return (Connection) Proxy.newProxyInstance(ConnectionInvocationHandler.class.getClassLoader(), new Class[]{ Connection.class }, new ConnectionInvocationHandler(conn));
  }
  
  ConnectionInvocationHandler(Connection original) {
    delegate = original;
  }

  public Object invoke(Object proxy, Method method, Object[] args)
      throws Throwable {
    Object result = method.invoke(delegate, args);
    String methodName = method.getName();
    if ( "prepareStatement".equals(methodName) ) {
      result = PreparedStatementInvocationHandler.createProxy((PreparedStatement) result, delegate.toString(), (String) args[0]);
    } else if ( "createStatement".equals(methodName) ) {
      result = StatementInvocationHandler.createProxy((Statement) result, delegate.toString());
    } else if ( "setAutoCommit".equals(methodName) ) {
      log("setAutoCommit(" + args[0] + ")");
    } else if ( "commit".equals(methodName) ) {
      log("commit()");
    } else if ( "rollback".equals(methodName) ) {
      log("rollback()");
    }
    return result;
  }
  
  private void log(String msg) {
    System.out.println("[" + delegate.toString() + "] " + msg);
  }
  
}
package org.foo.sql;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Statement;

public class StatementInvocationHandler implements InvocationHandler {

 String connId = null;
 Statement delegate = null;
  
  static Statement createProxy(Statement stmt, String connId) {
    return (Statement) Proxy.newProxyInstance(StatementInvocationHandler.class.getClassLoader(), new Class[]{ Statement.class }, new StatementInvocationHandler(stmt, connId));
  }
  
  StatementInvocationHandler(Statement stmt, String connId) {
    delegate = stmt;
    this.connId = connId;
  }
  
  public Object invoke(Object proxy, Method method, Object[] args)
      throws Throwable {
    Object result = method.invoke(delegate, args);
    String methodName = method.getName();
    if ( "executeQuery".equals(methodName) ) {
      log((String) args[0]);
    } else if ( "executeUpdate".equals(methodName) ) {
      log((String) args[0]);
    }
    return result;
  }
  
  private void log(String msg) {
    System.out.println("[" + connId + "][" + delegate.toString() + "] " + msg);
  }
}
package org.foo.sql;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.PreparedStatement;
import java.text.DecimalFormat;

public class PreparedStatementInvocationHandler implements InvocationHandler {
  
  String connId = null;
  StringBuilder query = null;
  PreparedStatement delegate = null;
  final DecimalFormat df = new DecimalFormat("0.#");
  
  static PreparedStatement createProxy(PreparedStatement pstmt, String connId, String query) {
    return (PreparedStatement) Proxy.newProxyInstance(PreparedStatementInvocationHandler.class.getClassLoader(), new Class[]{ PreparedStatement.class }, new PreparedStatementInvocationHandler(pstmt, connId, query));
  }
  
  PreparedStatementInvocationHandler(PreparedStatement original, String connId, String query) {
    delegate = original;
    this.connId = connId;
    this.query = new StringBuilder(query);
  }

  public Object invoke(Object proxy, Method method, Object[] args)
      throws Throwable {
    Object result = method.invoke(delegate, args);
    String methodName = method.getName();
    if ( "setInt".equals(methodName) ) {
      addParam(args[1].toString());
    } else if ( "setLong".equals(methodName) ) {
      addParam(args[1].toString());
    } else if ( "setDouble".equals(methodName) ) {
      addParam(df.format(((Double) args[1]).doubleValue()));
    } else if ( "setTimestamp".equals(methodName) ) {
      String dParam = args[1].toString();
      dParam = dParam.substring(dParam.indexOf('.')); // remove the ms
      addParam("to_date('" + dParam + "', 'yyyy-mm-dd hh24:mi:ss')");
    } else if ( "setString".equals(methodName) ) {
      addParam("'" + args[1].toString() + "'");
    } else if ( methodName.startsWith("set") ) {
      addParam("'<" + methodName.substring(3) + ">'");
    } else if ( "executeUpdate".equals(methodName) ) {
      log(query.toString());
    } else if ( "executeQuery".equals(methodName) ) {
      log(query.toString());
    }
    return result;
  }
  
  protected void addParam(String param) {
    int idx = query.indexOf("?");
    if ( idx > -1 ) {
      query.replace(idx, idx + 1, param);
    }
  }
  
  protected void log(String msg) {
    System.out.println("[" + connId + "][" + delegate.toString() + "] " + msg);
  }
}

Sunday, October 19, 2008

vi vi vi is the editor of the beast

I'm never satisfied with the text-editors I've been using so far. But, now I may have found _the one_. I started off with the humble MS-DOS edit, moved on to the GUI notepad then tried some serious text editors like EditPad, EditPlus, TextPad, SciteFlash, jEdit, Notepad++ and lots of others. There is always some feature that I really like accompanied with some equally irritating annoyance. Like for e.g. I love the beanshell find/replace in jEdit but, I had to give it up as it would choke for any file a little over 900KB. Editplus was great till I had to move to linux and Editpad was just too clunky.

I've used vi on and off mostly to quickly edit some file on the server to save the hassle of ftping the file back and forth. I never enjoyed these sessions much because I wasn't yet initiated to the magic of vi. Then one day I decided that all this brouhaha about vi and emacs must have some substance to it and hence resolved to use one of them as my primary editor henceforth. Its a tall order to master each of these editors but, since I was already familiar with vi and couldn't bear the ctrl+meta sequences of emacs for too long. For over 2 months now I've uninstalled every other text editor and use only gVim.

Its great. Everything you ever wanted to do with a piece of text can be done in vi. There are a wealth of cheatsheets and tutorials. Here are some of them.

Best of VIM Tips, gVIM's Key Features zzapper
A slightly advanced Introduction to Vim LG #152
Vi Reference Card
100 Vim commands every programmer should know

vi may be the editor of the beast because once you get to know it you start seeing its inner beauty :)

Thursday, May 08, 2008

Fun with awk

Recently I was wrangling with a few log files and sed and grep din't cut it and I had to use awk. I've always dreaded the use of awk as it was a programming language onto itself and hence I din't want to burden myself with learning another language that I rarely use. But, my latest interaction with awk left me satisfied and intrigued. Its almost like perl except that it follows a strict structure for just manipulating text and perl as I've learnt the hard way can be used to do anything (its been described as the duct tape that holds the internet together!!)

Thursday, February 28, 2008

Java Encryption

I was working on a project that required some encryption. So, I had some time to play around with the javax.crypto api. Here is a small utility which plays around with text files before encryption.
package org.foo.bar; 
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringWriter;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;

import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;


public class FcUtil {
  private static final String UTF8 = "utf-8";
  private static SecretKey getKey(String alg, String pass) {
    try {
      KeyGenerator keyGen = KeyGenerator.getInstance("Rijndael");
      keyGen.init(new SecureRandom(pass.getBytes(UTF8)));
      return keyGen.generateKey();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  private static Cipher getCipher(String alg, String pass) {
    try {
      Cipher cipher = Cipher.getInstance(alg);
      cipher.init(Cipher.ENCRYPT_MODE, getKey(alg, pass));
      return cipher;
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  private static void encrypt(Cipher cipher, InputStream is, OutputStream os) {
    try {
      byte[] buffer = new byte[1024 * 1024];
      CipherOutputStream cos = new CipherOutputStream(os, cipher);
      int read = -1;
      int count = 10;
      while ( (read = is.read(buffer) ) > 0 ) {
        cos.write(buffer, 0, read);
        if ( --count < 0 ) {
          count = 10;
          cos.flush();
        }
      } // endwhile
      is.close();
      cos.close();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  private static String rev(String str) {
    char[] chars = str.toCharArray();
    int l = chars.length, n = l >> 1;
    char c;
    for (int i = 0; i < n; i++ ) {
      c = chars[(b = l-i)];
      chars[b] = chars[i];
      chars[i] = c;
    }
    return new String(chars);
  }
  private static byte[] processText(File file) {
    try {
      BufferedReader br = new BufferedReader(
          new InputStreamReader(new FileInputStream(file), UTF8));
      ArrayList<String> list = new ArrayList<String>();
      StringBuilder b = new StringBuilder();
      String buf = null;
      while ( (buf = br.readLine()) != null ) {
        b.delete(0, b.length());
        list.add(b.insert(0, buf).reverse().toString());
      }
      Collections.reverse(list);
      br.close();
      int count = 10;
      StringWriter sw = new StringWriter();
      for (String s : list) {
        sw.write(s);
        sw.write('\n');
        if ( --count < 0 ) {
          count = 10;
          sw.flush();
        }
      }
      sw.close();
      return sw.getBuffer().toString().getBytes(UTF8);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  private static ByteArrayInputStream processText(InputStream is) {
    try {
      BufferedReader br = new BufferedReader(
          new InputStreamReader(is, UTF8));
      ArrayList<String> list = new ArrayList<String>();
      StringBuilder b = new StringBuilder();
      String buf = null;
      while ( (buf = br.readLine()) != null ) {
        b.delete(0, b.length());
        list.add(b.insert(0, buf).reverse().toString());
      }
      Collections.reverse(list);
      br.close();
      int count = 10;
      StringWriter sw = new StringWriter();
      for (String s : list) {
        sw.write(s);
        sw.write('\n');
        if ( --count < 0 ) {
          count = 10;
          sw.flush();
        }
      }
      sw.close();
      return new ByteArrayInputStream(sw.getBuffer().toString().getBytes(UTF8));
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  private static ByteArrayInputStream readFully(File file) {
    try {
      FileInputStream fis = new FileInputStream(file);
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      byte[] buffer = new byte[1024 * 1024];
      int read = -1;
      while ( (read = fis.read(buffer)) > 0 ) {
        baos.write(buffer, 0, read);
      }
      fis.close();
      baos.close();
      return new ByteArrayInputStream(baos.toByteArray());
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  private static void transferFully(InputStream is, OutputStream os) {
    try {
      byte[] buffer = new byte[1024 * 1024];
      int read = -1;
      int count = 10;
      while ( (read = is.read(buffer)) > 0 ) {
        os.write(buffer, 0, read);
        if ( --count < 0 ) {
          count = 10;
          os.flush();
        }
      }
      is.close();
      os.close();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  private static void process(Cipher cipher, File file) {
    if ( file.isDirectory() ) {
      return;
    }
    HashSet<String> textExt = new HashSet<String>() {{
      add("txt");
      add("java");
      add("properties");
      add("xml");
      add("bat");
      add("sh");
      add("jsp");
      add("html");
      add("tld");
      add("js");
      add("css");
      add("dtd");
      add("xsd");
      add("sql");
      add("ddl");
    }};
    try {
      String ext = file.getName().substring(file.getName().lastIndexOf('.')+1).toLowerCase();
      if ( textExt.contains(ext) ) {

        // encrypt
        ByteArrayInputStream bis = new ByteArrayInputStream(processText(file));
        encrypt(cipher, bis, new FileOutputStream(file));

        // decrypt
//        ByteArrayOutputStream baos = new ByteArrayOutputStream();
//        encrypt(cipher, readFully(file), baos);
//        transferFully(processText(new ByteArrayInputStream(
//            baos.toByteArray())), new FileOutputStream(file));
      } else {
        // encrypt & decrypt
        ByteArrayInputStream bis = readFully(file);
        encrypt(cipher, bis, new FileOutputStream(file));
      }
    } catch (Exception e) {
      throw new RuntimeException(e);
    }    
  }
  private static void handleDir(Cipher cipher, File dir) {
    long start = System.currentTimeMillis();
    FileFilter fileFilter = new FileFilter() {
      HashSet<String> encExt = new HashSet<String>() {{
        add("txt");
        add("java");
        add("properties");
        add("xml");
        add("bat");
        add("sh");
        add("jsp");
        add("html");
        add("tld");
        add("js");
        add("css");
        add("dtd");
        add("xsd");
        add("sql");
        add("ddl");
        
        add("doc");
        add("xls");
        add("ppt");
        add("pps");
        add("pdf");
        add("pst");
      }};
      public boolean accept(File pathname) {
        String ext = pathname.getName().substring(pathname.getName().lastIndexOf('.')+1).toLowerCase();
        return pathname.isFile() && encExt.contains(ext);
      }
    };
    FileFilter dirFilter = new FileFilter() {
      HashSet<String> exclude = new HashSet<String>() {{
        add(".svn");
        add("cvs");
        add(".hg");
      }};
      public boolean accept(File pathname) {
        return pathname.isDirectory() && !exclude.contains(pathname.getName().toLowerCase()) && pathname.getName().indexOf('.') != 0;
      }
      
    };
    File[] files = dir.listFiles(fileFilter);
    for (File file : files) {
//      System.out.println(file.getAbsolutePath());
      process(cipher, file);
    }
    File[] dirs = dir.listFiles(dirFilter);
    handleDirs(cipher, dirs);
//    System.out.println(dir.getAbsolutePath() + "[" + (System.currentTimeMillis() - start) + "ms]");
  }
  private static void handleDirs(Cipher cipher, File[] dirs) {
    for (File dir : dirs) {
      handleDir(cipher, dir);
    }
  }
  public static void main(String[] args) {
    final String alg = "Rijndael";
    if ( args.length != 2 ) {
      System.out.println("error");
      return;
    }
    final String key = args[0];
    
    try {
      Cipher cipher = getCipher(alg, key);
//      cipher.init(Cipher.DECRYPT_MODE, getKey(alg, key));
      final File file = new File(args[1]);
      handleDir(cipher, file);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

Slashdot Userscript

The funny quote in the footer of slashdot has always held my attention...here's a userscript to make it the title of the window
// ==UserScript==
// @name          Slashdot title quote
// @include       http://www.slashdot.org
// @include       http://www.slashdot.org/*
// @include       http://*.slashdot.org
// @include       http://slashdot.org/*
// @author        fc
// @namespace     http://fc-unleashed.blogspot.com
// @description   the footer quote in the title
// ==/UserScript==
(function() {
   if ( document.getElementById ) {
       var footer = document.getElementById('footer');
       if ( footer &amp;&amp; footer.getElementsByTagName ) {
           var small = footer.getElementsByTagName('small');
           if ( small &amp;&amp; small[0] ) {
               document.title = small[0].firstChild.nodeValue;
           }
       }
   }
})();