Tuesday, February 23, 2010

Pretty Printing JSON in Tapestry 5

One great feature of Tapestry 5 is the ability to construct properly-formed JSON to send to the client side for initializing javascript code. The only problem I've had with this is how the JSON is formatted at the bottom of the generated HTML page. For complex javascript components, it becomes a bit of a jumble of characters that are tricky to decipher at-a-glance during development. Sure, I could probably find a JSON formatter to paste it into, but that adds another step to the process.

As an example, this is the generated JSON for one of our components:


WidenUI.createGridView({"userAjaxError":"There was an error performing this action. Please contact an administrator for assistance.","successTitle":"Success","store":{"mappings":["id","class","objects-in-index","indexable-objects-in-database","difference","actions"],"context":[],"url":"/t5/widendev/searchadmin:indexstatusjson"},"resultsPerPage":25,"cellClickListeners":[],"errorTitle":"Error","isDev":true,"gridPanel":{"id":"grid","title":"Index Status","renderTo":"generated-gridview-grid","columns":[{"sortable":false,"width":1,"dataIndex":"class","hasAction":false,"header":"Class"},{"sortable":false,"width":1,"dataIndex":"objects-in-index","hasAction":false,"header":"Objects in Index"},{"sortable":false,"width":1,"dataIndex":"indexable-objects-in-database","hasAction":false,"header":"Indexable Objects in Database"},{"sortable":false,"width":1,"dataIndex":"difference","hasAction":false,"header":"Difference"}]},"options":[]});


There's a source URL buried in there somewhere, as an example of something I'd like to be able to pick out easily when issues arise.

To get this formatted a little nicer, I have pulled Tapestry's JSONObject class onto my classpath and changed toString() to this:


private static ThreadLocal<Integer> toStringLevel = new ThreadLocal<Integer>();

private int incrementToStringLevel()
{
   if (toStringLevel.get() == null)
   {
      toStringLevel.set(0);
   }

   toStringLevel.set(toStringLevel.get() + 1);

   return toStringLevel.get();
}

private void decrementStringLevel()
{
   toStringLevel.set(toStringLevel.get() - 1);
}

@Override
public String toString()
{
   int level = incrementToStringLevel();

   boolean comma = false;

   StringBuilder buffer = new StringBuilder("{\n");

   for (String key : keys())
   {
      if (comma)
      {
         buffer.append("¥");
      }

      for (int i = 0; i < level; i++)
      {
         buffer.append(" ");
      }

      buffer.append(quote(key));
      buffer.append(':');
      buffer.append(valueToString(properties.get(key)));

      comma = true;
   }

   buffer.append('}');

   decrementStringLevel();

   String result = buffer.toString();

   if (result.length() > 110)
   {
      result = result.replace("¥", ",\n");
   }
   else
   {
      result = result.replace("¥", ",");
   }

   return result;
}


Admittedly, that's a pretty ugly chunk of code, but it gets the job done:


WidenUI.createGridView({
 "userAjaxError":"There was an error performing this action. Please contact an administrator for assistance.",
 "successTitle":"Success",
 "store":{
  "mappings":["id","class","objects-in-index","indexable-objects-in-database","difference","actions"],
  "context":[],
  "url":"/t5/widendev/searchadmin:indexstatusjson"},
 "resultsPerPage":25,
 "cellClickListeners":[],
 "errorTitle":"Error",
 "isDev":true,
 "gridPanel":{
  "id":"grid",
  "title":"Index Status",
  "renderTo":"generated-gridview-grid",
  "columns":[{
   "sortable":false, "width":1, "dataIndex":"class", "hasAction":false, "header":"Class"},{
   "sortable":false,
   "width":1,
   "dataIndex":"objects-in-index",
   "hasAction":false,
   "header":"Objects in Index"},{
   "sortable":false,
   "width":1,
   "dataIndex":"indexable-objects-in-database",
   "hasAction":false,
   "header":"Indexable Objects in Database"},{
   "sortable":false, "width":1, "dataIndex":"difference", "hasAction":false, "header":"Difference"}]},
 "options":[]});


I've debated whether this should go into the actual Tapestry codebase (to be switched on when production mode is off at least), but it is pretty ugly, I'm not sure of a proper way to check the PRODUCTION_MODE flag inside JSONObject, and I'm not sure how many people want it. For now, if you want this, you can pull in the class and modify it like I have.

Monday, February 22, 2010

Running Tapestry 5.2 Snapshot on Google App Engine

Google App Engine represents a great hosting option these days; it is free up to a fairly generous amount of server resources, after which billing kicks in at reasonable rate up to a budget you specify. Between that and the power and scalability of Google's infrastructure, it is seems like a great option for new projects (or existing projects if you happen to be using the right mix of technologies).

The only catch is that you're limited to a pretty specific set of technologies, and this does NOT include a relational database, or Hibernate! Hence, great for new projects, not so great if you have a significant app that relies on Hibernate or even a relational database paradigm.

My web framework of choice, Tapestry, is sortof supported, but it takes a little work to get it running on the local development server provided by Google.

As of this post, you'll want to be running on the Tapestry 5.2 snapshot. Tapestry 5.1 uses a stax parser that is incompatible with GAE. I'm currently running this on GAE 1.3.1.

Dmitry Gusev has a great post on how to fix a strange Javassist-related error "java.lang.ClassFormatError: Invalid length ... in LocalVariableTable". Unfortunately for me, it took a little extra work to get that fix incorporated into the development server. This thread from the Tapestry user list got me the rest of the way. You'll want to check out this post for some additional gotchas.

These instructions essentially explain the Tapestry list thread in a little more detail since I struggled a little to get it working. I am assuming you already have a working app running on Tapestry 5.2 snapshot, that you have an output "war" folder in the proper format for GAE to run against, and that you have GAE installed, with the /bin folder in your system path.

I'm using IntelliJ CE on Windows, so I am forced to use the command line or an ant script to run the server. These instructions show how to setup both.

1. Create and compile a class com.google.appengine.tools.development.agent.AppEngineDevAgent with the following source:

package com.google.appengine.tools.development.agent;

import java.lang.instrument.Instrumentation;

public class AppEngineDevAgent
{
public static Object getAgent()
{
return null;
}

public static void premain(String foo, Instrumentation bar) {}
}

2. In <sdk root>/lib/agent, extract appengine-agent.jar

3. Go into the extracted files to com/google/appengine/tools/development/agent and copy in the .class file created in step 2, overwriting the existing file

4. Re-jar the extracted files and call it patched-appengine.jar, putting it in <sdk root>/lib/agent

5. Backup <sdk root>/bin/dev_appserver.cmd and replace the contents of the original with this (replacing <sdk root>):

@java -javaagent:<sdk root>/lib/agent/patched-appengine.jar -cp "%~dp0\..\lib\appengine-tools-api.jar" ^
com.google.appengine.tools.development.DevAppServerMain %*

Alternatively, if you want to run the ant script provided by GAE, backup <sdk root>/config/user/ant-macros.xml and find the dev_appserver macrodef. Replace the <sequential> section with this (replacing <sdk root>):

<sequential>
<java classname="com.google.appengine.tools.development.DevAppServerMain"
classpath="${appengine.tools.classpath}"
fork="true" failonerror="true">
<jvmarg value="-javaagent:<sdk root>/lib/agent/patchedappengine.jar" />
<arg value="@{war}"/>
</java>
</sequential>

The nice thing about this is that the production deployment process does not require any extra work (I tried it on a page that gave me the LocalVariableTable error, and it worked without issue).

Hopefully there won't be a need for workarounds with Tapestry soon, Howard Lewis Ship has been working on abstracting Javaassist out of Tapestry recently, and I've seen some GAE compatibility commits from him as well.

Next up, try to figure out how to get JDO working with Tapestry!

Monday, April 13, 2009

Budgeting Software Comparison & Spreadsheet

Over the past 6 weeks, my wife and I have been attending Dave Ramsey's Financial Peace University course online. If you have some debt and want to learn how to get rid of it for good, do yourself a favor and take this course. It has absolutely revolutionized the way we use and think about money.

The trickiest part of the program is coming up with monthly budgets and sticking to them. As a software developer, I wanted to find an easy way to track the monthly budget on the computer, and I had some very specific ideas about how I wanted the information laid out.

I've spent several weeks now trying different software programs to see what works best:
  1. Gazelle Budget: This is an online application you are given access to when you take the FPU course. pros: Makes it easy to create a zero-based budget. Good category groupings. Nice interface. cons: After the 4-month subscription to the courseware ends, you have to pay an annual fee to continue using the software, which is a little pricey. This program also does not connect directly to your bank, but it can import transaction files.
  2. Microsoft Money: Boxed software that runs on your computer. pros: Allows you to create a budget and can connect directly with the bank to download and automatically categorize expenses. cons: Not free. Not well suited to creating a zero-based budget (doesn't show income remaining for example). Clunky interface. Automatic categorization is often wrong, and the program doesn't learn from the categorizations you make. Not online, so only available on one computer. My copy of this program is a couple years old, so maybe some of these items have been addressed since then.
  3. mint.com: Relatively new, free online budgeting site. pros: Can be used to create a budget. Excellent, intuitive, web-2.0 interface. Uses Yodlee's authentication mechanism for connecting directly to your bank (if your bank is supported) to download and automatically categorize expenses. cons: Like Microsoft Money, not great for creating a zero-based budget. Security is a little suspect; while they authenticate with the bank using Yodlee, the site itself does not employ the same security devices that the Yodlee site does (in other words, the same ones used by the bank like security questions, CAPTCHAs, etc.)
  4. yodlee.com: Free online budgeting site. pros: Can be used to create a budget. Decent interface. Great security that is trusted by some of the biggest banks out there. Automatic categorization of expenses, with the ability to split transactions across multiple categories. cons: Interface is not as nice or intuitive as mint.com's. Also difficult to create a zero-based budget.
  5. Excel spreadsheet: pros: Free to use. Highly flexible and customizable. Easy to emulate the best features of the other software programs, and ignore the ones you don't care about. cons: Takes some time to create the templates. No transaction tracking. Mostly only available on one computer (unless it is shared in some way).
After reviewing these options, I ultimately decided on a hybrid option: use an Excel spreadsheet to create the budget, and use mint.com to track and categorize expenses in certain categories to keep the budget honest. The categories I am tracking on mint.com are groceries, entertainment, gas & fuel, restaurants, home improvement, lawn & garden, and fast food. I really don't feel the need to track anything on there except the categories that might go over budget.

If you search online, it is easy to find budgeting spreadsheets, some of which mesh nicely with FPU teachings. Being the picky person I am though, none of them laid things out as nicely as I would have liked. I ended up creating my own spreadsheet to meet the following goals:
  • One Page: I want to be able to see my entire month's budget on one page, without having to scroll down. Many of the templates I looked at did not utilize space very well, and often included details I don't care about in a monthly budget.
  • Easy to Modify: I want to be able to add, change, and remove subcategories easily. This means the formulas should work no matter what I change, I shouldn't have to insert entire rows into the spreadsheet to add things, and no putting things into an "other" category squirreled away in some corner of the sheet.
  • Annual Expense Savings: I want to be able to see when periodic expenses happen throughout the year (holidays, birthdays, water bill, homeowners insurance, etc.), and how much I should deduct from each paycheck to cover those expenses.
Here is a screenshot of the monthly budget section (using fake names and numbers). Click on the thumbnail to see a full-size version:


So the categories on the left have space for up to 7 user-defined subcategories. Adding, changing, or removing subcategories causes the category total and percentage to re-generate, and the Remaining to Budget number to regenerate. Color coding is used to indicate when a category is within Dave's recommended ranges (black = under, green = within range, red = over). Remaining to Budget is also colored based on whether there is money left to budget (green), zero remaining (black), or too much budgeted (red). Everything is on one screen, the layout makes sense, and changing items is a breeze.

As I mentioned though, I also wanted some tracking on annual expenses for things like holidays, birthdays, fertilizer, license plate renewals, etc. For this I created a sheet on another tab (more made up numbers and names):

The idea with this sheet is that you create a separate savings account that acts as a "buffer" for months that have more known expenses than others. So what I did was to change my direct deposit to take the amount listed for DEDUCTION PER CHECK, and send that directly to the savings account. Then, at the beginning of each month, I transfer the monthly total on the spreadsheet from that savings account to checking. This has the effect of evening-out expenses over the course of the year, and it means that the items on this sheet don't even need to be included in the regular monthly budget, simplifying monthly budgeting in the process.

This works much the same way as the budget sheet; you can add, change, or remove expenses on each month, and the monthly total, yearly total, and deduction per check changes automatically. In my case, I get paid bi-weekly so the deduction per check is simply the yearly total divided by 26. Of course, if you are paid on a different schedule, it should be easy to change this formula.

This spreadsheet is not written in stone; it makes some choices based on our situation, and should be adjusted to fit yours. I'm providing it as a suggested layout for tracking these things. I know I tend to stick with technology that is easy to use and gives me the right level of detail, and that was my goal with this sheet. When it comes time to generate the next month's budget, I recommend right-clicking on the current month's tab, and copying it onto a new tab.

With that, here is the spreadsheet for your budgeting pleasure: DOWNLOAD

I'd love to hear any feedback on my budgeting software reviews or the spreadsheet itself. Thanks!