Thursday, 4 March 2010

Setting up WS-Security with Rampart module in Apache Axis web services framework

NOTE: This is from a work document I wrote that I published to Blogger via Google Docs. I'll edit it later, remove Appendices, make it more blog-y and nicer looking, but the info should be useful to someone out there.

Setting up WS-Security with Rampart module in Axis2, for authentication using Password Digest

Instructions

 

These instructions assume that the user is using Axis2 and is packaging their web service into an .aar package

This has been tested using Axis2 1.4.1 - 1.5.1 on JDK 5.

1. Download these:

Apache Axis2 1.4.1 (or latest version)

Apache Rampart 1.4 (or latest version) - security module for Axis2

Bouncycastle Java Cryptography Extensions (JCE) provider for your Java version
http://www.bouncycastle.org/latest_releases.html


2. Install Bouncycastle in JVM, or copy it to Axis webapp's /WEB-INF/lib/

Bouncycastle library is a Rampart dependency, so we need to include the Bouncycastle JCE provider in either the webapp's /WEB-INF/lib or install it in the JVM

2.1 Adding bouncycastle as a security provider in JVM

2.1.1 Add the bcprov-jdk15-***.jar to your service's / client's classpath.
Don't get the file that ends in "-ext.jar" because these ones contain patented, non-free code that is not needed by Rampart.

bcprov-jdk15 is specified by Rampart in its dependency listing but bcprov-jdk16 should also work,  

2.1.2 Add the following line to  file which can be found in JRE's lib/security/java.security directory as the last line.

security.provider.X=org.bouncycastle.jce.provider.BouncyCastleProvider

- "X" is the next unused number available for the listed security provider)

Also see: http://www.bouncycastle.org/documentation.html


3.1 To modify an existing axis2 webapp: 

Copy the *-codegen-*.jar files from Axis installation /lib into your existing webapp's /WEB-INF/lib directory.


3.2 To create a new axis2.war webapp:

You can use the Ant build.xml provided in the Axis2 installation /webapp directory to create axis2.war, provided you make one change: delete the line <exclude name="axis2-codegen*.jar"/> near the end of the file.

Then open a console to the Axis2 webapp directory and run ant.

After the build.xml runs, you can find the created axis2.war Web application in the Axis2 installation /dist directory


4. Copy from Rampart  /lib  to Axis2 webapp /WEB-INF/lib

opensaml-1.1.jar
rampart-core-1.4.jar
rampart-policy-1.4.jar
rampart-trust-1.4.jar
wss4j-1.5.4.jar
xmlsec-1.4.1.jar


5. Rampart needs the library backport-util-concurrent-3.1.jar. It's no longer present in Axis2 1.5 and higher, so for these versions you'll need to get it from:

http://backport-jsr166.sourceforge.net/

or

Axis2 1.4.1  /lib

And copy the jar file to  /WEB-INF/lib of your Axis2 webapp

In case of any further dependency issues with Rampart, see: http://ws.apache.org/rampart/dependencies.html


6. Copy from Rampart /modules to Axis2 webapp /WEB-INF/modules

rahas{version}.mar
rampart-{version}.mar

ie,

rahas-1.4.mar
rampart-1.4.mar

Then add these filenames to /WEB-INF/modules/modules.list , one per line


7. Create a password callback handler that will be used by Rampart to handle authentication. The class must implement the interface javax.security.auth.callback.CallbackHandler.

If multiple services in different code bases or .aar will be using the same authentication functionality, it is recommended that the callback handler be set up in its own jar file then copied to the Axis2 webapp's /WEB-INF/lib.
 

See Appendix for structure of callback handler.



8. Update the services.xml for each web service and add the following xml fragments between the <service> </service> tags.

The following are the WS-Policy to use for authentication using password plaintext and password digest.

 

The <module> and <ramp:RampartConfig> elements must be present when using Rampart, and are only relevant when using the Axis2 framework.


8.1 WS-Policy using PlainText password - username and password will be sent in soap headers, and password sent in plaintext

<module ref="rampart"/>

<wsp:Policy wsu:Id="UsernameToken" xmlns:wsu=
        "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
        xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
      <wsp:ExactlyOne>
        <wsp:All>
          <sp:SupportingTokens
              xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
            <wsp:Policy>
              <sp:UsernameToken sp:IncludeToken=
                  "http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient"/>
            </wsp:Policy>
          </sp:SupportingTokens>

          <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy">
            <ramp:passwordCallbackClass>[REPLACE THIS WITH OUR PASSWORD CALLBACK CLASS, ie, security.package.PWCallBackHandler]</ramp:passwordCallbackClass>
          </ramp:RampartConfig>

        </wsp:All>
      </wsp:ExactlyOne>
</wsp:Policy>



8.2  WS-Policy using PasswordDigest

<module ref="rampart"/>

<wsp:Policy wsu:Id="UsernameToken" xmlns:wsu=
    "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
    xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
  <wsp:ExactlyOne>
    <wsp:All>
      <sp:SupportingTokens
          xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
        <wsp:Policy>
          <sp:UsernameToken sp:IncludeToken=
              "http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient">

            <wsp:Policy>
              <sp:HashPassword/>
            </wsp:Policy>

          </sp:UsernameToken>

        </wsp:Policy>
      </sp:SupportingTokens>

      <ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy">
        <ramp:passwordCallbackClass>[REPLACE THIS WITH OUR PASSWORD CALLBACK CLASS, ie, security.package.PWCallBackHandler]</ramp:passwordCallbackClass>
      </ramp:RampartConfig>

    </wsp:All>
  </wsp:ExactlyOne>
</wsp:Policy>


9. Test that everything is working by configuring plaintext authentication. Plaintext authentication is easier to test with a tool like SoapUI because you can simply cut and paste XML and enter the login details in the header.

 

Testing of password digest authentication will require code of some kind to generate the various elements and calculate the digest before sending the client request.
 

 

Appendix A.

 

About Password Digest authentication


In the case where the communicating parties - the requester and the service - uses an insecure transport channel they should take steps to protect the passwords being exposed to others. Here the requester creates a digest of the actual password concatenated with a set of random bytes (nonce) and another value that is dependent on the creation time (created).


This digest is computed as follows :

digest = Base64_encode(SHA-1(nonce+created+password))

This is the base 64 encoded, SHA-1 digest of the concatenation of the nonce, created and password values.

The requester will send the username, nonce, created, and the digest values in within
the UsernameToken to the service.

 

To authenticate the request the service will compute
the digest value using the password bound to the received usename and will compare the
received digest value and the computed digest value.

 

An example UsernameToken that uses a digest of the password is shown in following figure:

<soapenv:Header>
     <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soapenv:mustUnderstand="1">
        <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="UsernameToken-26752749">
         <wsse:Username>libuser</wsse:Username>
         <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">/Wt/2yDdZwa8a5qd7U70hrp29/w=</wsse:Password>
         <wsse:Nonce>4ZQz5ytME/RXfChuKJ03iA==</wsse:Nonce>
         <wsu:Created>2009-03-17T11:20:57.467Z</wsu:Created>
      </wsse:UsernameToken>
    </wsse:Security>
 </soapenv:Header>

If password in the username token is digested, the password callback handler only needs to provide the password for the client, to allow Rampart to verify it with the password received with the username token.
 

 

Appendix B


Password Callback handler implementation
 

The general structure of the server-side class will be like this:

public class PWCBHandler implements CallbackHandler {

    public void handle(Callback[] callbacks) throws IOException,
            UnsupportedCallbackException {
        for (int i = 0; i < callbacks.length; i++) {
            WSPasswordCallback pwcb = (WSPasswordCallback)callbacks[i];
            String username = pwcb.getIdentifer();
            String password = pwcb.getPassword();

            int usage = pwcb.getUsage();
 

/**

Usage is WSPasswordCallback.USERNAME_TOKEN for a digested password, and WSPasswordCallback.USERNAME_TOKEN_UNKNOWN for a plaintext password or a password of unknown type.

 

*/


            // used when plaintext password in message
            if (usage == WSPasswordCallback.USERNAME_TOKEN_UNKNOWN) {

/**

Verify username and password with existing authentication service
 

eg, authService.authenticate(username, password); 

If authentication fails, you should throw an UnsupportedCallbackException

If password digest is mandatory, then this section should either be
removed or just throw an UnsupportedCallbackException so that requests using plaintext password will fail.

*/
                             
            // when hashed password in message
            } else if (pwcb.getUsage() == WSPasswordCallback.USERNAME_TOKEN) {

    

/**                       
In actual app, this should be replaced by code that will get the password for given username and specify it in pwcb.setPassword()


eg, pwcb.setPassword(authService.getPassword(id));

 

Rampart will take care of calculating the digest and comparing it with the digest sent in the request.

 

This assumes we have a way of retrieving passwords in plaintext.

 

If the auth system only returns password hashes, then client should use also use password hashIn such cases, the client should use same hash method as the auth system, so if the password is only available as an md5 hash, then the client should also generate an md5 hash of the password before calculating the digest.

 

*/


            }
        }
    }
}

Appendix C

 

References:

Web Services Security, UsernameToken Profile 1.0
http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0.pdf
Contains explanation for design of password digest.

Java Web services: Axis2 WS-Security basics
http://www.ibm.com/developerworks/webservices/library/j-jws4/index.html


Password Callback Handlers Explained
http://wso2.org/library/3733


UsernameToken Authentication with Rampart
http://wso2.org/library/240#digest

The sample code for this article is incomplete and does not contain examples for password digest.
 

 

Appendix D

 

Examples of various authentication requests and error responses

 

1. Plaintext – simple XML request

 

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">

    <soapenv:Header>
       <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
                 soapenv:mustUnderstand="1">

        <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
                    wsu:Id="UsernameToken-1815911473">
                          <wsse:Username>client</wsse:Username>
                       <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">apache</wsse:Password>
         </wsse:UsernameToken>
       </wsse:Security>
    </soapenv:Header>
 

   <soapenv:Body/>
</soapenv:Envelope>
 


2.  Password Digest - Bad Request with correct format but incorrect values.
 

Request:


<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">

    <soapenv:Header>

         <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soapenv:mustUnderstand="1">
            <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="UsernameToken-26752749">
               <wsse:Username>libuser</wsse:Username>
               <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">wOaGIASh06nYuDs6RejYky0nsec=</wsse:Password>
               <wsse:Nonce>ole1X/60hjUqbUt3P2ak6w==</wsse:Nonce>
               <wsu:Created>2009-10-28T06:14:55.092Z</wsu:Created>
            </wsse:UsernameToken>
         </wsse:Security>

    </soapenv:Header>

   <soapenv:Body/>
</soapenv:Envelope>

Response:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
   <soapenv:Body>
      <soapenv:Fault xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
         <faultcode>wsse:InvalidSecurity</faultcode>
         <faultstring>The security token could not be authenticated or authorized</faultstring>
         <detail/>
      </soapenv:Fault>
   </soapenv:Body>
</soapenv:Envelope>

 

3. Password Digest - Bad Request with incorrect format, plaintext password
 

Request:


<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">

    <soapenv:Header>

             <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
                 soapenv:mustUnderstand="1">

               <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
                    wsu:Id="UsernameToken-1815911473">
                 <wsse:Username>client</wsse:Username>
                 <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">apache</wsse:Password>
              </wsse:UsernameToken>
            </wsse:Security>

    </soapenv:Header>
   <soapenv:Body/>
</soapenv:Envelope>


Response:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
   <soapenv:Body>
      <soapenv:Fault xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
         <faultcode>wsse:InvalidSecurity</faultcode>
         <faultstring>The security token could not be authenticated or authorized</faultstring>
         <detail/>
      </soapenv:Fault>
   </soapenv:Body>
</soapenv:Envelope>


4. Password Digest - Bad Request, incomplete format, password digest is required, but plaintext is sent instead of digest, and without nonce and createdtime
 

Request:


<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">

    <soapenv:Header>

             <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
                 soapenv:mustUnderstand="1">

               <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
                    wsu:Id="UsernameToken-1815911473">
                 <wsse:Username>client</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">plaintext password</wsse:Password>
              </wsse:UsernameToken>
            </wsse:Security>
    </soapenv:Header>
   <soapenv:Body/>
</soapenv:Envelope>

Response:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
   <soapenv:Body>
      <soapenv:Fault xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
         <faultcode>wsse:InvalidSecurity</faultcode>
         <faultstring>An invalid security token was provided (Bad UsernameToken Values)</faultstring>
         <detail/>
      </soapenv:Fault>
   </soapenv:Body>
</soapenv:Envelope>
 

New YouTube feature - "Turn down the lights"

This is probably the most awesome and stupidly simple feature that I've seen on YouTube. Hover your mouse over the lightbulb and a tooltip comes up saying "Turn down the lights". Click on it and the page background turns dark grey, making it easier for your eyes to focus on the video.

Tuesday, 2 March 2010

WTF? Weblogic has a lower-case class called "service"

While writing up some unit tests, I was trying to get IntelliJ IDEA to
automatically create a variable declaration like such:


request.setService(service);

-- normally, what would happen is IDEA would pick up that no
declaration exists for "service", and base on the parameter type
expected by the method call "setService", it would insert a
declaration for "service" of the expected type.

But this didn't seem to happen as normal, and then I noticed in the
imports list:


import weblogic.jms.interception.service;


I clicked on "service" and was taken to this decompiled stub:


package weblogic.jms.interception;

public static final class service {
private static weblogic.jms.interception.service singleton;

private service() throws
weblogic.messaging.interception.exceptions.InterceptionServiceException
{ /* compiled code */ }

public static void initialize() throws
weblogic.messaging.interception.exceptions.InterceptionServiceException
{ /* compiled code */ }

private void registerWithInterceptionService() throws
weblogic.messaging.interception.exceptions.InterceptionServiceException
{ /* compiled code */ }

private class JMSInterceptionPointNameDescriptor extends
weblogic.messaging.interception.interfaces.InterceptionPointNameDescriptor
{
private java.lang.String title;

public JMSInterceptionPointNameDescriptor(java.lang.String p2)
{ /* compiled code */ }

public java.lang.String getTitle() { /* compiled code */ }

public int getTotalNumberOfUniqueValue() { /* compiled code */ }

public boolean isValid(java.lang.String s) { /* compiled code */ }
}
}

Friday, 12 February 2010

questions automatically piped to /dev/null

"Is there any way you can deliver early some of your web services?
They want us to release in March."

Asked in mid-february. The project is actually planned for production
release after june.

was wondering if i wanted to LOL or groan.

Saturday, 6 February 2010

Just approved a whole list of moderated comments. Oops!

Just noticed that I had comments needing moderation, and some were from early 2009! I approved all of them except for one spammer promoting some outlook shit. Doh! I'm really sorry, if anyone who has posted a comment actually gets to read this.

Thursday, 21 January 2010

EasyMock gotchas

1. Using Easymock matchers when passing in known values to mock classes.

When we get errors like:

java.lang.IllegalStateException: 2 matchers expected, 1 recorded.
at org.easymock.internal.ExpectedInvocation.createMissingMatchers(ExpectedInvocation.java:42)
at org.easymock.internal.ExpectedInvocation.<init>(ExpectedInvocation.java:34)


It means we are missing a matcher for our mocked classes.

Take this code, for example:

Map<String, Object> params = new HashMap<String, Object>();
List<Integer> groupIds = Arrays.asList(1,2,3);
params.put("groupIds", groupIds);

expect(mockSimpleJdbcTemplate
.queryForList((String)anyObject(), params))
.andReturn(results);


Replace the last line with:

expect(mockSimpleJdbcTemplate
.queryForList((String)anyObject(), eq(params)))
.andReturn(results);

"If you would like to use matchers in a call, you have to specify matchers for all arguments of the method call."

Otherwise, it should only be known values.

If you are mixing known values with matchers, you will have to use the eq() ("equals) static method

See: http://www.easymock.org/EasyMock2_2_Documentation.html


2. when mocking classes that are not an interface, like
SimpleJdbcTemplate, we need to use the createMock, replay, and verify
methods FROM org.easymock.classextension.EasyMock NOT FROM
org.easymock.EasyMock


otherwise, we get this error when running the test:

java.lang.IllegalArgumentException: not a proxy instance
at java.lang.reflect.Proxy.getInvocationHandler(Proxy.java:637)
at org.easymock.EasyMock.getControl(EasyMock.java:1440)
at org.easymock.EasyMock.reset(EasyMock.java:1397)


Update 11 November 2010:
You no longer need to do the easymock.classextension package in (2) if you are using EasyMock 3. The package and classes still exist in EasyMock 3 only for backward compatibility. See the documentation.

Saturday, 16 January 2010

SRSLY? My email to my work address bounced because it had the word "SHIT"

"Delivery to the following recipient failed permanently:

<username removed of course>@optus.com.au

Technical details of permanent failure:
Google tried to deliver your message, but it was rejected by the
recipient domain. We recommend contacting the other email provider for
further information about the cause of this error. The error that the
other server returned was: 550 550 5.7.1 Inappropriate language
used:SHIT (state 18)."

Unfuckingbelievable.

I wonder if Telstra do the same to their work emails.

Monday, 11 January 2010

WTF? Error message that has 1st letter removed from each package name.

Got this error message when trying to run a test in IntelliJ IDEA 9.

NoClassDefFoundError: org/pache/ommons/bcp/asicDataSource

I don't really know what's going on, but somehow each of the package
groups has the 1st character missing. Bizarre.

I'll clean out the IDEA cache using "File --> Invalidate Caches" and
see if that fixes it.

Nope, didn't.

Further down the stacktrace it shows the right name:

Caused by: java.lang.ClassNotFoundException:
org.apache.commons.dbcp.BasicDataSource
at java.net.URLClassLoader$1.run(URLClassLoader.java:200)

Thursday, 3 December 2009

Linux desktop adventures: Trying out Loxomo - 'Password Safe V3 compatible Password Vault'

I've been using Password Safe for a while, but my new workplace uses
Linux desktops (fedora 11), so I've found this compatible substitute,
as Password Safe is Windows-only.

http://www.christoph-sommer.de/loxodo/

But first, I had to install wxWidgets, which was easy enough:

sudo yum install wxWidgets

then another 9.5Mb installation later, created a launcher for the app,
pointed to the loxomo.py file, then voila.

The interface seems a bit limited, and you don't have options like
changing the encryption method or the rules it uses for password
generation but it's functional enough. I'll see how it goes in the
next few days.

Friday, 20 November 2009

Linux app install FAIL - TrueCrypt Suse RPM on Fedora 11

Trying to install truecrypt Suse RPM on Fedora 11, as I don't want to
waste my time compiling source. Ironic, cos I end up wasting time
trying to install dependencies anyway!


1. Failed after install attempt. Missing dependencies.

Do you accept and agree to be bound by the license terms? (yes/no): yes

Running package installer...
error: Failed dependencies:
libfuse2 is needed by truecrypt-6.3-0.i586
xorg-x11-libSM is needed by true


2. Hmmm.. okay, try and get dependencies:

sudo yum install libfuse2
Loaded plugins: refresh-packagekit
Setting up Install Process
No package libfuse2 available.
Nothing to do

3. SHIT! I give up.

Friday, 30 October 2009

Monitor SOAP traffic with tcpdump, a crude alternative to TcpMon if you're really desperate

I've started working with SOAP again, using Axis2, and I've been
trying to use the TcpMon application to monitor XML traffic to and
from webservices. However, it performs like shit, and I'm not sure if
it's because of the app or because my machine only has 1Gb.

Anyway, I looked for something more lightweight, and came across a
blog post for tcpdump.

http://mdzyuba.blogspot.com/2008/02/how-to-debug-soap-on-mac-with-tcpdump.html

So to monitor the Axis2 webapp running on Tomcat at port 8080, I type
in this as root (or use sudo! i know i know..)

/usr/sbin/tcpdump -i lo -A -s 8080 -l 'dst host localhost and port
8080' | tee dump.log

this creates a file called dump.log which contains the packets going
over the loopback interface on port 8080


I wouldn't want to do it very often, but in a pinch, it works, and
doesnt take over my machine.

Of course, you'd only want to do it on a machine where *you* control
what requests come in. On a production box the best you can do is
create a log and process it later to find what you want. No humanly
way of eyeballing the logs in real-time in that scenario. =)

Wednesday, 16 September 2009

Recruiters aren't very smart, are they: #1938743982749 in a long series

Why is it that unless you explicitly point out that "I have attached
my resume to this email", a recruiter will not be able to notice the
resume attached, and will instead reply asking you to send in your
resume?

Sunday, 6 September 2009

Safari annoyance: not remembering page history for tabs

This is a common situation for users like me who always have a lot of tabs open:

For one reason or another, you find yourself having to restart Safari.
Maybe AdBlock has updated its adserver list, or you find that Safari's
consuming too much memory and needs a kick in the bum. Whatever. The
nasty nasty thing about this is that you'll now have to work at making
sure all the tabs in all the windows you currently have open are
saved. The usual thing I've done in Safari is to create a bookmark for
each window containing all the tabs I have. Or if I only have one
window open, I just close it and then select "History" then "Reopen
All Windows From Last Session".

So fine, you reopen Safari, but then find yourself going at a
particular tab, "I'd like to go back to the page I was looking at
before I got to this one." But no, you can't! The Back button is
greyed out. I suppose you could look at the History menu and try to
find it there, but the problem is that in History, they are all just
grouped according to the time the site was opened, without any regard
for the particular tab you were on. When you restart Safari, you are
unable to trace the "route" you took to get to a particular page in a
tab.

This is not the case with Firefox and Opera. Now given that they *all*
use internal databases to retain info about the pages they've gone
through, I'd have thought Safari should be able to work similarly to
the other browsers. The question is: does Apple really give a shit?
For some unknown reason, I've actually started using Safari as my
default browser, despite the appeal of having umpteen zillion plugins
available in Firefox. It turns out that that having just the AdBlock
plugin is quite good enough for my normal usage patterns.

Monday, 24 August 2009

General rant on the HP NC6400. Oh, and "Tap To Click" on trackpads suck.

(this was in my Drafts folder, and I don't remember if I have anything
to add to it, so I'll just post it)

I've started bringing the work laptop home and trying to do - uh -
stuff in it. Its a Hewlett Packard HP NC6400, with CoreDuo CPU, 2Gb
RAM, 80Gb hard drive.

A few comments about it:

* The keyboard's not too awful, though I find the keys a bit slippery,
as if coated with Teflon. And yes, it does what it should, but it
still feels and looks cheap.

* It's got a blue "nipple" pointer controller between the G, H and B
keys. It's there, it's weird, and it's staying unused. Never liked it,
and never will.

* The button to turn of the wireless card had me stumped for a few
days, since I had turned it off and forgot what the button was for.
Then I spent an entire weekend trying to figure out why the Windows
system tray icon for the Wireless Network Connection wasn't detecting
any Wi-Fi networks! I didn't get it - this was a new laptop that I
took home the week before, and it detected mine and my neighbours'
networks. It was only when I got back to work that I realised what was
going on: no blue light on the button, no WiFi. A shame that they
didn't have small labels in addition to the button icon, just to make
things clearer.

Having had a Powerbook as my first laptop, I was used to having the
Airport icon in OS X as my main interface to WiFi. Apple laptops don't
have a button for Wi Fi. Actually, they don't have *any* buttons,
aside from the Power button. Okay, there's the Eject CD/DVD button on
the top right, but that's integrated into the keyboard layout.

<#include: usual fanboi rave about how Apple design is elegant,
uncluttered and all that.>

<#include: While mostly true for the hardware design (except that
Super Mouse.."oh, it's got 2 buttons, but we don't actually show it to
you. we like to unclutter it and keep you guessing". fktards.)>

<#digress-heavily: as for OS X... boy, the Finder is such a pitiful
excuse for a file manager. And why the hell is it so difficult to move
a bloody file? Why do I have to copy and *then* delete?>

* The screen is too dim. fn+F9 and fn+10 *look* like they're meant to
control screen brightness but don't have any effect. And coming from a
17" Powerbook, the 14" screen is taking some getting used to. No way
can you have more than one app displayed at a time.

* And then there's the trackpad. By default, two taps in close
succession are treated as a double click. This behaviour has caused me
no small amount of grief.

Saturday, 15 August 2009

Dickhead recruiter conversation #1

Recruiter: So when did you finish work?

Me: End of May, it's in the resume I sent you.

Recruiter: So you've been out of work for FIVE MONTHS!

Me: That's just a bit over two months. (you moron)

Recruiter: And what have you been doing since then?

Me: I've been looking for work. That's why I'm contacting you. (NOOO, i've been working on my tan in the French Riviera - what the hell do you think?)

and the clincher:

Recruiter: Your resume looks pretty good. So why haven't you found a job yet?

(At which point I found myself promising to disembowel him at the first chance I got.)

Friday, 7 August 2009

When it rains.... (job hunting chronicles continued)

Had a bit of a productive week in job hunting! I've had a break in active job hunting for the previous 2 weeks because I was studying for my Java certification exam. Got 70%, (65% was the pass score) so my hard work paid off.

Today, did an interview with a recruiter for a contract role with my old company, but in another division. The project, which is just in
the initial stages, post-design, is to build a workflow system to handle news reports so they go to the website and to the print systems at the same time. Funny enough, another part of the organisation already has something like this in place, so why they don't just build on it, I don't know. Possibly a NIH (not invented here) attitude? Anyway, as long as I get paid. LOL. Interview went well, and recruiter said he'll contact the manager and check if they want my resume sent forward. The other funny thing about this is that a friend just rang me last week and said he's already working there! Fun times if I get in, though it'll be such a bitch having to walk 20 minutes from the train station. Apparently there are buses but they are infrequent.

Around lunchtime, got a call from a recruiter I've been dealing with for about 2 months now, but still no interviews from her. She finally tells me the name of the company she sent my resume to, and it's quite a big one. Makes annoying software that I really haven't installed or used in about 10 years. Antivirus crap that fucks up your system, causes unknown or untraceable problems, resource f*ing hog.

They used to make really cool system utilities back in the DOS days, but their main product now is seen by a lot of people as a total pain in the ass, and one to avoid. But, they've got market share, and people still pay for their stuff. They must be doing something right. Anyway, the recruiter says she'll send me a set of questions to work on during the weekend. Insists that I get it in by Monday, even though earlier in the conversation she says I can work on it at my own pace. Then why the fuck does she say I must hand it in by 4pm Monday, even though I asked if I could just hand it in on Tuesday. What part of "at your own pace" don't you understand, you saucy minx/btch? (she looks fit, reminds me of Riot Becki when her glasses are on, so I still keep in touch)

Then in the afternoon, it was with a more recent former employer. This time, the role is to work on the ad-serving system for the entire media network, which comprised of quite a number of sites. Back to working with my old friend SQL Server! (much nicer than MySQL, at least) It's a humongous task, and I was a bit shocked that at the moment, there are only two developers working on this system. For a system that is used by more than a dozen websites, you'd think it would have a bit more resources devoted to it. Anyway, interesting work, looks painful, exciting, not really that sexy (and everyone hates you so they just AdBlock what you create), but it's a definite money-spinner, so it's on the radar of people who matter, like that old guy who used to be Australian but is now American. Couldn't help noticing that the interviewer kinda gave of vibes of the under-appreciated. Twice he mentioned that "this is the part of the company that never gets mentioned in emails sent to the staff".

Opened my Gmail. Oooh, that really shit hot software house that makes "developer tools that you actually use" have sent me a problem assignment to work on. Yay, my resume didn't get knocked back! I was starting to get worried. I'll have to ace this because this company is pretty much #1 on my wishlist. I've so drunk their kool-aid already.

Then in another email I see the set of questions mentioned by the recruiter. Hmm... can I read .docx files? Oh, good, TextEdit can open them. I start reading and it's nearly as vile as their software. Some of it is bullshit - "name all the Software Methodologies you've worked with." WTF? - some are more reflective of the stuff I might end up working on - "how can you implement a licensing system that does bla bla bla..." and "Provide a high-level system architecture that protects against piracy and misuse." WTF? This is a developer role, right? I'm having my doubts about this company already. I don't really want to work for a company that I can't be proud of. And it's in North Sydney! But then, the company is a big one, and might send me in another direction, career-wise. But all software should be free, right? Hahahhaha...

On Monday, I have a 2nd interview with world-famous software consulting firm. Mainly world-famous because of their chief scientist who wrote a great book about 10 (?) years ago, but I'm not really sure what he does now. This company is #2 on my wishlist, but I'm not really sure if they're as fun as #1. I guess that's why they're #2, hahah. Anyway, it's got these super geeky, quite top notch people, allegedly, and friend of a friend who works there always raves about the company. Maybe it's good. I've invested quite a bit of time already with them - first the phone interview (which was really casual and fun), then stage 2 was the programming problem (killed me for a few days, but got it in, though they didn't get back to me til a month
later), and now stage 3 is a battery of tests and an interview (3 hours in total). I'll have to do some studying on the weekend, just so I don't embarrass myself and look like a dill. Good thing is that it's casually attired.. not a lot of interviews in jeans so far; this'll be my first.

Something's gotta come out of all this!!

Sunday, 21 June 2009

Formatting a hard drive > 32Gb in FAT32

It shits me no end, to think that even now, FAT32 is the best that we
have, in terms of a near-universal file system. FAT32 is good enough,
except for those cases where you have >4Gb files. This will probably
happen more often, now that Virtual Machines are becoming more normal,
and people are dealing with BlueRay extracted media files and DVD ISO
files.

I got a new LG 500Gb portable drive, and now have to format it for use
with my WD TV HD Media Player. It's formatted NTFS by default, but I
have a Mac, so I can't write to the damn thin without installing
something like NTFS-3G, which *should* be stable enough these days,
but I've no experience with it, so I'll do more research before going
NTFS on my external drives. I'll just keep my virtual machines on my
local drive for now. (they're going to be faster that way anyway)

For large drives, previously I've had to go through crap like creating
a small partition because Windows format command only supports drives
up to 32Gb for FAT32, formatting in FAT32, and then expanding the
partition using PartitionMagic.

Someone has written a free utility to format a large drive quickly in
FAT32. Incredible speed - formatted my 500Gb drive in seconds!

http://www.crapcontrol.com/content.php?article.14

Doing our bit to stimulate the economy... buy buy buy!

What an expensive week this has been.

What have we bought?

1. Media Player and
2. 2.5" HDD - Bought this online on Tuesday from mwave.com.au. They
had a sale on the Western Digital WD TV HD Media Player, at around
$175. And I also got an LG 500Gb 2.5" portable hard drive, at $140.
Went to the Mwave offices in Lidcombe (it's local!) to pick it up. The
place is pretty much a warehouse, with a small room for a "public
shopfront". It's not really a retail outlet, since there's no items
there to look at and buy directly. Finally I'll be able to watch the
backlog of DivX files I've had over the past 2 years, and clear out
space on my main computer (only 4Gb available!).

3. Netbook - OMG, the perils of getting a redundancy, having $$$ in
the bank, and being at home browsing those "one bargain a day" sites.
At catchoftheday.com.au on Thursday they had a sale on the ASUS EEE
1000HE laptop, which is a new model with Atom N280 1.6Ghz, 1Gb RAM,
160Gb HDD, Wireless-N, Bluetooth, 10" screen, 92% size keyboard. Very
very nice. Couldn't resist a bargain at $619 (after $40 discount if
you used Paypal)! Current retail is around (according to Shopbot)
"Price range: $669.00 to $934.95 at 55 stores". So I got it, thinking
if it doesn't work out between us - I fear the small screen could be a
problem when working with an IDE - I can always sell it *and make a
profit*, but still way below retail prices!

4. Lanwmower - our GMC lawnmower carked it about a month ago, so got
ourselves to Bunnings on Friday and bought a Flymo lawnmower. I think
it's by Husqvarna, since that's the name on one of the stickers on the
box. Or maybe they're the importers? Anyway, it looks quite
"Scandinavian".. my son said "Big Red Car" when I carried the box
inside. My brother says it looks like a race car. It's the kind of
lawn mower that'd fit in at IKEA, if they sold em there. Was thinking
of getting this other, much smaller lawnmower also from Flymo, but
this was different because it was like *a hovercraft lawn mower*! It
floated on a bed of air, and cut the grass, but didn't have a
container for storing the clippings. I guess the clippings just stayed
on the ground and you had to rake it. Which means I'd have to get a
rake, and do more work? No thanks. Someday, I *will* get a
hovercraft-something. Hovercraft food processor? Hovercraft vegetable
slicing julienne tool. Yeah, someday, my hovacraft will come, boiee..

5. Steam Iron - Also on Friday, we got the Philips GC4420 steam iron
from Good Guys for $95, to replace our steam iron which I stupidly
dunked in a pail of water. People - *never* iron your clothes in the
bathroom. Stupid ironing board had a board cover that was oversize,
and covered that metal tray on the right side where you place the
iron. Anyway, I placed it there, but on its rear, so it was unstable
and fell in the bucket of water. And - this is what I moron you get
when you're sleep-deprived - I almost put my hand in the water to
retrieve the iron! All while it was plugged in to the outlet. Good
thing I stopped and thought, "Yeah, I should probably turn off the
power outlet first!" By which time the safety switch kicked in anyway
and all the electricity in the house turned off. So got on the forums
- thanks whirlpool.net.au forums - and word on the street is that
Tefal and Philips are the brands to go for. Also learned about this
extremely expensive - but allegedly excellent - brand called
LauraStar. Holy shit, their "systems" can cost up to $1400!! The irons
themselves are selling for $500+, after heavy discounting already. I
don't wanna know the "normal" price. Anyway, another brand name I can
fake and claim that I own, just like Lexus, Miele, Dyson and Apple.
Oh, I do have the latter two. Anyway, it has auto-stop (so it turns
off if you accidentally forget it lying around) and anti-calc system
(so you don't get that yucky particulate flakes coming out of the
steamer when you iron, thereby fucking up your just ironed clothes),
which are the most important features for me. Also had to be light,
since my mother-in-law does an unfair majority of the ironing around
here, so that kicked the GC 4630 out of the running (that had the
Ionic Steam feature). And this model also has Steam boost, so giving
out 100g instead of 40g of steam. Awesome.

6. Running shoes - Friday too, wife and I bought new running shoes at
Paul's Warehouse, Homebush. The bargains are not that extreme as the
ads make it out to be. Tons of stuff were selling for $100+ anyway.
How the fuck can they justify these prices for running shoes? It's not
like they now have cpus and memory foam and teflon. My old ones are 4
yrs old anyway, and looking ratty, so was a good time to get. Total
cost - $170.

All through the week I've been checking out deals on the iPhone
(unlimited broadband for $50 on Optus... please please) and the Nokia
E63 on 3 ($29/mo for 12 mos.. wow). Must forget all this and realise
I've survived this far without this anyway.

Okay, economy, get better!! I just paid you to do so. =)

Wednesday, 17 June 2009

500 Days of Summer - International Trailer

I've just fallen for Zooey Deschanel. Who wouldn't?

I'm really looking forward to this film! :) Nothing's made me feel like this in a while.

Tuesday, 16 June 2009

Rapid SVN for OSX - doesn't work at all

RapidSVN 0.9.8 for OSX doesn't work at all. Running on OSX 10.5.7 and can't even run it. What a piece of shit.

Friday, 12 June 2009

Removing .svn directories and getting "Operation not permitted" on OSX

I had some code that I'd checked out, but now wanted to check back
into another repository. The cleaner way to do this would have been to
export the directories I wanted, so that .svn directories didn't get
created. But I no longer had access to the original repositories, so I
have to remove the .svn directories recursively.

And I found this bit of shell scripting would work on OSX (currently
on 10.5.7) after moving to the top of the directory tree where i
wanted to delete .svn:

find . -name .svn -exec rm -rf {} \;

Source: http://codesnippets.joyent.com/posts/show/104

most of the time.

Except when I would get "Operation not permitted" with some directory
trees. Turns out these files under .svn had the immutable flag set on
them, and we have to use the 'chflags' command with the 'nouchg' to
remove the immutable flag.


chflags -R nouchg [name of directory containing .svn directories]


eg: chflags -R nouchg MyProject/


Source: http://www.osxfaq.com/tutorials/learningcenter/AdvancedUnix/ugp2/page2.ws

Yay!

Tuesday, 12 May 2009

Spring FAIL - incomplete documentation on JibxMarshaller from spring-oxm library

This issue is prevalent in a lot of open source projects: inadequate
documentation and non-existent examples.

<bean id="jibxMarshaller"
class="org.springframework.oxm.jibx.JibxMarshaller">
<property name="targetClass" value="au.com.company.JOB"/>
<property name="indent" value="4" />
<property name="bindingName" value="jibx_binding" />
</bean>


The javadoc for setBindingName() in JibxMarshaller simply says:

/** Sets the optional binding name for this instance. */

There is no indication what it actually means - is the bindingName the
same as the binding file? Should I be indicating the location of the
file??

I try this:

<property name="bindingName" value="classpath:jibx-binding.xml" />

and I get

Caused by: org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'jibxMarshaller' defined in class path
resource [batchContext.xml]: Invocation of init method failed; nested
exception is org.springframework.oxm.jibx.JibxSystemException: Binding
'classpath:jibx-binding.xml' not found for class
au.com.careerone.xmlbinding.adicio.JOB; nested exception is
org.jibx.runtime.JiBXException: Binding 'classpath:jibx-binding.xml'
not found for class au.com.careerone.xmlbinding.adicio.JOB
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1337)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:473)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory$1.run(AbstractAutowireCapableBeanFactory.java:409)
at java.security.AccessController.doPrivileged(Native Method)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:380)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:264)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:221)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:261)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:185)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:164)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:269)
... 32 more
Caused by: org.springframework.oxm.jibx.JibxSystemException: Binding
'classpath:jibx-binding.xml' not found for class
au.com.careerone.xmlbinding.adicio.JOB; nested exception is
org.jibx.runtime.JiBXException: Binding 'classpath:jibx-binding.xml'
not found for class au.com.careerone.xmlbinding.adicio.JOB
at org.springframework.oxm.jibx.JibxMarshaller.afterPropertiesSet(JibxMarshaller.java:137)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1368)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1334)
... 42 more
Caused by: org.jibx.runtime.JiBXException: Binding
'classpath:jibx-binding.xml' not found for class
au.com.careerone.xmlbinding.adicio.JOB
at org.jibx.runtime.BindingDirectory.getFactory(BindingDirectory.java:180)
at org.jibx.runtime.BindingDirectory.getFactory(BindingDirectory.java:197)
at org.springframework.oxm.jibx.JibxMarshaller.afterPropertiesSet(JibxMarshaller.java:130)
... 44 more


I then tried putting in the mapping name, even though it doesn't
really make much sense:


<property name="bindingName" value="JOB" />

Still no dice.

I look in the source for JibxMarshaller, and find this line where
bindingName is used:

bindingFactory = BindingDirectory.getFactory(bindingName, targetClass);

I look in the Jibx class BindingDirectory, and find:

public static IBindingFactory getFactory(String name, Class clas,
ClassLoader loader) throws JiBXException {
String list = getBindingList(clas);
String match = GENERATE_PREFIX + name +
BINDINGFACTORY_SUFFIX + '|';
int index = list.indexOf(match);


where GENERATE_PREFIX and BINDINGFACTORY_SUFFIX are:

/** Prefix used in all code generation for methods and classes. */
public static final String GENERATE_PREFIX = "JiBX_";

/** Suffix of binding factory name. */
public static final String BINDINGFACTORY_SUFFIX = "Factory";


Then I look around in my /target directory (I am using Maven), and see
that there is a class called:

JiBX_jibx_bindingFactory.class

Opening this in Notepad++, I find a string:


au/com/careerone/xmlbinding/adicio/JiBX_jibx_bindingFactory

So it looks like Jibx first converts the name of the binding file into
Java class format, and the '-' in jibx-binding becomes an underscore.

So we get: jibx_binding

But was this documented anywhere? No!

This Jibx bug helped me figure it out:

https://svn.sxc.codehaus.org/browse/JIBX-210

Oh well.

Monday, 27 April 2009

Maven: replace a shell script with a Groovy script run by gmaven-plugin

I've never even heard of the gmaven-plugin before and just though this
looks quite awesome.

Text below is just cut and pasted from the Maven mailing list.

================================================================

PROBLEM:

At a customer site there is a custom, company-wide dictionary available for
spellchecking. This dictionary is managed in an proprietary application from
where you can export it. For the webapp we're building we need to transform this
dictionary into a very simple format: a single file with one
dictionary entry per line. The export format is somewhat special as
its spread over a bunch of files (one for each letter of the
alphabet), contains additional syllabication info, which we don't need
and also has some comments that have to be removed. The specifics of
the format aren't really that important here though...

After some testing I came up with the following short bash-script that fullfills
all my needs:

8<-----------------------------------------------------------
tmp_folder=target/dict
cls_folder=target/classes
mkdir -p $tmp_folder
mkdir -p $cls_folder

cat src/main/dictionary/*.lst > $tmp_folder/tmp1.dict
sed "s/[~?]//g" $tmp_folder/tmp1.dict > $tmp_folder/tmp2.dict
sed "s/ .*$//g" $tmp_folder/tmp2.dict > $tmp_folder/tmp3.dict
sort -u -o $cls_folder/my.dict $tmp_folder/tmp3.dict
8<-----------------------------------------------------------

(In other words: Take all files src/main/dictionary/*.lst, concat them into one
single file, match some strings with simple regexes and remove those, and
finally sort the dictionary entries and remove all duplicates.)

This script is then called from within maven with exec-maven-plugin. Afterwards
maven-jar-plugin wraps the file in a simple jar, so the dictionary can
be easily consumed in Java using
getClassLoader().getResourceAsStream().

Now all is well & nice and this script even performs sufficently given about 1.6
million dictionary entries (~38MB). But of course it's not really the
Maven way to do things, especially because it's not portable. You need
to have some kind of Unix-like enviroment in place for this script to
work.


SOLUTION:

Assuming the dictionary source files are already broken down by letters
of the alphabet, then the following 5 lines of code does most of it.
(Note that the sed scripts are pretty close to just a line by line
trim() and, of course, you may need to sort the file names.)

<plugin>
<groupId>org.codehaus.groovy.maven</groupId>
<artifactId>gmaven-plugin</artifactId>
<executions>
<execution>
<phase>generate-resources</phase>
<goals>
<goal>execute</goal>
</goals>
<configuration>
<source>
<![CDATA[
new File( "target" ).mkdirs();
def resultfile = new File( "target/dictionary" );
new File("src/main/dictionary").eachFileMatch(~/.*\.lst/){ file ->
file.readLines().sort().each(){resultfile << it.trim() + "\n";}
}
]]>
</source>
</configuration>
</execution>
</executions>
</plugin>

Saturday, 18 April 2009

Wednesday, 18 March 2009

Gmail fixes "mark as unread" annoyance - you can now mark individual messages instead of the whole thread

Great to see that Google have fixed that issue where "Mark as unread"
marks *the whole thread* instead of just the specific message that you
wanted to mark as unread. I'm just surprised it took so long to get
it fixed.

http://gmailblog.blogspot.com/2009/03/small-but-helpful-change-to-mark-as.html

Tuesday, 17 March 2009

Jaxb notes: specifying a package for a classes generated from a schema

Use the <package> element of the <schemaBindings> binding declaration
to define the package name for the schema.

For example, the following defines the package name for all JAXB
classes generated from the simpleservice.xsd file:

<jaxb:bindings
xmlns:xs="http://www.w3.org/2001/XMLSchema"
schemaLocation="simpleservice.xsd"
node="/xs:schema">
<jaxb:schemaBindings>
<jaxb:package name="examples.jaxb"/>
</jaxb:schemaBindings>
</jaxb:bindings>


Taken from http://edocs.bea.com/wls/docs103/webserv/data_types.html#wp223112

Saturday, 7 March 2009

Getting a replacement power supply for Western Digital external drives - easy as pie

I very rarely get to actually praise a company for doing something beyond my expectations of service so here goes.

I've owned a 500GB WD Elements external hard drive for a little more than a year, and it stopped working about a month ago. I suspected the power supply was faulty, because I would plug it in, it would light up for half a second, then die out.

I was running out of disk space on my computer so I finally got around to looking for all the paperwork for it to make a warranty claim - the receipt, the warranty documents, etc. I went online, registered the product, adjusted the warranty dates by sending them a photo of my receipt, since there were a few weeks between the time of manufacture and the time of sale.

Then I rang Western Digital support and told them about my problem. The conversation went something like this:

Me: I've got a problem with the power supply of my external hard drive. It lights up for a moment after plugging in then goes out.

WD support: So what is the plug for your power supply. Is it 5-pin or something else?

Me: 5 pin. (I'd read on their support site that models made after sometime in 2008 used a different power supply and pin)

WD support: Okay, give me your name and address. You should get it in about two weeks.

Me: Do you need my registration number, or the model of hard drive I have.

WD: No, it doesn't matter what size of drive, it's just the plug that's different.

So they didn't even check if I actually had a registered drive. I could have been anyone or the item could have been out of warranty and would still have received a replacement power supply! Not that I'm complaining. Probably the chances of people doing that are quite unlikely anyway, so they decided to simplify the procedure.

The power supply arrived yesterday, Friday, 6 March. It was sent by FedEx from Singapore on 3 March. No questions, no rego check, no hassles. Just send it out when requested. WD support are f*king excellent!







Saturday, 8 November 2008

Re-ordering songs in a playlist in iTunes - you first need to click on the playlist number (1st column)

I haven't really had much to complain about when it comes to iTunes, until now.

I was preparing a CD songs for my son, and I had the songs in a playlist. The first group of songs I added was from a single CD, so that was easy enough. I added a few more songs later, and discovered that changing the order of the songs in a playlist can be a bit puzzling. Trying to drag around a song in the list, which normally you'd expect to be enough to change the sequence, had no effect. Googling it reveals that it doesn't work when the list of songs is already sorted on a column other than the first one, which is the list number. However, there is no indication anywhere in the interface that this restriction is in place!

Now from an implementation point of view, I can see why they don't allow it. For example, if the list of songs is sorted by Name, in ascending alphabetical order, allowing the songs to be dragged around and resequenced would be inconsistent with what the table widget is meant to
be doing.

But this is a totally inadequate user experience! If you can't drag and drop the songs while it's sorted on any of column after list number, then why couldn't they tell that to the user? There should be a drag/ drop event detectable from the GUI toolkit, so why couldn't they put in a condition like:

IF (song is being dragged and dropped) AND (songs are sorted on any
column from 2 onwards) THEN
SHOW MESSAGE "Click on 1st column before changing the order of the songs"

Unfortunately, iTunes has worked like this for a while (and so have complaints about it), so I don't think Apple will be making this operation a bit easier.

So to change the order of songs in an iTunes playlist, click on the first column to cancel out any other sorting on the list, before dragging the songs to the desired sequence.




NOTE: Make sure that the shuffle button in the bottom left is not clicked. (thanks Andrew!)

Thursday, 30 October 2008

Xpath info - don't use "/" at the end of a xpath query

Xpath info - don't use "/" at the end of a xpath query, otherwise
you'll get this error:

javax.xml.transform.TransformerException: A location step was expected
following the '/' or '//' token.


http://p2p.wrox.com/topic.asp?TOPIC_ID=8429

"A path expression never ends in "/", except for "/" used on its own
to refer to the root node."

Thursday, 11 September 2008

O'Reilly Maker




http://www.oreillymaker.com/link/19781/groping-for-admins/


I'm pretty sure I've come across it before but didn't have the requisite brain cells to come up with something.

The things you do at 2 in the AM....

Sunday, 20 July 2008

Problem upgrading firmware on Billion 7300G ADSL Modem? Try using Firefox. + Hidden config pages.



I was trying to upgrade my Billion 7300G modem to firmware ver. 1.36 using my iMac, and kept getting up to a certain point and then bomb out around at the 24% mark and return an alert/dialog box that just had:

http://192.168.1.254
Error!


Great, talk about informative error messages!

After a bit of Googling, I found out that this problem only occurs when performing the upgrade from Safari, and Billion have supposedly promised to fix this. I But there haven't been any more firmware updates after ver. 1.36 anyway. The only solution is to use another browser like Firefox.


On another note, I've also sorted out my issues with my wi-fi at home simply by boosting my wireless signal.

My modem/wifi router is in a room on the second floor, while my main computer, a 24" iMac, is downstairs. Now the problem was the iMac would keep losing the wi-fi connection when it came back from Sleep mode. After this any open tabs in Safari would no longer work, and the only workaround I've had was to turn off Airport, then turn it back on again.

The Billion has some hidden configuration pages, and going to http://192.168.1.254/wireless_txpower.asp shows the Wireless signal is only transmitting at 70% strength! Setting the value to 100% sorted out my issues.

Google's Blogger widget for Mac OSX is rubbish

I wanted a quicker way of making posts to Blogger, so I downloaded Google's Blogger widget. Absolute crap. You can't even put a link or an image. All you can do is enter plain, bold and italicised text.

The only thing I found interesting was that it encodes punctuation marks so you end up with &#39; instead of the (') character. But that's probably Blogger itself, and not the widget making those changes.

WTF? It's 2008, guys. I'm sure you can do better than this!

I've never been so insulted by Google Ads!



So how are we meant to describe musicthing's audience, based on these two ads? "Overweight and buys a lot of games and DVDs"?

Quotes from Anchorman: The Legend of Ron Burgundy

It all started with me trying to clean up my hard drive, remove some movie files taking up precious space. Then I started watching Anchorman: The Legend of Ron Burgundy. I wrote down one quote that I thought was cool (the one about "I have many leather bound books..") . Then another. And another. After about two hours I decided I'd better just post what I have, then do another post later. I wonder if there's already a Ron Burgundy quotes app on Facebook? The world needs one!

Yes, I realise this has probably all been done before, and I this is all wasted effort and I should've Googled it first... but what can I say? I felt the urge! It was a labour of love. :P

"I know what you're asking yourself - and the answer is yes, I have a nickname for my penis. It's called The Octagon. But I also nicknamed my testes. My left one is James Westfall, and my right one is Dr Kenneth Noisewater. You ladies play your cards right you might just get to meet the whole gang."

"People seem to like me because I am polite and rarely late. I like to eat ice cream and enjoy a nice pair of slacks. "

"We've been coming to the same party for twelve years now, and in no way is that depressing."

"I don't usually do this, but I felt compelled to tell you something. You have ... an absolutely ... breathtaking ... heinie. I mean, that thing is good. I wanna be friends with it."

"I'm very important. I have many leather-bound books and my apartment smells of rich mahogany."

"(to Baxter the dog)You're so wise. Like a miniature Buddha, covered in hair."

"(to Baxter the dog) You know I don't speak Spanish. In English, please."

"(to Baxter the dog) You pooped in the refrigerator - and you ate a whole wheel of cheese? How'd you do that?"

"Well, I could be wrong, but I believe Diversity is an old, old wooden ship that was used during the Civil war era."

"It is anchor-MAN, not anchor-LADY, and that is a scientific fact!"

"It's terrible! She has beautiful eyes and her hair smells like cinnamon!"

"I read somewhere that their periods attract bears. The bears can smell the menstruation."

"Hey! Where did you get those clothes? At the... toilet store?"

"I will smash your face into a car windshield, and then take your mother Dorothy Mantooth out for a nice seafood dinner, and never call her again!"

"You know those ratings systems are flawed. They don't take into account houses that have more than two television sets."

"Can't say one word? Even the guy who can't think says something, and you guys just stand there?"

"But I think my son is just going through a phase. I have no idea where he would've gotten hold of German pornography."

"But you and I are mature adults who've both seen our share of pornographic materials - oh, you never have? Of course you haven't, how stupid of me, neither have I. I was just speaking in generalities.... I'll stop by the school later Sister Margaret."

"What do you say we go out on a date? Have some chicken, maybe some sex. You know, see what happens."

"It's called Sex Panther, by Odeon. It's illegal in nine countries. Yep, it's made of bits of real panther. So you know it's good."

"They've done studies, you know. Sixty percent of the time it works every time."

"Its smells like a used diaper filled with indian food!

What is that? Smells like a turd covered in burnt hair!

Smells like Bigfoot's dick!"

"I would like to extend to you an invitation to the pants party....

The party.. with the pants....

Party with pants..."

"The only way to bag a classy lady is give her two tickets to the guns show... (kisses 'guns').. and see if she likes the goods"

"But now I am too hurt.. and shocked.. and offended.. and... hurt!"

"Veronica: Mr Burgundy, you have a massive erection.

Ron: Yes... I do... I'm sorry... it's... the pleats. Essentially an optical illusion... the pattern on the pants it's not flattering in the... the crotchular region. I'm actually taking them back right now."

"This is a mistake, he's very cute... No he's not, he's not, he's hairy."

"San Diego - drink it in, it always goes down smooth. It's a fact, it's the greatest city in the history of mankind. Discovered by the Germans in 1904, they named it San Diago, which, of course, in German means 'a whale's vagina.'"

"Ron: I don't know what it means. I'll be honest, I don't think anyone knows what it means. Scholars maintain that the translation was lost hundreds of years ago.

Veronica: Doesn't it mean Saint Diego?

Ron: No."

"We have a saying in my country about people like him - 'The coyote of the desert always likes to eat the heart of the young, when the blood drips down to the children for breakfast, lunch and dinner, and only the ribs will be broken in two.'"

"Well, I have one great passion that lives deep within my loins like a flaming, golden hawk."

"Wait.. what if just for tonight we weren't co-workers - we were just co-people.

You be a woman. I'll be a man. That's all."

"Veronica and I are trying this new fad called jogging. I believe its 'jogging' or 'yogging', it might be a soft 'J', I'm not sure. But apparently you just run for an extended period of time."

"I know that one day Veronica and I are gonna get married on top of a mountain. And there's gonna be flutes playing, and trombones and flowers and garlands of fresh herbs. And we will dance til the sun rises, and then our children will form a family band. And we will tour the countryside and you won't be invited!"

"It seems our youngest, Chris, was on something called 'acid', and was firing a bow and arrow into a crowd. You know how kids are."

"This city needs its news, and you're gonna deprive them of that because I have breasts? Exquisite breasts?"

"I'm in a glass case of emotion!"

"Why are you being this way? Why can't you just be proud of me as a peer and as my gentleman lover?"

"Veronica: You have man boobs.

Ron: You've got a dirty whorish mouth. I'm gonna punch you in the ovary, that's what I'm gonna do.

Veronica: Jazz flautist for little fairy boys.

Ron: Okay, you know what - that's uncalled for! I can't work with this woman."

"Ron: There's only one thing a man can do when he's suffering from a spiritual and existential funk.

Champ: Go to the zoo? Flip off the monkeys?

Ron: No. Buy new suits.

All: YAAY!!!"

More to come once I get the urge to surge. No, I don't know what that meant.

Let's end this post on a high note. I wanna talk about love.

"Brick: I love... carpet. I love... desk.

Ron: Brick, are you just looking at things in the office and saying that you love them?

Brick: I love lamp.

Ron: Do you really love the lamp or are you just saying it because you saw it?

Brick: I love lamp. I love lamp."

You stay classy, Information Superhighway.

Thanks for stopping by.

Saturday, 19 July 2008

Video interview with Adrian Utley of Portishead at sonicstate.com

Okay, so I really found this via good ole musicthing, but the more links to the interview the better! Still haven't decided whether I like Portishead's "Third" enough to get the CD.

Part 1:



Amazingly, his daughter actually sits still for this entire interview. I wonder if she's developed an affinity for modular synths by now. Bonus points for a really gritty sounding version of "I'm a little teapot". :P


Part 2:



More gear - mixing desks, drums

Part 3:



Mikes galore! And the el cheapo guitar used on "The Rip".

Monday, 26 May 2008

Java code to concatenate values from get__ methods of a class dynamically

Ended up discarding this code from a project, but this was a good
exercise to learn a little about Java reflection methods.

This was based on code from HashCodeBuilder in Apache commons-lang,
which can generate hashcodes via reflection - ie,
HashCodeBuilder.reflectionHashCode(this);


/**
* Given an object of type clazz, go through the fields in that
class and concatenate the
* values of all the fields, excluding the ones listed in excludedFields.
* This will only work on bean-type getter methods that 1. do not
have parameters
* and 2. return a String.
* This code does not do any lookup on inherited methods.
* @param object instance where we are getting field values from.
* @param clazz the Class of the object.
* @param builder StringBuilder object to contain our concatenated values.
* @param excludeMethods fields that are not to be included in our builder.
* @throws InvocationTargetException when an error occurs while
calling a method in our object.
*/
public static void concatenateFieldValues(Object object, Class
clazz, StringBuilder builder,
String[]
excludeMethods) throws InvocationTargetException
{

List excludedMethodList = excludeMethods != null ?
Arrays.asList(excludeMethods) : Collections.EMPTY_LIST;

Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods)
{
// only get values from public getter methods that are not
in exclusion list
if ((method.getName().indexOf("get") == 0)
&& !excludedMethodList.contains(method.getName())
&& (Modifier.isPublic(method.getModifiers()))
) {
try {

// this will only deal with bean-type get methods
without params
assert method.getParameterTypes().length == 0;

// TODO - shouldnt we explicitly check and throw
an exception here?
Object fieldValue = method.invoke(object);

if (fieldValue instanceof String) {

builder.append(StringUtils.remove(fieldValue.toString(), ' '));
}
} catch (InvocationTargetException e)
{
throw new InvocationTargetException(e, "Unable to
invoke the method: " + method.getName() +
" from
instance of: " + clazz.getName());
} catch (IllegalAccessException e) {
// this can't happen. Would get a Security exception instead
// throw a runtime exception in case the impossible happens.
throw new InternalError("Unexpected
IllegalAccessException");
}
}
}

}

Friday, 23 May 2008

Java generics - assigning a List<C> where type D implements interface D to a variable of List<D> - can't be done - AKA:Type Erasure ARRGH!

Java generics still confuse me.

A work colleague was asking me something about assigning typed lists
and it wasn't working the way
it would work if you were simply dealing with single variables.

Class C implements interface D.

Then we have a list of interface D.

Another class E has a method findAll() which returns a list of type C.

But we can't assign E.findAll() to a list of type D.


List<Comparable> listOne;

listOne.add("String");
listOne.add(new Long(1000));

BUT

listTwo = new ArrayList<Long>();

listTwo.add(new Long(33));

listOne = listTwo;

Error: "Incompatible types, Required List<java.lang.Comparable> Found:
List<java.util.List>"


Hmm... maybe we can cast the list?

listOne = (List<Comparable>) listTwo;

Error: Inconvertible types; cannot cast
'java.util.List<java.lang.Long>' to
'java.util.List<java.lang.Comparable>'

But why?

Maven IDEA plugin generating incorrect references in .ipr file in multi-module project

Is there a bug in the maven IDEA plugin when working with multi-module files?
I normally do most of my command-line work in the Cygwin environment, and
have not experienced this problem on my previous project, which was a
single-module project.


ERROR in parent project file:

- generated this when running mvn idea:idea under cygwin
- trying to open the project file results in error dialog box:
"Cannot load module file
'C:\work\Fads\C:\work\Fads\FeedProcessor\FeedProcessor.iml':
File C:\work\Fads\C:\work\Fads\FeedProcessor\FeedProcessor.iml does not exist.
Would you like to remove the module from the project?"

<modules>
<module fileurl="file://$PROJECT_DIR$/C:/work/FeedsProject/packaging/FeedsProject.iml"
filepath="$PROJECT_DIR$/C:/work/FeedsProject/packaging/FeedsProject.iml"
/>
<module fileurl="file://$PROJECT_DIR$/FadsParent.iml"
filepath="$PROJECT_DIR$/FadsParent.iml" />
<module fileurl="file://$PROJECT_DIR$/C:/work/FeedsProject/FeedProcessor/FeedProcessor.iml"
filepath="$PROJECT_DIR$/C:/work/FeedsProject/FeedProcessor/FeedProcessor.iml"
/>
<module fileurl="file://$PROJECT_DIR$/C:/work/FeedsProject/FileService/FileService.iml"
filepath="$PROJECT_DIR$/C:/work/FeedsProject/FileService/FileService.iml"
/>
</modules>

- the fix is to generate the IDEA project file under DOS - mvn idea:idea
- this file is correct.

<modules>
<module fileurl="file://$PROJECT_DIR$/packaging/FeedsProject.iml"
filepath="$PROJECT_DIR$/packaging/FeedsProject.iml" />
<module fileurl="file://$PROJECT_DIR$/FadsParent.iml"
filepath="$PROJECT_DIR$/FadsParent.iml" />
<module fileurl="file://$PROJECT_DIR$/FeedProcessor/FeedProcessor.iml"
filepath="$PROJECT_DIR$/FeedProcessor/FeedProcessor.iml" />
<module fileurl="file://$PROJECT_DIR$/FileService/FileService.iml"
filepath="$PROJECT_DIR$/FileService/FileService.iml" />
</modules>

Saturday, 3 May 2008

OutOfMemory error when using Findbugs plugin in Maven? Set the Maven heap size via MAVEN_OPTS

I was doing a "mvn site" and I got an OutOfMemory error at the stage
where it's using the Findbugs plugin:

[INFO] Generating "FindBugs Report" report.
[INFO] No effort provided, using default effort.
[INFO] Using FindBugs Version: 1.2.0
[INFO] No threshold provided, using default threshold.
[INFO] Debugging is Off
[INFO] No bug include filter.
[INFO] ------------------------------------------------------------------------
[ERROR] FATAL ERROR
[INFO] ------------------------------------------------------------------------
[INFO] Java heap space
[INFO] ------------------------------------------------------------------------
[INFO] Trace
java.lang.OutOfMemoryError: Java heap space
at edu.umd.cs.findbugs.ba.vna.ValueNumberFrame.getUpdateableAvailableLoadMap(ValueNumberFrame.java:409)
at edu.umd.cs.findbugs.ba.vna.ValueNumberFrame.copyFrom(ValueNumberFrame.java:285)
at edu.umd.cs.findbugs.ba.FrameDataflowAnalysis.copy(FrameDataflowAnalysis.java:38)
at edu.umd.cs.findbugs.ba.vna.ValueNumberAnalysis.trans

After a bit of research, I set this variable to give enough memory to
Maven, and it fixed the problem. Obviously, change the Xmx value
depending on how much RAM you have to spare.

MAVEN_OPTS="-Xmx1024m -Xms128m -XX:MaxPermSize=512m"

Other posts have mentioned that it's actually setting a MaxPermSize
higher than the default value (32m) that makes the difference. So
let's test it by just setting the first two values:

MAVEN_OPTS="-Xmx1024m -Xms128m"

Actually, it works!

INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1 minute 11 seconds


Other people have recommended setting the plugin property
"maven.findbugs.jvmargs" but I have not tried this out yet. Now in my
pom.xml, I currently have:

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>1.1.1</version>
</plugin>

Which isn't actually the latest version anymore. Maybe the newest
version of the plugin - 1.2 has fixed it.. Let's change it to:

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>1.2</version>
</plugin>

But this time, let's NOT have anything in MAVEN_OPTS. Let's see if the
newer version can do it with the default java settings. Now run mvn
site again.

(Long delay follows). Oh great, more jars to download. WTF? castor?
openejb? xstream? Hardly a build goes by where I don't get yet another
surprise download due to transitive dependencies. Am glad we have
Artifactory set up at work, so only one person has to suffer the
delays of downloading each new jar file.

No dice. OutOfMemoryError again. Let's set our heap to a smaller
figure, compared to before, see if that's enough.

MAVEN_OPTS="-Xmx256m -Xms128m"

Works!

I think this last one can be a safe starting value to use if you ever
run into the OutOfMemory error Findbugs from inside maven. I was
surprised to find out that the default heap is only 32m when JVM is in
client mode, and 64m when in server mode. See here:
http://www.unixville.com/~moazam/stories/2004/05/17/maxpermsizeAndHowItRelatesToTheOverallHeap.html

Sunday, 20 April 2008

The Element Song: various versions

Got home at 2.30, after watching DJ Shadow + Cut Chemist at Luna Park
in the city... (http://au.youtube.com/results?search_query=dj+shadow+the+hard+sell) then checked my email... noticed a link about Tom Lehrer, who's famous for the Element Song, a crazy romp through the periodic table, sung to the tune of Gilbert and Sullivan's, "I Am the Very Model of a Modern Major-General" from Pirates of Penzance (thanks to Bob Funchess for the song title)

end result: just spent 30 minutes going through youtube, checking out
various versions of The Element Song.

Damn you Google blog!

http://googleblog.blogspot.com/2008/04/heres-to-tom-lehrer-elemental-geek.html

(and he also did that song about "silent e" for Electric Company! OMG.)

my faves so far:

the original (audio only):



http://au.youtube.com/watch?v=WNfx0FO4hzs&NR=1



great version at a talent show:



http://au.youtube.com/watch?v=9cbgAELAX18&feature=related

sung by a 4-yr old (who kinda sounds like Martin Price from the Simpsons):



http://au.youtube.com/watch?v=QWkVO6Bp8VM&feature=related




showing the elements' location in the table:



http://au.youtube.com/watch?v=WNfx0FO4hzs&NR=1




this isn't a version.. but still interesting rapping with *some* of
those elements..




http://au.youtube.com/watch?v=pmTXtbRR7c0&feature=related

Saturday, 19 April 2008

OpenSocial for MMOGs?

Woke up at 5.30 am, after dozing off last night without having dinner. Torrential rain pouring when I woke up but it's stopped now. Random browsing. Finally saw that Judd Apatow "Backlash" video that's been in an Opera browser tab for >1 month - shameless, self-aware viral marketing... lol. i think.

Came across an great post on another gaming site, "Player vs. Everything: When will the players leave WoW?", discussing why people aren't going to be leaving WoW anytime soon.

Interesting how the social aspect - in this case networks of friends from all over the net - is such a big factor in the "stickiness" of these games, whereas before it was completely based on the gameplay. It makes it much harder for rivals to make a significant dent in the market. Not only does your game need to be much more compelling, but you need to factor in that networking aspect, which goes beyond coding wizardry or eye candy.   You can't just fix pain points, or be slightly better.  You have to make it compelling enough not just for one new player, but all that player's friends to play and stay with your game. 

If you have a new player, how do you make it easy for them to move over as much of their current network to your game?   Signup bonuses? Almost-free copies of the game, which you can't really play standalone? (I think WoW already have this?) In-game equivalents of pyramid schemes? Those horrible Facebook apps do it by spamming everyone you know - actually, it's not the app, it's you doing the spamming! But how the hell will that apply to a MMOG?

It's quite similar to the stickiness of the current "web 2.0" hotspots like MySpace, YouTube... once you've built up relationships and conversations in one area, it'll be very difficult to get out and start anew, since you've built up all this content and would be loath to just throw it all away. Social networks are the new vendor lock-in. (yes, someone else came up with that)

The only sites I can think of that make it a bit easy to migrate are the blogs, but mainly because the content is fairly generic (text, tags, comments!) and easily restructrured. I can't see this model ever being applied to games - "Why sure, we'll let you move on to another MMO, no probs!"  OpenSocial for MMOGs, anyone? Of course, the question here becomes: is there anything at all that is portable across different games?

Saturday, 12 April 2008

How to run code when your Java program is being terminated? Use ShutdownHook!

The starting point for this was a problem I have at work, where we have a program that is using ScheduledExecutorService, and is meant to run continuously, but we also want to run some code when the program is terminated with Ctrl-C or a 'kill' command.

At first, I thought that a try-finally would give me what I wanted. A little bit of testing proved me wrong. The try-finally structure can handle exceptions, and ensure that code enclosed in finally is called, but only in the case where the process is running normally, and not terminated externally. The statement inside a finally{} will not execute if the JVM is terminated.

The ShutdownHook is what I needed, which was added in JDK 1.3.1.  The method to use is Runtime.addShutdownHook().  The shutdown hook is pretty much any code you put inside the Thread.run() that you pass to this method.

public class FinallyTest {
public static void main(String[] args) {

final int thisNumber = 9;
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
System.out.println("I am now calling my shutdown stuff. EVERYTIME!");
System.out.println("This number: " + thisNumber);
}
});

try {
System.out.println("FinallyTest.main");
boolean loopIt = true;
int i = 0;
System.out.println("Now entering infinite loop.");
while (loopIt) {
i++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

} finally {
System.out.println("Finally has been called!");
}
}
}





Running the program, and then killing it gives me (this is on OS X, on an iMac):

krangsquared$ java -cp . FinallyTest &
[1] 5735
krangsquared$ FinallyTest.main
Now entering infinite loop.

Okay, we now have a process Id. Let's kill it! 


krangsquared$ kill 5735

krangsquared$ I am now calling my shutdown stuff. EVERYTIME!
This number: 9

[1]+ Exit 143 /System/Library/Frameworks/JavaVM.framework/Versions/1.6/Commands/java -cp . FinallyTest


The only catch to this is that this will not work if you terminate the program using "kill -9".  And I haven't checked this yet on Windows to see what happens if you kill it using TaskManager. (is there a "kill -9" equivalent on XP?)

Hope this helps someone out there! Or maybe someone who has a better idea or suggestion can make a comment. I need all the help I can get.  :)

Btw, I've found a blog post with a nicer way of doing this - instead of dumping a whole lot of stuff in a Thread.run() as an anonymous inner class (insert. closures. proposal. here.), have another Runnable inner class to contain all the shutdown code, then pass that in to the constructor of a new Thread that you pass to addShutdownHook().