Monday, 24 December 2007

BrowserShots - browser testing

Testing websites in all of the different browsers across all of the different platforms is no easy feat, and not always possible. I stumbled across BrowserShots which, given a web address, will produce screen shots of your website as they appear in different browsers on different platforms.

This easily lets you see how your site is rendered. Although there are still other aspects you might want to test, having access to these screen shots is fantastic.
Posted in

Thursday, 6 December 2007

404 with Grails

Grails allows you to specify a custom 404 page simply using the UrlMappings.groovy class:
class UrlMappings {
static mappings = {
"500"(controller:"errors", action:"serverError")
"404"(controller:"errors", action:"notFound")
}
}

This allows you to direct to a controller where you can then forward to a view:
class ErrorsController {
def serverError = {
render(view:'/error')
}

def notFound = {
render(view:'/notFound')
}
}

I started off with a simple view using the 'main' layout:
<html>
<head>
<meta name="layout" content="main" />
</head>
<body>
Page not found!
</body>
</html>

But for some reason, the layout does not get applied. I haven't dug to the bottom of this yet, but I have found a work-around:
<g:applyLayout name="main">
<body>
Page not found!
</body>
</g:applyLayout>

Now the layout is applied and the page looks like I would expect.

The error page for '500' is fine, it only seems to affect the 404 page.

(Grails 1.0-RC1  & Grails 1.0-RC2)

Sunday, 28 October 2007

No SilverLight for Linux?

I've just been to the MLB video site, and it tells me that I'll need to install SilverLight to view the videos! Following the links to install it takes you to the Microsoft site where I find out its only available for Windows and OS X. Searching Google it looks as though the Linux version is still in progress. Guess I'll have to skip the baseball until next year.

Upgrading to Ubuntu 7.10

I've been running Ubuntu on my laptop for a while now - I think I started with 6.10 and then reinstalled when 7.04 came out, and just now I've done a clean install of 7.10. It hasn't gone totally smoothly though. I've had problems with wireless, suspend and hibernate.

I was hoping 7.10 would resolve most of these problems, and I got quite excited when wireless worked straight out of the box. I set the laptop to suspend when the lid was closed, and when I closed the lid, it tried to suspend and failed- when I tried to use it again, it seemed to wake up but then immediately hibernated and powered off.

After resuming the machine, no surprise that the wireless no longer worked. It was the same problem as always, /var/log/dmesg revealing:
[ 17.004000] ipw2200: Radio Frequency Kill Switch is On:
[ 17.004000] Kill switch must be turned off for wireless networking to work.

(My laptop has a kill switch on the left hand side - this turns the wireless off hardware style)

I finally found something that helped here. So simple to get it going really:

  1. sudo modprobe acerhk

  2. echo 1 > /proc/driver/acerhk/wirelessled


My laptop is an ITC Millennia 6100 - I think this is essentially an Acer CL56 (I say that because it came with a CD-ROM labelled CL56 Users manual).

Suspend is still hit and miss. I tried it a few times just yesterday and both suspend and hibernate worked. I wasn't running many applications. However, today after leaving the machine alone for a while I came back to see that when I tried to wake it, suspend had failed and then it went and hibernated.

Oh well, its getting better. Maybe my laptop is too old. I bought it probably 3-4 years ago. I'm planning on moving to a desktop soon - cheaper, more powerful and brand new. I don't need to be super mobile at the moment anyway.

Saturday, 27 October 2007

What to do when your domain class won't save

When developing with Grails and GORM, if a domain class fails validation when calling the save() method, you won't get an exception - save() just returns false! This is mentioned in the Gotchas page and to find out what went wrong, it suggests looking in the errors.allErrors property of your domain class.

To do this, would go something like this:


if(book.save()) {
log.info("Book saved : ${book.title}")
} else {
log.info("FAILED: book save failed")
book.errors.allErrors.each { error ->
println error
}
}


If your book class had a constraint such as:



class Book {
String title static constraints = {
title(maxSize:50)
}
}


and you set the title to be more than 50 characters, you'd see an error message:
<the value of title> exceeds the maximum size of [50]

So, remember to check the boolean returned from save().

You can find out how to enforce constaints on your domain objects in the validation documentation - notice that some of these constraints are marked with 'influences schema generation'.

Saturday, 20 October 2007

Tomcat with Apache2 Virtual Hosts

Using Apache2 with named virtual hosts is standard, but what do you do when you want to put an application on Tomcat behind these hosts?

Thats simple too...

Lets say you have configured two virtual hosts in Apache:

  1. host1.javathinking.com

  2. host2.javathinking.com

and you have two java applications you want running in Tomcat behind these virtual hosts.

To configure Tomcat-5.5.20 with named virtual hosts, update <CATALINA_BASE>/conf/server.xml to look something like this:

<Server port="9281">
<Service name="Catalina">
  <Connector port="9282" proxyPort="80" proxyName="host1.javathinking.com"/>
  <Connector port="9283" proxyPort="80" proxyName="host2.javathinking.com"/>
  <Engine name="Catalina" defaultHost="localhost">
    <Host name="host1.javathinking.com" appBase="webapps/host1">
      <Context path="" docBase="" debug="1"/>
    </Host>
    <Host name="host2.javathinking.com" appBase="webapps/host2">
      <Context path="" docBase="" debug="1"/>
    </Host>
  </Engine>
</Service>
</Server>


Notice how there is a connector for both hosts (using different ports), and each host is configured with context path = "". The applications are deployed to "webapps/host1" and "webapps/host2" but you can change that.

Now, in the Apache configuration for each virtual host I use mod_proxy to forward the requests from Apache to Tomcat using the appropriate port number. Note the security warnings in the Apache documentation about enabling the proxy module.

To enable the proxy modules, use:
sudo a2enmod proxy
sudo a2enmod proxy_http

So inside the virtual host configuration for host1.javathinking.com:
<IfModule mod_proxy.c>
ProxyRequests Off

<Proxy *>
Order deny,allow
Deny from all
Allow from all
</Proxy>

ProxyPass /index.html !
ProxyPass /sitemap.xml !
ProxyPass / http://host1.javathinking.com:9282/
ProxyPassReverse / http://host1.javathinking.com/

</IfModule>

And likewise for host2 but with 9283 as the port number.

Tuesday, 2 October 2007

Developing with Grails and Apache2

In production I use Apache2 in front of Tomcat. The configuration for this is quite simple and standard. However, while developing Grails applications I wanted to run the same configuration, so that there are very few differences between my development and production environments. In Apache, I use named virtual hosts to forward to Tomcat, and Tomcat is configured with web applications corresponding to the virtual host name, with just '/' as the context path.

Grails uses Jetty, and when running 'grails run-app' a Jetty container is configured and started. This is fine for me, I don't think I really need to switch to Tomcat in development, but running Apache2 in front of it would be very nice. This way I don't need to worry about differences in URLs.

I'm not familiar with Jetty at all, so the simplest and fastest way to get this working was to make a copy of the RunApp.groovy script that comes with Grails and change it so that it looks for the context path definition in 'application.properties' in the root of the Grails project.

Update March 7 2008:
See the comments below - I have produced a better implementation:

- appContext is now defined in Init.groovy so it is available to all scripts
- RunApp.groovy and RunWar.groovy use the appContext to configure the server and display the url.
- RunAppHttps.groovy reuses RunApp.groovy so the only change here is for displaying the url.


This is now raised in JIRA as an enhancement.

So, in $GRAILS_HOME/scripts/RunApp.groovy add the following after the 'includeTargets' directive:


def props = Ant.antProject.properties
def appContext = props.'app.context' ? props.'app.context' : grailsAppName
if(!appContext.startsWith('/')) {
appContext = "/${appContext}"
}


Now change the webContext definition:
webContext = new WebAppContext("${basedir}/web-app", "${appContext}")

For completeness, update the StatusFinal event:
event("StatusFinal", ["Server running. Browse to http://localhost:$serverPort$appContext"])

Each Grails project has an application.properties file in the root of the project. If you want to change the context path from the default, define it in this properties file like so:
app.context=/mycontextpath

Now your application can be viewed at http://localhost:8080/<app.context>, or, with Apache in front of it, http://virtualhostname/<app.context>.

In my case I want to set the context path to merely '/' so in application.properties I define:
app.context=/

And with Apache2 configured properly, I view my application at http://virtualhostname/.

Download the modified Grails 0.6 RunApp.groovy here.

Download the modified Grails 1.0-RC1 RunApp.groovy here.

Download the modified Grails 1.0-RC2 RunApp.groovy here.

Download the modified Grails 1.0-RC3 RunApp.groovy here.

Download the modified Grails 1.0 RunApp.groovy here.

Download the modified Grails 1.0.1 scripts here: grails-1.0.1-context-path-mod.tar.gz

Sunday, 30 September 2007

Why are you coding?

There is a good article here about the need to explain why you are coding. I've often found that with most projects or tasks people are reluctant to give you the high level picture - it wastes too much time and, "you don't need to know".

It reminded me of several situations where someone comes to me and says - "I want you to write a program that does this...". I say "sure", expecting that they have done the due diligence and arrived at the conclusion that this is the correct thing to do. (I've even had my knuckles wrapped several times for second-guessing these requests).

So, you proceed with development and somewhere along the line (at the watercooler, elevator or kitchen) you mention what you are doing, and they hit you with "There's no need to do that because system XYZ is being replaced with ABC" or some other equally jaw-dropping statement.

I like to know the bigger picture of where a piece of work fits in, because then I can do a much better job. After all, I'm not just a monkey. And it's not my job to just code. I am supposed to be building solutions. And without the bigger picture this is a lot harder.

Note: I am a advocate for 'project induction' where new team members are taken aside and explained how everything fits together, and how they fit in to the picture. For programmers, this is a technical session (NOT an HR or project management session) and should be run by the architects of the system. This is a good way to discover the talents of your team, and to make them feel involved. I've not had much buy-in though, anyone out there seen it being done effectively?

Wednesday, 19 September 2007

grails-p6spy-0.2

This information is out of date - please see http://grails.org/plugin/p6spy for the current documentation.


Grails 0.6 was recently released, and this new version included changes to the way the DataSource is defined. So, to make my grails-p6spy-plugin compatible with 0.6, here is a new release:

grails-p6spy-0.2.zip


P6Spy lets you monitor the JDBC queries by proxying your database driver. In addition to logging the prepared statements, it also logs the sql with parameters in place so you can copy and paste the exact sql into your favourite database client to test the results.

This Grails plugin makes it easy to utilize P6Spy in your Grails applications.

Introduction


This plugin contains 2 files - the p6spy jar and spy.properties. After installing the plugin in your Grails application, you can find them in:

  • <project directory>/plugins/p6spy-0.2/grails-app/conf/spy.properties

  • <project directory>/plugins/p6spy-0.2/lib/p6spy-1.3.jar


The install script updates your DataSource.groovy to add a commented line containing the p6spy driver class:

  • // driverClassName = "com.p6spy.engine.spy.P6SpyDriver"


Example use


To give the plugin a quick run through, you can either run the following shell script, or work through the step-by-step instructions below.

Shell script:
wget http://javathinking.com/grails/grails-p6spy-plugin/0.2/grails-p6spy-0.2.zip
wget http://javathinking.com/grails/grails-p6spy-plugin/0.2/test/regression/Book
wget http://javathinking.com/grails/grails-p6spy-plugin/0.2/test/regression/BookTests
wget http://javathinking.com/grails/grails-p6spy-plugin/0.2/test/regression/DataSource

grails create-app spytest
cd spytest
grails install-plugin ../grails-p6spy-0.2.zip
grails create-domain-class book
cp ../Book grails-app/domain/Book.groovy
cp ../BookTests test/integration/BookTests.groovy
cp ../DataSource grails-app/conf/DataSource.groovy
grails test-app
tail spy.log

Step-by-Step

Download the plugin from
http://javathinking.com/grails/grails-p6spy-plugin/0.2/grails-p6spy-0.2.zip

Create a new Grails application, add a domain class, and install the plugin:
grails create-app spytest
cd spytest
grails install-plugin ../grails-p6spy-0.2.zip
grails create-domain-class book

In DataSource.groovy, comment out the HSQLDB driver and uncomment the p6spy driver:
// driverClassName = "org.hsqldb.jdbcDriver"
driverClassName = "com.p6spy.engine.spy.P6SpyDriver" // use this driver to enable p6spy logging

Edit Book.groovy and add a property:
class Book {
String author
}

Now update BookTest.groovy to exercise the database:
class BookTests extends GroovyTestCase {
void testBook() {
def book1 = new Book(author:'paul')
book1.save()
assertEquals(1, Book.count())

def book2 = Book.get(book1.id)
assertEquals('paul', book2.author)

def books = Book.list()
assertEquals(1, books.size())
}
}

Now run your tests:
grails test-app

The file spy.log is created, which contains a trace of the sql statements invoked during the test.
06:46:24|17|3|statement||select sequence_name from information_schema.system_sequences
06:46:24|3|3|statement||create table book (id bigint generated by default as identity (start with 1), version bigint not null, author varchar(255) not null, primary key (id))
06:46:29|1|5|statement|insert into book (id, version, author) values (null, ?, ?)|insert into book (id, version, author) values (null, 0, 'paul')
06:46:29|0|5|statement|call identity()|call identity()
06:46:29|3|5|statement|select count(*) as y0_ from book this_|select count(*) as y0_ from book this_
06:46:29|-1||resultset|select count(*) as y0_ from book this_|y0_ = 1
06:46:29|0|5|statement|select this_.id as id0_0_, this_.version as version0_0_, this_.author as author0_0_ from book this_|select this_.id as id0_0_, this_.version as version0_0_, this_.author as author0_0_ from book this_
06:46:29|-1||resultset|select this_.id as id0_0_, this_.version as version0_0_, this_.author as author0_0_ from book this_|
06:46:29|0|5|statement|select this_.id as id0_0_, this_.version as version0_0_, this_.author as author0_0_ from book this_|select this_.id as id0_0_, this_.version as version0_0_, this_.author as author0_0_ from book this_
06:46:29|-1||resultset|select this_.id as id0_0_, this_.version as version0_0_, this_.author as author0_0_ from book this_|
06:46:29|1|5|statement|delete from book where id=? and version=?|delete from book where id=1 and version=0

This file has several columns, delimited by the | (pipe) character. The last column is the most interesting - it contains the SQL with the parameters inlined. More information about configuring P6Spy can be found on the P6Spy website.

Wednesday, 5 September 2007

Changing code in Grails services

I've been using Grails for the last couple of months, and I'm loving it. While looking up some reference information, I stumbled across this gem.

Its always been frustrating when updating services that the server needed to be restarted. Well, this problem has been caused because I've been specifying the type of my services instead of using the def keyword. Now that I'm not specifying the type, no more server restarts!

Saturday, 25 August 2007

Jetty - lost in the web

Grails uses an embedded Jetty instance to serve the application during development. I'm trying to figure out how to configure it with Apache in front using mod_proxy - this means I need to do some Jetty configuration so that when running Jetty it knows it has a proxy in front of it.

It seems though that jetty.mortbay.org is not available on the web (for the last 24 hours I've been getting 'Can't find the server'). Google, however is turning up a lot of documentation that points there. So, its taken a long time to find information not on jetty.mortbay.org.

http://docs.codehaus.org/display/JETTY/Jetty+Wiki seems to be the best reference - it has working links to the binary distributions and source repositories.

Apache 2 mod_proxy - 403 error

I've been trying to put Apache2 in front of my Grails Jetty instance so that I can have common resources served by httpd and not included in my Grails web application. I enabled the proxy module, but always got 403 forbidden errors.

Searching around the web turned up this post, which lead me to check which modules were loaded.

Where I had simply done:
a2enmod proxy

I really need to also do:
a2enmod proxy_http
a2enmod proxy_connect

Of course, after enabling these modules, remember to force a reload:
/etc/init.d/apache2 force-reload

Wednesday, 22 August 2007

Nautilus - stop opening new windows

Nautilus on Fedora has an annoying habit of opening new windows every time you double click on a directory. This is enough to drive any one mad, but there is a solution.

Under the edit menu select preferences. On the behaviour tab click the 'Always open in browser windows'. Not particularly intuitive.

Thanks to Lars E. Pettersson for this tip! Now, Nautilus behaves sensibly and we have access to the directory tree.

Dovecot - Error: No protocols given in configuration file

I have been trying to get Dovecot running on my laptop for development purposes, but something went wrong somewhere. The service never started and running the /etc/init.d/dovecot start script never complained or produced any output.

Just trying to find out what the problem is can be difficult sometimes, never mind finding the solution to the problem. Luckily excellent documentation from the makers came to the rescue.

A wiki entry on logging showed how to find where the logs are:
root@laptop:~# grep "starting up" /var/log/*
/var/log/acpid:[Sun Aug 19 14:05:05 2007] starting up

But no mention of Dovecot in those logs.

The clue came from directly running dovecot:
root@laptop:~# dovecot
Error: No protocols given in configuration file

And sure enough, looking at /etc/dovecot/dovecot.conf showed:
...
# Protocols we want to be serving: imap imaps pop3 pop3s
# If you only want to use dovecot-auth, you can set this to "none".
#protocols = imap imaps
protocols =
...

Setting the protocols property did the trick:
protocols = pop3

Now, I can see it starting in the logs:
root@laptop:~# grep "starting up" /var/log/*
/var/log/acpid:[Sun Aug 19 14:05:05 2007] starting up
/var/log/mail.info:Aug 21 11:52:26 laptop dovecot: Dovecot v1.0.rc17 starting up

Tuesday, 21 August 2007

ProFTPd - Fatal: Socket operation on non-socket

I was trying to set up ProFTPd on Ubuntu recently and for some reason it wouldn't start. Somehow the configuration had been messed up (or something had been). I uninstalled and reinstalled it, but still it wouldn't start.

While trying to fix this problem, I came across this brilliant FAQ:  Chapter 4. Common Running problems. This described all the problems I was having:
3. Unable to bind to port/Address already in use
5. "Fatal: Socket operation on non-socket"

It's great to see such a useful FAQ.
Posted in

Wordpress themes

Looking for Wordpress themes? Try WPThemesFree.com. This site catalogues tons of themes, and has them categorised appropriately (number of columns, colour scheme, topic). You can view the themes against the test database, and there are statistics available for downloads, views, and tests.
Posted in

VMWare Server - Can ping but nothing else between host and guest

I've been using VMWare Server for testing some software and I've found it very easy to use and extremely valuable. This is a free product that lets you create virtual servers using multiple operating systems. Its great, because you can install an OS and then back it up in that state. Then you can make modifications (try out various configurations or software products) while being able to easily revert to a back up at any time.

I'd created a VM on one PC and after a while I transferred it to another host. I thought all was well, until I tried to SSH to it. No joy. I thought something was up with the networking, since I was also using it on another network and had changed from DHCP to static ip addresses.

After a while I figured that:

  1. the host could ping the guest

  2. the guest could ping the host

  3. the guest could see the internet

  4. no other networking (http, ssh etc) worked


Now that I knew what I was dealing with, I quickly found a bug report:
Bug #105697 in vmware-player (Ubuntu)

My host was Ubuntu 7.04 and the guest was Fedora Core 5. I seems there is a problem with TCP (ping is ICMP so that works) - to quote the bug report:
Host and Guest could talk to each other when using non-TCP based services, but when TCP based services(SSH) were used the connection failed:
I believe from looking at the vmware forums, that this can happen with any host operating system if the driver for your network card has some kind of TCP Offload Engine or something like that.

From lspci I can see my ethernet card is a Realtek:
02:01.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL-8139/8139C/8139C+ (rev 10)

I still haven't found a solution to this - the command for turning off TCP Offload Engine doesn't seem to work for the Realtek.

The PHP binary coming with ISPConfig does not work properly on your system!

Ahhh, this old chestnut. I was installing ISPConfig-2.2.5 when I came across this. I'd done a Fedora 5 install as per The Perfect Setup - Fedora Core 5 (at least I think I followed it exactly), but I still came up with this error.

I found this forum entry (see post 13) , and essentially I needed to install bison and flex. I issued this command to make sure I had everything:
yum install make bison flex gcc

I think it was 'bison' that made the difference, because I've actually seen a different error when flex is not installed.

I have to say, Till and Falko (and others?) at HowToForge are fantastic, and have created a great resource on the web. These guys really know their stuff and are very helpful.

Basic Apache2 setup

I just installed Apache 2.2.4 on Windows XP. I needed to set up virtual hosts, so I added the following to C:\WINDOWS\system32\drivers\etc\hosts:
127.0.0.1 server1.localhost server2.localhost
127.0.0.1 www.server1.localhost www.server2.localhost

Now to configure Apache, the virtual host configuration needs to be included in C:\Program Files\Apache Software Foundation\Apache2.2\conf\httpd.conf by uncommenting the line containing:
Include conf/extra/httpd-vhosts.conf

Edit C:\Program Files\Apache Software Foundation\Apache2.2\conf\extra\httpd-vhosts.conf to set up your virtual hosts – In my case I used the following:
NameVirtualHost *:80

<Directory />
Order deny,allow
Allow from all
</Directory>

<VirtualHost 192.168.0.102:80>
ServerAdmin webmaster@server1.localhost
DocumentRoot /www/docs/server1.localhost
ServerName server1.localhost
ServerAlias www.server1.localhost
ErrorLog logs/server1.localhost-error_log
CustomLog logs/server1.localhost-access_log common
</VirtualHost>


<VirtualHost 192.168.0.102:80>
ServerAdmin webmaster@server2.localhost
DocumentRoot /www/docs/server2.localhost
ServerName server2.localhost
ServerAlias www.server2.localhost
ErrorLog logs/server2.localhost-error_log
CustomLog logs/server2.localhost-access_log common
</VirtualHost>

Notice the <directory> directive. By default, everything is denied in httpd.conf, so you need this to allow the files in your docs directory. If you don’t, you’ll see 403 not authorized errors in your browser, and you’ll see something like the following in your error log (C:\Program Files\Apache Software Foundation\Apache2.2\logs\server1.localhost-error_log):
client denied by server configuration: C:/www/docs/server1.localhost/

I’ve put the content for my hosts in
C:\www\docs\server1.localhost
C:\www\docs\server2.localhost

Now I can access my content through
http://server1.localhost/
http://server2.localhost/

The next thing I want to do is redirect from www.server1.localhost to server1.localhost – the extra www is unnecessary.

So in httpd.conf uncomment the rewrite module:
LoadModule rewrite_module modules/mod_rewrite.so

Now add the following to the virtual host definition:
RewriteEngine on
RewriteCond %{HTTP_HOST} !^server1\.localhost [NC]
RewriteCond %{HTTP_HOST} !^$
RewriteCond %{SERVER_PORT} !^80$
RewriteRule ^/(.*) http://server1.localhost:%{SERVER_PORT}/$1 [L,R]
RewriteCond %{HTTP_HOST} !^server1\.localhost [NC]
RewriteCond %{HTTP_HOST} !^$
RewriteRule ^/(.*) http://server1.localhost/$1 [L,R]

Try accessing http://www.server1.localhost/ and see how the browser location changes to http://server1.localhost/

I have a few static resources that I use in several applications on different virtual hosts – things like libraries (Prototype, YUI etc). Rather than uploading them several times, I can put them in a common location and map an alias in each virtual host:
Alias /common /www/docs/common

Now I can access http://server1.localhost/common and http://server2.localhost/common and have them both serving from the same content.

If I put content in versioned directories, then my applications can pick and choose which version they need, and none are forced to upgrade. For example:
/www/docs/common/yui/2.2.0/
/www/docs/common/yui/2.2.2/

Serving this content from Apache in this manner is much more efficient than (for example) including it in my java web applications – when uploading new versions of the applications the WAR is much smaller without all of this static content!

Download Apache from http://httpd.apache.org/download.cgi and view the documentation at http://httpd.apache.org/docs/2.2/

The full vhosts file:
NameVirtualHost *:80

<Directory />
Order deny,allow
Allow from all
</Directory>

<VirtualHost 192.168.0.102:80>
ServerAdmin webmaster@server1.localhost
DocumentRoot /www/docs/server1.localhost
ServerName server1.localhost
ServerAlias www.server1.localhost
ErrorLog logs/server1.localhost-error_log
CustomLog logs/server1.localhost-access_log common

RewriteEngine on
RewriteCond %{HTTP_HOST} !^server1\.localhost [NC]
RewriteCond %{HTTP_HOST} !^$
RewriteCond %{SERVER_PORT} !^80$
RewriteRule ^/(.*) http://server1.localhost:%{SERVER_PORT}/$1 [L,R]
RewriteCond %{HTTP_HOST} !^server1\.localhost [NC]
RewriteCond %{HTTP_HOST} !^$
RewriteRule ^/(.*) http://server1.localhost/$1 [L,R]

Alias /common /www/docs/common
</VirtualHost>

<VirtualHost 192.168.0.102:80>
ServerAdmin webmaster@server2.localhost
DocumentRoot /www/docs/server2.localhost
ServerName server2.localhost
ServerAlias www.server2.localhost
ErrorLog logs/server2.localhost-error_log
CustomLog logs/server2.localhost-access_log common

RewriteEngine on
RewriteCond %{HTTP_HOST} !^server2\.localhost [NC]
RewriteCond %{HTTP_HOST} !^$
RewriteCond %{SERVER_PORT} !^80$
RewriteRule ^/(.*) http://server2.localhost:%{SERVER_PORT}/$1 [L,R]
RewriteCond %{HTTP_HOST} !^server2\.localhost [NC]
RewriteCond %{HTTP_HOST} !^$
RewriteRule ^/(.*) http://server2.localhost/$1 [L,R]

Alias /common /www/docs/common
</VirtualHost>
Posted in

Wednesday, 8 August 2007

Constantly learning what not to do

I'm sitting here looking at a JSP file which is over 5000 lines long. It has a massive amount of Java code and JavaScript scattered through it, along with some custom tag libraries. It generates an HTML file over 1.5 meg in download size.

The workstation I am using is a Pentium D with 1 Gig of RAM, and the tools I'm using can't cope so I'm resorting to simple text editors to make changes.

Once again, I'm spending most of my time at work learning (once again) what not to do. I'm one of the (apparently) few people who know not to do this kind of thing. But this is where I spend most of my time - fixing other peoples mistakes - and unfortunately this is a direct impediment to me learning new, better things and improving myself.

I'm really looking forward to the day a manager says:
"Instead of getting 3 developers who don't know what they are doing to build the system, and then bring someone good in to fix it, why don't we get one good developer to build it and 3 inexperienced developers to maintain it."

This attitude would mean:

  1. The good developer gets to learn and improve, instead of working on problems that should never have existed

  2. The inexperienced developers get to work on well written systems where they might actually learn what should be done


Does anyone actually think we might get there one day?

Does anyone know any companies that employ this strategy?

Until that day, I'll just have to learn 'what to do' after hours at home (few), and try and unlearn 'how not to do it' which I learn during the hours at work (many).

It's a crazy world.

Distributing applications as zip files

I really like just being able to download a program as a zip file, unzip it, and run it. Sure, an installer has some advantages such as adding shortcuts to your menu etc, but I don't rate these as highly as the convenience and simplicity of a 'zip install'.

This is especially true for Java programs which can remain platform independent. I even use the same installation on my dual boot laptop - regardless of whether I'm in Windows or Linux, I can just have the one binary unzipped to a common location.

There is another reason why simplicity wins too - at work the corporate environment is necessarily complex. I just tried installing a program (WinMerge) through an installer and it claimed that 'You must be logged in as an administrator when installing this program'. I am supposed to have administrator rights on this machine. I've run other installers (FireFox) okay. So this installer tried to do something different and apparently it couldn't. Luckily there is a plain old binary zip download for it.

Friday, 29 June 2007

Grails P6Spy plugin

This information is out of date - please see http://grails.org/plugin/p6spy for the current documentation.


I wanted to have a go at creating a Grails plugin so I thought I'd write one to make it easy to add P6Spy to your Grails application. The experience was very good. The structure and distribution mechanism is excellent and the Grails guys have done a great job.

Here's the result:

grails-p6spy-0.1.zip


P6Spy lets you monitor the JDBC queries by proxying your database driver. In addition to logging the prepared statements, it also logs the sql with parameters in place so you can copy and paste the exact sql into your favourite database client to test the results.

This Grails plugin makes it easy to utilize P6Spy in your Grails applications.

Introduction


This plugin contains 2 files - the p6spy jar and spy.properties. After installing the plugin in your Grails application, you can find them in:

  • <project directory>/plugins/p6spy-0.1/grails-app/conf/spy.properties

  • <project directory>/plugins/p6spy-0.1/lib/p6spy-1.3.jar


The install script updates your TestDataSource.groovy to add a commented line containing the p6spy driver class:

  • // String driverClassName = "com.p6spy.engine.spy.P6SpyDriver"


Example use


Download the plugin from
	http://www.javathinking.com/grails-p6spy-0.1.zip

Create a new Grails application, add a domain class, and install the plugin:
 grails create-app spytest
cd spytest
grails create-domain-class book
grails install-plugin grails-p6spy-0.1.zip

In TestDataSource.groovy, comment out the HSQLDB driver and uncomment the p6spy driver:
//   String driverClassName = "org.hsqldb.jdbcDriver"
String driverClassName = "com.p6spy.engine.spy.P6SpyDriver"

Edit Book.groovy and add a property:
class Book {
String author
}

Now update BookTest.groovy to exercise the database:
class BookTests extends GroovyTestCase {
void testBook() {
def book1 = new Book(author:'paul')
book1.save()
assertEquals(1, Book.count())

def book2 = Book.get(book1.id)
assertEquals('paul', book2.author)

def books = Book.list()
assertEquals(1, books.size())
}
}

Now run your tests:
 grails test-app

The file spy.log is created, which contains a trace of the sql statements invoked during the test.
20:07:16|16|1|statement||select sequence_name from information_schema.system_sequences
20:07:16|2|1|statement||create table book (id bigint generated by default as identity (start with 1), version bigint not null, author varchar(255) not null, primary key (id))
20:07:20|0|1|statement|insert into book (id, version, author) values (null, ?, ?)|insert into book (id, version, author) values (null, 0, 'paul')
20:07:20|0|1|statement|call identity()|call identity()
20:07:20|2|1|statement|select count(*) as y0_ from book this_|select count(*) as y0_ from book this_
20:07:20|-1||resultset|select count(*) as y0_ from book this_|y0_ = 1
20:07:20|0|1|statement|select this_.id as id0_0_, this_.version as version0_0_, this_.author as author0_0_ from book this_|select this_.id as id0_0_, this_.version as version0_0_, this_.author as author0_0_ from book this_
20:07:20|-1||resultset|select this_.id as id0_0_, this_.version as version0_0_, this_.author as author0_0_ from book this_|
20:07:21|0|1|statement|delete from book|delete from book


This file has several columns, delimited by the | (pipe) character. The last column is the most interesting - it contains the SQL with the parameters inlined. More information about configuring P6Spy can be found on the P6Spy website.

Thursday, 28 June 2007

How To Forge

If you need to do anything on Linux, make sure you check out HowToForge - this site has some great walk throughs showing how to set up different distributions in different ways. The guys here really know their stuff and have created some fantastic documentation here. They've got guides for installing the major distros with easy to follow instructions – copy and paste command lines - quality stuff. Good work guys, it's great to see such quality available.

Friday, 22 June 2007

Hibernating to disk with Ubuntu

Since moving to Ubuntu 7.04 on my laptop I've had a couple of problems (hibernate/suspend, battery life, wireless) but just recently I did a system update which contained a kernel update. Its now running 2.6.20-16-generic and there seems to be a significant difference in hibernate to disk.

Before it was very hit and miss - sometimes it worked, sometimes it didn't. Now it seems much more reliable - hibernate and resume although it seems to take a while it looks like it works.

There is a strange battery problem though. When I'm running on battery, it is only a couple of minutes before the system starts warning me that I only have minutes left. The battery light on the laptop flashes rapidly and the battery meter in Gnome shows empty. However, if I just carry on working I can still get half and hour or more out of it.

Even stranger is that when I shutdown or hibernate while in this condition, the laptop will not boot again until it is plugged in to the power. I think it genuinely thinks the battery is flat. I can get it to boot if I plug it in to the power just long enough to get through grub and start loading linux. Then I can disconnect the power and continue working on the (not so flat as it thinks) battery.

Sunday, 20 May 2007

Installing Ubuntu 7.04 from ISO image

I decided to do a clean install of Ubuntu 7.04 on my old desktop. I'd downloaded the ISO image some time ago, but it was on my laptop so I used the laptop to burn the image to a CD. The burning went fine and the CD looked good, but the desktop would not boot from it. The desktop has a LITEON DVD burner and a LITEON CD burner, but the CD burner is not powered up.

It turns out the CD would not boot from the DVD drive, but if I put it in the CD drive, it would. I'm not sure why it would matter, since the DVD drive could read the disk, just not boot it.

Unfortunately, after getting the machine to boot and installing Ubuntu to disk, I end up with the same problem described here (an /sbin/modprobe abnormal exit resulting in the boot process hanging). I've got an old (2001?) Gigabyte GA-8SR533 F7d motherboard, and kernel 2.6.20-15 doesn't seem to like it.

Saturday, 12 May 2007

Grails and Eclipse

I've just been lead astray by a little problem with my Eclipse setup, while working on a Grails project. It's documented that when working on a Grails project and using the Groovy Eclipse plugin, you must turn off 'Enable Groovy Compiler Generating Classes' otherwise it will interfere with your Grails project.

While working on a Groovy project I had re-enabled this, and after switching back to my Grails project, I would get this exception when running grails run-app :

Server failed to start:
org.mortbay.util.MultiException
[org.codehaus.groovy.runtime.InvokerInvocationException:
groovy.lang.MissingMethodException:
No signature of method:
Note.save() is applicable for argument types: () values: {}]

(Note.groovy is my domain class)

When I noticed a bunch of Eclipse generated content in the root of my project I quickly remembered this issue.

Since the setting in question is a workspace setting, I think the best solution for me would be to have separate workspaces for Groovy and Grails projects.

Wednesday, 9 May 2007

ACEGI plugin for Grails

As part of my adventures with Grails, I thought I'd try the ACEGI plugin which is described here. If you try the download links though, you'll be a bit disappointed. They just seem to show a Yahoo advert which is in Japanese??

Instead of clicking on the download links, go here http://sky.geocities.jp/acegiongrails/ first and you'll get to a page where you can download them.

There doesn't seem to be anything wrong with the URLs - they are the same as the ones on the page that works, but somehow they behave differently.

Sunday, 29 April 2007

Moving to Ubuntu

I recently installed Ubuntu 6.10 on my laptop (dual boot with Windows XP). I already had my hard disk partitioned in two, C: for Windows, and D: for data. So, I copied D: onto a USB drive and then installed Ubuntu onto what was the D: partition.

Everything went well, and Ubuntu was up and running without any trouble. One most excellent resource I came across was Automatix which not only allowed me to easily install essential software, but it also introduced me to new software which I now consider essential.

However:

  1. while the wireless network card worked immediately after the install, it hasn't worked since the first time I used the hibernate feature.

  2. battery life 'seems' severely reduced


A month or two goes by and Ubuntu 7.04 is released, so I did and upgrade through the update manager. It only took 10mins for me to download the 1Gig of update (at over 1000kbps on my cable broadband connection), but it took about 50 minutes to install.

I was hoping this would fix the wireless connection and power consumption problems, but instead it broke the hibernate feature. So now I had no wireless and couldn't hibernate and a battery that only just lasted the train trip to and from work. I've just now fixed hibernation by following Robin Battey's comment on this blog (it's the 4th comment). However, after resuming my usb mouse doesn't work (this is plugged into a USB hub which in turn has several USB devices attached). I'll have to try this a few more times and see if it stays broken.

I've just now begun looking into the wireless issue. Looking in /var/log/dmesg I see:

[ 20.460000] NET: Registered protocol family 23
[ 20.700000] ipw2200: Radio Frequency Kill Switch is On:
[ 20.700000] Kill switch must be turned off for wireless
networking to work.
[ 20.700000] ipw2200: Detected geography ZZR (14 802.11bg
channels, 0 802.11a channels)


so at least I have a trail to follow now. More on this later, if I make any progress...

Update 1 : I haven't had much time for fault finding this, but I did notice Beagle updating its database - possible source of battery drain - lucky there is a 'Index data while on battery power' option which can be disabled. Maybe this will help. I'll probably just go for a clean reinstall and see what happens. Not something I'm looking forward to though...

Update 2 : Just thought I'd mention that I've also had problems with my USB mouse after resuming. I've got a USB hub with a few things plugged into it and maybe this is causing some grief. I'll have to stop using the hub and see if things improve.

Monday, 23 April 2007

Getting to know Groovy

I've recently become interested in Groovy because of its very cool language enhancements, and ease of use. One of the stated goals is to make 'writing concise meaningful maintainable code easier'. I first used it just to write some simple utility scripts, but it in the future I hope to write some full applications (swing and web based) using it to see just how productive it can be.

To get started, I bought the Groovy in Action book. This is very well written and was easy to work through, and covered so much information I think I'll be going back through it many times.

If you are interested in Groovy, then you should have a look at these resources:

Some more specific references that you'll need once you start playing with Groovy:

As I was working through the book, I wrote my own sample code to test out the new language features and to help get a grasp on the new syntax. In case you are interested, I've included this code below:


/*
Sample Groovy script to demonstrate language features
*/
// semi-colons optional
println('Hello');
println("World")
//
// asserts are enabled by default
println "\n\n### ASSERTS ### "
assert 1==1
//
def value = 2
try {
assert 1==value
assert false
} catch (AssertionError e) {
assert true
}
//
// gstrings
println "\n\n### GSTRINGS ### "
def firstName = "MC"
String lastName = "Hammer"
assert "Hello MC Hammer"=="Hello ${firstName} ${lastName}"
//
String multiline = """This
is a multiline gstring
${lastName} time!"""
assert "This\nis a multiline gstring\nHammer time!"==multiline
//
// everything is an object - no primitives
println "\n\n### OBJECTS ### "
def a = 10
def b = 20
assert a+b==30
//
// notice the language enhancements (java.lang.Number http://groovy.codehaus.org/groovy-jdk.html#cls27)
assert a.plus(b)==30
assert b.minus(a)==10
assert 'java.lang.Integer'==a.getClass().getName()
//
// Ranges
println "\n\n### RANGES ### "
def myRange = 1..5
assert myRange.size()==5
assert myRange.contains(4)
//
// Lists
println "\n\n### Lists ### "
def myList = []
myList += '1'
assert myList == ['1']
myList += ['2','3']
assert ["1","2","3"] == myList
//
def otherList = ['2']
assert ["2"] == myList.grep(otherList)
//
// Closures
println "\n\n### CLOSURES ### "
// standard java (can't use standard for loop)
/*
for(Iterator i = myList.iterator(); i.hasNext();) {
System.out.println(i.next());
}
*/
// standard java (using while loop)
String result1 = ''
Iterator i = myList.iterator();
while(i.hasNext()) {
result1+=(i.next());
}
assert '123'==result1
//
// groovy
String result2 = ''
myList.each { item -> result2+=item }
assert '123'==result2
//
String result3 = ''
myList.each { item -> result3 += item*2 }
assert '112233'==result3
//
class MethodClosureSample {
int limit
//
MethodClosureSample (int limit) {
this.limit = limit
}
//
boolean validate (String value) {
return value.length() <= limit
}
}
//
def MethodClosureSample first = new MethodClosureSample (6) //#1
def MethodClosureSample second = new MethodClosureSample (5) //#1
def Closure firstClosure = first.&validate //#2
def words = ['long string', 'medium', 'short', 'tiny']
// java.util.collection.find(Closure)
assert 'medium' == words.find (firstClosure) //#3
assert 'short' == words.find (second.&validate) //#4
//
// Looping
println "\n\n### LOOPING ### ";
def store = ""
for(x in [1,2,3]) {
store+=x
}
assert store == "123"
//
store=""
for(x in 1..3) {
store+=x
}
assert store == "123"
//
store = ""
(1..3).each { store+=it }
assert store == "123"
//
// CONSTRUCTORS AND PROPERTIES
println "\n\n### CONSTRUCTORS AND PROPERTIES ### "
class MyClass1 {
String name
void setName(String n) {
name = n?.toUpperCase() // NPE safe
}
String toString() {
return "myclass1 -> name : ${name}"
}
}
def c1 = new MyClass1(name: 'MC Hammer')
assert "myclass1 -> name : MC HAMMER" == c1.toString()
assert c1.name == 'MC HAMMER'
assert c1.getName() == 'MC HAMMER' // auto created getter (and setter) only if they don't already exist
c1.setName('BustaRhymes')
assert 'BUSTARHYMES' == c1.@name // direct access to field
//
// protection from Null Pointer Exception
c1.name=null
assert null == c1?.name?.toLowerCase()

Saturday, 14 April 2007

USB Hard Drive enclosure

I've got a couple of PATA harddrives from old computers lying around and recently I needed some extra disk space to shift some things around and for backup. So I bought a USB Hard Drive enclosure (NexStar 3).

Since I have a windows laptop and a Linux desktop, I really wanted to be able to use it with both, so, what disk format to use?

From what I've read, Linux access to NTFS is getting better, but still not 100%. On the other hand, with this nifty little program Ext2 IFS For Windows windows can mount EXT2 volumes. So, I've formatted it as EXT3 (backwards compatible with EXT2) and have successfully used it on both platforms.

I had one little confusing incident, where windows refused to recognise the disk and wanted to format it – but reading the FAQ for Ext2 IFS the author documents how (when using a journalled filesystem such as EXT) if anything is in the journal then windows won't mount the volume since it is mounting as EXT2 (unjournalled). In any case, to get things working again I just reattached it to the linux machine and shutdown cleanly.

Alternatives to the USB enclosure could be a Network Storage device or a hard disk caddy for the desktop. I wonder if anyone makes a USB hard disk caddy?

Monday, 9 April 2007

Copying files between linux machines

Being a Windows user and new to Linux I automatically started looking around for a Linux equivalent of WinSCP. I've just recently installed Linux on my laptop, and when I wanted to browse the file system of my desktop, it suddenly occurred to me that I might be able to just browse it through Nautilus.

It turns out to be really simple in Gnome: just go to 'Places' in the menu bar and select 'Connect to server...'. From here you can pick from several different protocols (including FTP, SSH, Windows share).

I just needed to specify SSH, and supply the desktops details and then I could see the server in the Nautilus tree. Fantastic!

Details : Ubuntu 6.10 (Linux laptop1 2.6.17-11-generic #2 SMP Thu Feb 1 19:52:28 UTC 2007 i686 GNU/Linux)

SoundJuicer and MP3

By default, SoundJuicer doesn't give you an option to extract to MP3. You need to set up an MP3 profile to do this, as described here. Note though, when I tried it I had to restart SoundJuicer before Mp3 turned up in the Output Format list (under preferences).

Details : Ubuntu 6.10 (Linux laptop1 2.6.17-11-generic #2 SMP Thu Feb 1 19:52:28 UTC 2007 i686 GNU/Linux)

Sunday, 4 March 2007

Windows remote control

It helps to be able to remote control another computer - for example if you need to install or fix something on your parents/family/friends computer and you cannot travel to their location.

One-Click VNC makes it easy.

Essentially, the person that is doing the controlling:

  1. If you are using a router with your internet connection you need to set up port forwarding for port 5500 to your computer.

  2. Run 'vncviewer.exe -listen' on your computer.

  3. Send your routers external ip address to the person to be controlled.


Now, at the other end, for the person who will be controlled:

  1. Update settings.ini with the controllers external ip address

  2. Run OneClickVNC.exe.


Now the client talks to the controllers routers external address which forwards to your internal ip address and you are done.See One-Click VNC for the full story...

Note, Windows Live Messenger allows remote controlling of computers you are chatting with (the user can select Actions/Request remote assistance and then pick from any of their online contacts), but I haven't had much luck with it. I'm not sure if it is the router configuration at either end, or just the network connection. I have used it successfully before, when there wasn't a router at the other end.

Saturday, 3 March 2007

Recovering disk space from Oracle

Where has the disk space gone
To find out which tables are the biggest you can run this query:
SELECT
owner, segment_name, segment_type, tablespace_name,
SUM (BYTES / 1024 / 1024) sizemb
FROM dba_segments
GROUP BY owner, segment_name, segment_type, tablespace_name
ORDER BY sizemb DESC

Recovering disk space
Most of the following was learned from this forum post. As tables grow, it can be necessary to compact them to free up disk space. Deleting unnecessary rows won't necessarily free disk space. The space occupied by those rows just becomes available for new rows to consume.

To free up space, you need to truncate (or drop and re-create) the table. If there are constraints referencing some of the rows in the table you want to shrink then you'll have to disable those first.

So, assuming:

  • you want shrink table FOO

  • you've deleted the rows you don't need from FOO

  • table BAR has constraints referencing FOO


-- copy those rows you want to keep into a new table
CREATE TABLE temp_storage_table AS SELECT * FROM foo;
-- disable constaints so you can truncate
ALTER TABLE bar DISABLE CONSTRAINT foo_fk01;
-- truncate which will release the disk space
TRUNCATE TABLE foo;
-- copy the rows you want to keep back into the original table
INSERT INTO foo SELECT * FROM temp_storage_table;
-- re enable constraints
ALTER TABLE bar ENABLE CONSTRAINT foo_fk01;
-- remove temp data
DROP TABLE temp_storage_table;

Try this out on a test table first to make sure everything is working as expected.

Making an ISO file from a DVD

Its really easy to convert a DVD or CD into an ISO file. Its really fast too (10MB/s or 5 mins for 3.6GB).

Use the dd command to create an ISO image:
dd if=/dev/dvd of=/tmp/mydvd.iso

Now, if you want to access the contents of this ISO image you can mount it and access it like a normal DVD player:
mkdir /mnt/iso
mount -o loop -t iso9660 /tmp/mydvd.iso /mnt/iso


To unmount use
umount -d /mnt/iso

If you use this to backup your DVD collection, you can play them back using VLC Media Player. This lets you 'open a disc' and you can use the URL dvd:///mnt/iso to access it like it was in the DVD tray. Very cool! Its nice having the whole disk as one file, which you can mount when you like to access the individual files. No more messing around finding disks and having problems reading them when they get scratched.

You can play straight from the iso (without mounting it) with Xine.

Use:
xine -pfhq dvd:/path/to/iso

Xine can be installed with apt-get install xine-ui

SYSDATE manipulation

I quite often work with database rows that contain audit information which includes the last modified time. Oracle allows SYSDATE addition so that you can add or subtract a number of days (or fraction of days):
select SYSDATE-1 from dual;

would return the current time minus 24 hours. So, if you are in the process of debugging and looking at the database rows you could restrict the select statement to show only those modified by yourself in the last hour:
select * from foo
where
last_modified_date > SYSDATE-(1/24)
and
last_modified_by='bar';


or last half hour:
select * from foo
where
last_modified_date > SYSDATE-(1/48)
and
last_modified_by='bar';

Copy and Paste

Copy and pasting code is a sure sign of a problem that you don't want to have. If you are a development team manager or team leader, then I suggest you set up PMD reports across your code and make use of the Copy/Paste Detector.

Adding these reports to your Maven 2 build is easy, but you can also run it from an ANT build or even through the WebStart client.

In an ideal environment, these reports would be generated and published as part of your continuous integration or nightly build process and would therefore be low maintenance and available at all times.

Keeping an eye on these sorts of reports can help you address issues before they become big problems, which is good for everyone involved. FindBugs is another tool you may want to make use of to keep the quality high.

Burning DVDs with K3B

K3B is a Linux CD/DVD burning application. I was recently using it to burn a DVD ISO image onto a DVD+RW disk, when I came across this error:

/dev/hdd: unable to proceed with recording: unable to unmount
Fatal error at startup: Device or resource busy
Unable to eject media.


This error hadn't occurred the first time I burnt the blank disk (K3B automatically formatted it for me). But this time it turns out that because the disk actually has some content, the DVD drive has been automatically mounted by the OS - and this is now causing problems for K3B. Presumably it can't issue the umount command when I run it because its not running as root.

To resolve this issue, as root, umount the volume:

umount /dev/hdd

Now try burning the disk again, and it should proceed.

Notes:
K3B can be installed on Fedora Linux using:
yum install k3b
Details:
Fedora Core 5 (2.6.16-1.2111_FC5), K3B 0.12.15 (Using KDE 3.5.2-0.2.fc5 Red Hat)