Friday, December 11, 2009

Groovy on Google App Engine Part II

In the previous post, I linked to the demo of the Atlanta Groovy on Grails User Group presentation from August given by Pratik Patel. In this, post we will review the steps required to create a Groovy app in the demo, and in addtion create some nice little Groovy features to the application.

Recap

Using Eclipse 3.5 Galileo the following plugins were installed for this demo..
- Groovy Eclipse Plugin
- Google AppEngine Eclipse Plugin
- Aptana Studio Plugin

Before starting you will need a Google AppEngine Account. Google AppEngine now supports Python and Java. We will be running Groovy over Java using the Groovy Gaelyk library that allows Groovy libraries to run on the Google App Engine. The data service is also on Google's servers and is using DataStore.

Next, create a Google AppEngine project in Eclipse. The Google AppEngine plugin should add this feature into your Eclipse IDE. We should treat the project as if we were creating a Google AppEngine java project, and then we will include the Gaelyk libraries to allow Groovlets to run on the server.

Update the appengine-web.xml

<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<application>pac-gcloud</application>
<version>1</version>

<!-- Configure java.util.logging -->
<system-properties>
<property name="java.util.logging.config.file"
value="WEB-INF/logging.properties"/>
</system-properties>
<static-files>
<exclude path="/WEB-INF/**.groovy" />
<exclude path="**.gtpl" />
</static-files>
</appengine-web-app>


Update web.xml to handle Gaelyk servlets

<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
<!-- The Gaelyk Groovlet servlet -->
<servlet>
<servlet-name>GroovletServlet</servlet-name>
<servlet-class>groovyx.gaelyk.GaelykServlet</servlet-class>
</servlet>

<!-- The Gaelyk template servlet -->
<servlet>
<servlet-name>TemplateServlet</servlet-name>
<servlet-class>groovyx.gaelyk.GaelykTemplateServlet</servlet-class>
</servlet>

<!-- Specify a mapping between *.groovy URLs and Groovlets -->
<servlet-mapping>
<servlet-name>GroovletServlet</servlet-name>
<url-pattern>*.groovy</url-pattern>
</servlet-mapping>

<!-- Specify a mapping between *.gtpl URLs and templates -->
<servlet-mapping>
<servlet-name>TemplateServlet</servlet-name>
<url-pattern>*.gtpl</url-pattern>
</servlet-mapping>

<!-- Define index.gtpl as a welcome file -->
<welcome-file-list>
<welcome-file>index.gtpl</welcome-file>
</welcome-file-list>
</web-app>


Copy Gaelyk and Groovy jar file into your webapp/lib directory.

Next, we need an index.gtpl that will be the landing page for this application. The index.gtpl used in this demo can be found here. In this index page we provide links to all the source code for this project. The application starts at the edititems.gtpl page.

The edititems.gtpl will load the data from Google DataStore with the following lines...

<% def query = new Query("Item")
def entityList = datastoreService.prepare(query).asList(FetchOptions.Builder.withLimit(100))
%>


The Items.groovy entity seen above is used to handle the different types of requests as well.

All of the code can be found here. There is a good bit of code for each function of the Items.groovy file, so the exercise of reviewing those functions can be completed outside of this article.

Also, one unique item is a screen scraper which is executed when selecting the "import" button in the edititems.gtpl file. This function uses the XMLSlurper and TagSoup to screen scrape an html file in a wikipedia table. The function then generates a list of buildings in Jacksonville, FL sorted by height and imports the list into the Google DataStore database. The list will be displayed next time the edititems.gtpl is viewed.

public class Building {

def findTallestInJacksonville = {
def xmlReader = new XmlSlurper(new org.ccil.cowan.tagsoup.Parser())
def url = new URL("http://en.wikipedia.org/wiki/List_of_tallest_buildings_in_Jacksonville")
url.withReader { reader ->
def html = xmlReader.parse(reader)
def listTable = html.body.div.div[0].div.div[1].table[0].tr
return listTable.list()
}
}



The code can be deployed from Eclipse to Google by selecting the project and right click > Google > Deploy to App Engine. The entire code base and functoning application that was deployed can be found here...

http://pac-gcloud.appspot.com/

Also, I recommend Google AppEngine, since it also has some nice management features and is free up to certain limits. This is a great way to start up an application for free until it is successful. Good luck.

Sunday, October 4, 2009

Groovy on Google App Engine Using Gaelyk

There is an interesting post by Pratik Patel from last month's Atlanta Groovy and Grails (ATL2G) meeting. This video demonstrates using Groovy on Google AppEngine using the Groovy Gaelyk libraries. If you are interested in taking advantage of Google's cloud computing resources using Groovy, this is a good presentation.

More posts will follow with a brief written "How-to" and some twists on Pratik's demo.

Sunday, July 26, 2009

Java TimeZones

There seems to be a lack of documented TimeZone ids for Java 6. For anyone searching for their timezone, the list is below. Yah, I know not terribly exciting. More interesting Java posts will follow.

Code snippet:

import java.util.TimeZone
for (String id:TimeZone.getAvailableIDs()){println id + ", "}


TimeZones:
Etc/GMT+12, Etc/GMT+11, MIT, Pacific/Apia, Pacific/Midway, Pacific/Niue, Pacific/Pago_Pago, Pacific/Samoa, US/Samoa, America/Adak, America/Atka, Etc/GMT+10, HST, Pacific/Fakaofo, Pacific/Honolulu, Pacific/Johnston, Pacific/Rarotonga, Pacific/Tahiti, SystemV/HST10, US/Aleutian, US/Hawaii, Pacific/Marquesas, AST, America/Anchorage, America/Juneau, America/Nome, America/Yakutat, Etc/GMT+9, Pacific/Gambier, SystemV/YST9, SystemV/YST9YDT, US/Alaska, America/Dawson, America/Ensenada, America/Los_Angeles, America/Tijuana, America/Vancouver, America/Whitehorse, Canada/Pacific, Canada/Yukon, Etc/GMT+8, Mexico/BajaNorte, PST, PST8PDT, Pacific/Pitcairn, SystemV/PST8, SystemV/PST8PDT, US/Pacific, US/Pacific-New, America/Boise, America/Cambridge_Bay, America/Chihuahua, America/Dawson_Creek, America/Denver, America/Edmonton, America/Hermosillo, America/Inuvik, America/Mazatlan, America/Phoenix, America/Shiprock, America/Yellowknife, Canada/Mountain, Etc/GMT+7, MST, MST7MDT, Mexico/BajaSur, Navajo, PNT, SystemV/MST7, SystemV/MST7MDT, US/Arizona, US/Mountain, America/Belize, America/Cancun, America/Chicago, America/Costa_Rica, America/El_Salvador, America/Guatemala, America/Indiana/Knox, America/Indiana/Tell_City, America/Knox_IN, America/Managua, America/Menominee, America/Merida, America/Mexico_City, America/Monterrey, America/North_Dakota/Center, America/North_Dakota/New_Salem, America/Rainy_River, America/Rankin_Inlet, America/Regina, America/Swift_Current, America/Tegucigalpa, America/Winnipeg, CST, CST6CDT, Canada/Central, Canada/East-Saskatchewan, Canada/Saskatchewan, Chile/EasterIsland, Etc/GMT+6, Mexico/General, Pacific/Easter, Pacific/Galapagos, SystemV/CST6, SystemV/CST6CDT, US/Central, US/Indiana-Starke, America/Atikokan, America/Bogota, America/Cayman, America/Coral_Harbour, America/Detroit, America/Eirunepe, America/Fort_Wayne, America/Grand_Turk, America/Guayaquil, America/Havana, America/Indiana/Indianapolis, America/Indiana/Marengo, America/Indiana/Petersburg, America/Indiana/Vevay, America/Indiana/Vincennes, America/Indiana/Winamac, America/Indianapolis, America/Iqaluit, America/Jamaica, America/Kentucky/Louisville, America/Kentucky/Monticello, America/Lima, America/Louisville, America/Montreal, America/Nassau, America/New_York, America/Nipigon, America/Panama, America/Pangnirtung, America/Port-au-Prince, America/Porto_Acre, America/Resolute, America/Rio_Branco, America/Thunder_Bay, America/Toronto, Brazil/Acre, Canada/Eastern, Cuba, EST, EST5EDT, Etc/GMT+5, IET, Jamaica, SystemV/EST5, SystemV/EST5EDT, US/East-Indiana, US/Eastern, US/Michigan, America/Caracas, America/Anguilla, America/Antigua, America/Aruba, America/Asuncion, America/Barbados, America/Blanc-Sablon, America/Boa_Vista, America/Campo_Grande, America/Cuiaba, America/Curacao, America/Dominica, America/Glace_Bay, America/Goose_Bay, America/Grenada, America/Guadeloupe, America/Guyana, America/Halifax, America/La_Paz, America/Manaus, America/Marigot, America/Martinique, America/Moncton, America/Montserrat, America/Port_of_Spain, America/Porto_Velho, America/Puerto_Rico, America/Santiago, America/Santo_Domingo, America/St_Barthelemy, America/St_Kitts, America/St_Lucia, America/St_Thomas, America/St_Vincent, America/Thule, America/Tortola, America/Virgin, Antarctica/Palmer, Atlantic/Bermuda, Atlantic/Stanley, Brazil/West, Canada/Atlantic, Chile/Continental, Etc/GMT+4, PRT, SystemV/AST4, SystemV/AST4ADT, America/St_Johns, CNT, Canada/Newfoundland, AGT, America/Araguaina, America/Argentina/Buenos_Aires, America/Argentina/Catamarca, America/Argentina/ComodRivadavia, America/Argentina/Cordoba, America/Argentina/Jujuy, America/Argentina/La_Rioja, America/Argentina/Mendoza, America/Argentina/Rio_Gallegos, America/Argentina/San_Juan, America/Argentina/San_Luis, America/Argentina/Tucuman, America/Argentina/Ushuaia, America/Bahia, America/Belem, America/Buenos_Aires, America/Catamarca, America/Cayenne, America/Cordoba, America/Fortaleza, America/Godthab, America/Jujuy, America/Maceio, America/Mendoza, America/Miquelon, America/Montevideo, America/Paramaribo, America/Recife, America/Rosario, America/Sao_Paulo, Antarctica/Rothera, BET, Brazil/East, Etc/GMT+3, America/Noronha, Atlantic/South_Georgia, Brazil/DeNoronha, Etc/GMT+2, America/Scoresbysund, Atlantic/Azores, Atlantic/Cape_Verde, Etc/GMT+1, Africa/Abidjan, Africa/Accra, Africa/Bamako, Africa/Banjul, Africa/Bissau, Africa/Casablanca, Africa/Conakry, Africa/Dakar, Africa/El_Aaiun, Africa/Freetown, Africa/Lome, Africa/Monrovia, Africa/Nouakchott, Africa/Ouagadougou, Africa/Sao_Tome, Africa/Timbuktu, America/Danmarkshavn, Atlantic/Canary, Atlantic/Faeroe, Atlantic/Faroe, Atlantic/Madeira, Atlantic/Reykjavik, Atlantic/St_Helena, Eire, Etc/GMT, Etc/GMT+0, Etc/GMT-0, Etc/GMT0, Etc/Greenwich, Etc/UCT, Etc/UTC, Etc/Universal, Etc/Zulu, Europe/Belfast, Europe/Dublin, Europe/Guernsey, Europe/Isle_of_Man, Europe/Jersey, Europe/Lisbon, Europe/London, GB, GB-Eire, GMT, GMT0, Greenwich, Iceland, Portugal, UCT, UTC, Universal, WET, Zulu, Africa/Algiers, Africa/Bangui, Africa/Brazzaville, Africa/Ceuta, Africa/Douala, Africa/Kinshasa, Africa/Lagos, Africa/Libreville, Africa/Luanda, Africa/Malabo, Africa/Ndjamena, Africa/Niamey, Africa/Porto-Novo, Africa/Tunis, Africa/Windhoek, Arctic/Longyearbyen, Atlantic/Jan_Mayen, CET, ECT, Etc/GMT-1, Europe/Amsterdam, Europe/Andorra, Europe/Belgrade, Europe/Berlin, Europe/Bratislava, Europe/Brussels, Europe/Budapest, Europe/Copenhagen, Europe/Gibraltar, Europe/Ljubljana, Europe/Luxembourg, Europe/Madrid, Europe/Malta, Europe/Monaco, Europe/Oslo, Europe/Paris, Europe/Podgorica, Europe/Prague, Europe/Rome, Europe/San_Marino, Europe/Sarajevo, Europe/Skopje, Europe/Stockholm, Europe/Tirane, Europe/Vaduz, Europe/Vatican, Europe/Vienna, Europe/Warsaw, Europe/Zagreb, Europe/Zurich, MET, Poland, ART, Africa/Blantyre, Africa/Bujumbura, Africa/Cairo, Africa/Gaborone, Africa/Harare, Africa/Johannesburg, Africa/Kigali, Africa/Lubumbashi, Africa/Lusaka, Africa/Maputo, Africa/Maseru, Africa/Mbabane, Africa/Tripoli, Asia/Amman, Asia/Beirut, Asia/Damascus, Asia/Gaza, Asia/Istanbul, Asia/Jerusalem, Asia/Nicosia, Asia/Tel_Aviv, CAT, EET, Egypt, Etc/GMT-2, Europe/Athens, Europe/Bucharest, Europe/Chisinau, Europe/Helsinki, Europe/Istanbul, Europe/Kaliningrad, Europe/Kiev, Europe/Mariehamn, Europe/Minsk, Europe/Nicosia, Europe/Riga, Europe/Simferopol, Europe/Sofia, Europe/Tallinn, Europe/Tiraspol, Europe/Uzhgorod, Europe/Vilnius, Europe/Zaporozhye, Israel, Libya, Turkey, Africa/Addis_Ababa, Africa/Asmara, Africa/Asmera, Africa/Dar_es_Salaam, Africa/Djibouti, Africa/Kampala, Africa/Khartoum, Africa/Mogadishu, Africa/Nairobi, Antarctica/Syowa, Asia/Aden, Asia/Baghdad, Asia/Bahrain, Asia/Kuwait, Asia/Qatar, Asia/Riyadh, EAT, Etc/GMT-3, Europe/Moscow, Europe/Volgograd, Indian/Antananarivo, Indian/Comoro, Indian/Mayotte, W-SU, Asia/Riyadh87, Asia/Riyadh88, Asia/Riyadh89, Mideast/Riyadh87, Mideast/Riyadh88, Mideast/Riyadh89, Asia/Tehran, Iran, Asia/Baku, Asia/Dubai, Asia/Muscat, Asia/Tbilisi, Asia/Yerevan, Etc/GMT-4, Europe/Samara, Indian/Mahe, Indian/Mauritius, Indian/Reunion, NET, Asia/Kabul, Asia/Aqtau, Asia/Aqtobe, Asia/Ashgabat, Asia/Ashkhabad, Asia/Dushanbe, Asia/Karachi, Asia/Oral, Asia/Samarkand, Asia/Tashkent, Asia/Yekaterinburg, Etc/GMT-5, Indian/Kerguelen, Indian/Maldives, PLT, Asia/Calcutta, Asia/Colombo, Asia/Kolkata, IST, Asia/Katmandu, Antarctica/Mawson, Antarctica/Vostok, Asia/Almaty, Asia/Bishkek, Asia/Dacca, Asia/Dhaka, Asia/Novosibirsk, Asia/Omsk, Asia/Qyzylorda, Asia/Thimbu, Asia/Thimphu, BST, Etc/GMT-6, Indian/Chagos, Asia/Rangoon, Indian/Cocos, Antarctica/Davis, Asia/Bangkok, Asia/Ho_Chi_Minh, Asia/Hovd, Asia/Jakarta, Asia/Krasnoyarsk, Asia/Phnom_Penh, Asia/Pontianak, Asia/Saigon, Asia/Vientiane, Etc/GMT-7, Indian/Christmas, VST, Antarctica/Casey, Asia/Brunei, Asia/Chongqing, Asia/Chungking, Asia/Harbin, Asia/Hong_Kong, Asia/Irkutsk, Asia/Kashgar, Asia/Kuala_Lumpur, Asia/Kuching, Asia/Macao, Asia/Macau, Asia/Makassar, Asia/Manila, Asia/Shanghai, Asia/Singapore, Asia/Taipei, Asia/Ujung_Pandang, Asia/Ulaanbaatar, Asia/Ulan_Bator, Asia/Urumqi, Australia/Perth, Australia/West, CTT, Etc/GMT-8, Hongkong, PRC, Singapore, Australia/Eucla, Asia/Choibalsan, Asia/Dili, Asia/Jayapura, Asia/Pyongyang, Asia/Seoul, Asia/Tokyo, Asia/Yakutsk, Etc/GMT-9, JST, Japan, Pacific/Palau, ROK, ACT, Australia/Adelaide, Australia/Broken_Hill, Australia/Darwin, Australia/North, Australia/South, Australia/Yancowinna, AET, Antarctica/DumontDUrville, Asia/Sakhalin, Asia/Vladivostok, Australia/ACT, Australia/Brisbane, Australia/Canberra, Australia/Currie, Australia/Hobart, Australia/Lindeman, Australia/Melbourne, Australia/NSW, Australia/Queensland, Australia/Sydney, Australia/Tasmania, Australia/Victoria, Etc/GMT-10, Pacific/Guam, Pacific/Port_Moresby, Pacific/Saipan, Pacific/Truk, Pacific/Yap, Australia/LHI, Australia/Lord_Howe, Asia/Magadan, Etc/GMT-11, Pacific/Efate, Pacific/Guadalcanal, Pacific/Kosrae, Pacific/Noumea, Pacific/Ponape, SST, Pacific/Norfolk, Antarctica/McMurdo, Antarctica/South_Pole, Asia/Anadyr, Asia/Kamchatka, Etc/GMT-12, Kwajalein, NST, NZ, Pacific/Auckland, Pacific/Fiji, Pacific/Funafuti, Pacific/Kwajalein, Pacific/Majuro, Pacific/Nauru, Pacific/Tarawa, Pacific/Wake, Pacific/Wallis, NZ-CHAT, Pacific/Chatham, Etc/GMT-13, Pacific/Enderbury, Pacific/Tongatapu, Etc/GMT-14, Pacific/Kiritimati

Saturday, July 11, 2009

JavaOne Notes on EBAY Architecture

Ever wonder how to architect a system with 2 billion pages views and 48 billion sql executes daily? Chief architect of EBay, Randy Shoup, gives his insights at JavaOne 2009.

Monday, June 22, 2009

Homegrown Ubuntu Backups

sbackup on Ubuntu is a nice backup utility, but I needed to copy my backup files across network to a Windows server with a 1TB external drive formatted in FAT32. What sbackup needed was a way to samba mount my external drive, a way to break up files larger than 4GB, and to breakup the backups into nice size chunks that can be copied nicely over my network.

Here's my quick script to backup my /home dir.

#!/bin/sh

BACKUP_DIR=/home/robert

DATESTAMP=`date '+%Y%m%d'`
echo "Backing up Copernicus laptop..."

ls -1 $BACKUP_DIR | while read dir
do
echo "Backing up $dir"
TARFILENAME="${dir}_${DATESTAMP}"
tar cvpjf /var/backup/local_backups/backup_${TARFILENAME}.tar.bz2 --exclude=*.log --exclude=lost+found $BACKUP_DIR/$dir
split -b4GB /var/backup/local_backups/backup_${TARFILENAME}.tar.bz2
done


sudo smbmount //192.168.0.122/copernicus/backup /mnt/backup -o username=YYY,password=XXX

mkdir -p /mnt/backup/backup_${DATESTAMP}

mv /var/backup/local_backups/backup_*_${DATESTAMP}.tar.bz2* /mnt/backup/backup_${DATESTAMP}
echo "Finished backing up Copernicus laptop..."
exit 0

Monday, June 15, 2009

SMF Review

SMF or simple machines forum is a php-based forum software with many features and an active developer community. After searching for several forum software packages, and checking ratings, maturity of the code base, frequency of updates, helpful forums, SMF appeared to be one of the top packages available. Also, my hosting company (GoDaddy) provided free install and setup (another plus!). Pretty much what I wanted, because this wasn't to be a main focus of one of my sites, but I also wanted a forum that was low maintenance, so that I could do other work that was more interesting. SMF also appeared to also have many useful features.

So everything looks good right? Well almost. As a java developer, I learned several things good and bad about php. I also waged a small battle with security. My hats off to the guys that manage LAMP apps full time.

Lessons learned...
  1. Remote file inclusion - PHP without the correct barriers set can allow hackers to send a url that references a remote file. These remote scripts serve different purposes, but in my case a script was used to customize php files and drop in redirects for certain URLs that boost different websites rankings in Google.
    There is a great description about remote file inclusion here. The article also provides a great description and some remedies for this issue.

    Here's the first fix to keep remote file inclusion from being used on my site...

Set allow_url_fopen to OFF
Set allow_url_include to OFF
Set register_globals to OFF


2. Do not allow users to register themselves - As much as I don't want to keep up with users, I soon found that this is the best way to see whats happening on your site after setup especially since I am by no means an SMF expert. After setting up my final SMF 1.1.9 forum, I had 16 spam bots attempt to register users between 1AM and 7AM. The first setup, was to add admin approval for all users. SMF sent me a nice little email that Viagara28 and other creative users were interested in joining. Yeah right!

Allowing users to register themselves will open the doors for spambots, and you will soon have hordes of nefarious advertisements posted on your forum.

One really nice feature about SMF is that with a click you can view and lookup the IP of the requesting member. This helps give you an idea of with whom you are dealing. The 16 member requests were all from spamlands Ukraine and Russia. SMF also allows you to block certain IP ranges if you wish to block out an entire country. Sorry Ukraine!

3. Set the captcha to the highest level of difficulty - This one step filtered out numerous emails from spambots. This article here explains that Captcha can be hacked and should be made as difficult as possible to filter out the bots.

These 3 suggestions kept my SMF forum from being decimated into a heaping pile of ash. Overall, though I believe that SMF/php is a little bit rough around the edges on security, it is by no means a reason to stay away from SMF or php in general. I must note that I did start with SMF 1.1.5 which is the default SMF install from godaddy.com. After reading newsgroups about security enhancements, I decided to go out on my own and install 1.1.9. A good move and all remote file inclusions seemed to be wiped clean by making this move.

So here's my analysis on SMF experiences.

1.) Install - (B+) Overall pretty good install. There seems to be a low barrier to setup SMF which is nice. I didn't quite understand upgrade paths.. can i go from 1.1.5 -> 1.1.9 with a simple upgrade package? but in my situation everything appeared to work. Also, the learning curve on setup was pretty small. I had a few issues installing themes and understanding if a theme would fit 1.1.9 but this issue was soon resolved. Also, you have to go back and remove the php pages for install.php and upgrade.php. It would be nice not to need to do that.

2.) Security - (C) Spambots are ready for SMF, but I think following the suggestions above and upgrading to the latest version of SMF will keep you a few steps ahead of the spammers and hackers. Default settings are prone to hacking, and I believe spambots prey on this fact.

3.) User Community (A-) There is large SMF community out there which in my book is worth more than the app itself sometimes. There was a lot of good information and the developers seem to be actively posting. Good job on this!

Another note on this... SMF allows homegrown pre-packaged themes to be installed which I really liked. You can visit a site and browse many different types of themes, pick one, download it and install it.

4.) Features - (A) Lots of features have been added to SMF. Almost too much really for what I needed, but I was able to navigate through the menus and using IP tracking, style sheet editing, and many more.

5.) Usability - (B) Well this is good and bad. The good is that there are a lot of features. The bad is that there are a lot of features. Overall, I was able to navigate, setup, and configure a site pretty quickly. However, there were times where it took me some time to figure out the right way to do something. One example is setting the current theme. After installing the theme I liked, do I simply change the paths on the current theme? I tried changing the path, but for some reason my site turned into garbled mush and couldn't find URLs etc. I soon found the install theme button, and everything cleared up. There were other small things like that, which kept SMF from becoming stupidly simple to use. Some level of technical knowledge is useful for SMF without a doubt.

Monday, June 1, 2009

PHP and script.aculo.us Web 2.0 Application Interfaces

I just recently finished reviewing PHP and script.aculo.us Web 2.0 Application Interfaces by Sridhar Rao. Sridhar provides many hands-on examples of the Script.aculo.us library and explains in detail many of the effects used in many of our favorite web 2.0 applications i.e. tadalist, digg, delicious, and 43things.com. If you have ever struggled to get JavaScript to play nicely this is a must read.

Sridhar explains the concepts of Script.aculo.us, Prototype, and AJAX in PHP. The chapters are well-written and follow up with many useful examples. The author weaves those concepts into reality and explains how many of the effects that we enjoy on the web are done. My favorite was creating a Delicio.us / Digg -like bookmark application along with many effects such as search with real-time autocompletion, realtime updates, and tag clouds. Another useful chapter describes an AJAX enabled shopping cart that provides awesome features like drag and drop, and applies some of the interesting Script.aculo.us effects to the online store. Very cool.

The author also writes in a fun tone, and gives the reader a lot of interesting tidbits to chew on. A good read overall.

Sunday, March 22, 2009

Designing Java Mock Objects with Commons-VFS

Unit tests and mock objects can get messy when dealing with file objects, but recently I have had the chance to throw in another tool into the old Java toolbox. Commons-VFS allows developers to create a virtual file system, where the files spanning multiple drives/formats/protocols can be made to appear as a single drive accessible through the VFSManager.

Common-VFS allows the developers to span the following file systems and formats...
Commons-VFS is a very useful library with many possible applications. In the following case, I would like to demonstrate using a commons-VFS FileObject in a mock object to simulate a basic runtime file manipulation. This is a bit more realistic than creating a mock object or fakes with no true file changes and is simple and easy to use while only making a virtual file updates in memory.

First, I will create an interface named PACFileHandler. The interface will define the contract for any implementation of the PACFileHandler.








/**
*
*/
package com.pac.ps.file;
/**
* @author robert
*
*/

public interface PACFileHandler {

public void open(String fileName);

public StringBuffer read();

public boolean write(StringBuffer sbf) ;

public boolean append(StringBuffer sbf);

public void close(String fileName);


}


Next, you can create an implementation of this class for runtime. The implementation is not important at this point since we are reviewing commons-VFS with mock objects.

The mock class of the implementation will implement PACFileHandler and will look like this...

/**
*
*/
package com.pac.ps.file;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;

import org.apache.commons.vfs.FileContent;
import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSystemException;
import org.apache.commons.vfs.FileSystemManager;
import org.apache.commons.vfs.FileUtil;
import org.apache.commons.vfs.VFS;

/**
* @author robert
*
*/
public class PACFileHandlerMock implements PACFileHandler {

FileObject jarFile ;

/* (non-Javadoc)
* @see com.pac.ps.file.PACFileHandler#append(java.lang.StringBuffer)
*/
public boolean append(StringBuffer sbf) {
// TODO Auto-generated method stub
return false;
}

/* (non-Javadoc)
* @see com.pac.ps.file.PACFileHandler#open(java.lang.String)
*/
public void open(String fileName) {
try {
FileSystemManager fsManager = VFS.getManager();
String fileString = "ram://" + fileName;
jarFile = fsManager.resolveFile(fileString);
} catch (FileSystemException fse) {
System.out.println("ERROR: " + fse.toString());
}
}

/* (non-Javadoc)
* @see com.pac.ps.file.PACFileHandler#read()
*/
public StringBuffer read() {
StringBuffer fileContents = new StringBuffer() ;

try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
FileUtil.writeContent(jarFile, outputStream);
fileContents.append(outputStream);
} catch (IOException fse) {
System.out.println("ERROR: " + fse);
}

return fileContents ;
}

/* (non-Javadoc)
* @see com.pac.ps.file.PACFileHandler#write(java.lang.StringBuffer)
*/
public boolean write(StringBuffer sbf) {
try {
jarFile.createFile() ;
FileContent fc = jarFile.getContent() ;
OutputStream outputStream = fc.getOutputStream() ;
PrintWriter bw = new PrintWriter(outputStream);
bw.write(sbf.toString());
bw.close();
outputStream.close();
} catch (IOException fse) {
System.out.println("ERROR: " + fse);
}

return true;
}

public void close(String fileName) {
try {
jarFile.close() ;
} catch (FileSystemException fse){
System.out.println("ERROR: " + fse);
}

}

}


The following code from the mock open() method will initialize the commons-VFS File System Manager. Notice, how the file name is referenced. The path to our virtual file will start with "ram://". Other tests could easily be created using the formats discussed earlier, but in this case we will be creating the virtual file in the ram:// path in memory.

FileSystemManager fsManager = VFS.getManager();
String fileString = "ram://" + fileName;
jarFile = fsManager.resolveFile(fileString);


The above mock class will be passed into a PACFile object which will read and write the contents of the file using the PACFileHandlerMock class. In runtime the PACFile object will reference the PACFileHandlerImpl, and our mock object should imitate a true File object without many differences.

/**
*
*/
package com.pac.ps.file;

/**
* @author robert
*
*/
public class PACFile {

PACFileHandler pfh ;

public PACFile(PACFileHandler pfHandler){
this.pfh = pfHandler ;
}

public boolean write(String fileName,String contents){

pfh.open(fileName);
StringBuffer line = new StringBuffer(contents);
pfh.write(line);
pfh.close(fileName);

return true ;
}

public StringBuffer read(String fileName){

StringBuffer line = pfh.read() ;

return line;
}
}

Last, let's create a JUnit 4 test case called PACFileTest...

/**
*
*/
package com.pac.ps.file;

import org.junit.Test;
import junit.framework.TestCase;

/**
* @author robert
*
*/
public class PACFileTest extends TestCase {

/**
* Test method for {@link com.pac.ps.file.PACFile#PACFile(com.pac.ps.file.PACFileHandler)}.
*/
@Test
public void testPACFile() {
PACFileHandlerMock pfhm = new PACFileHandlerMock() ;
PACFile pacFile = new PACFile(pfhm) ;

String fileName = "testFile";
pacFile.write(fileName,"Four score and seven years ago");
StringBuffer sb = pacFile.read(fileName);

assertEquals("Four score and seven years ago",sb.toString());

}


}

Next run the previous unit test...

Output:
Mar 25, 2009 11:45:58 PM org.apache.commons.vfs.VfsLog info
INFO: Using "/tmp/vfs_cache" as temporary files store.


The previous code will create the mock PACFileHandler, and after create and write a virtual file to memory with the contents "Four score and seven years ago".

The file will then be read back and regurgitated back into the unit test assertion for a final check. Many permutations of this example can be made for test cases. Many of the commons-VFS library formats outside of ram:// can be used instead (ftp, http, smb, etc.)

Wednesday, March 18, 2009

IBM in talks to buy Sun

Say it aint so.. IBM is in talks to buy Sun. Southeastern Asset Management, an activist investment group and largest shareholder of Sun is looking for ways to turn a quick profit on its shares of Sun.

Well what better way to make a profit than to carve up their holdings and auction them off to the highest bidder! IBM is apparently the first bidder. HP has also been rumored to have been approached to bid on Sun.

Saturday, February 7, 2009

Self Assembling Robotics

I was reading some information on the Player/Stage project which is a Hardware Abstraction Layer (HAL) and simulator for robotics, and ran into this... enjoy


Monday, February 2, 2009

EasyB and Easiness

John Brothers visited the ATL2G (Atlanta Groovy/Grails User Group) last Wedesday and gave us the details of his new Grails application named "Easiness". Easiness is a Grails application that wraps the new behavior driven design (BDD) framework EasyB.

EasyB, "the best thing since sliced bread", is a domain specific language (DSL) that imitates natural English, and allows analysts to create complex test cases via Stories and Scenarios. Syntax details can be found on the Easyb website http://easyb.org/.

Here's an example of an easy to read test case...

scenario "customers should receive discounts", {
given "a shopping cart with 3 items"
when "a user checks out"
then "they should receive a 10% discount"
}

Easiness is a web framework written in Grails that provides a web interface to create and edit Stories and Scenarios. Easiness provides authentication for company intranets and also allows users to execute test cases from the web front end.

Slides of the presentation can be found here.