Monday, October 26, 2009

Get around file upload in Flex and Java

Http File Upload
My goodness me but I've had trouble with file upload for web sites. All sorts of issues ranging from timeouts to size restrictions to firewalls and now most recently Mac OS X 10.5 server just likes to keep a hold of uploaded file streams before releasing them to Tomcat.

The Scenario
Mac OS X 10.5 server on a mac mini with apache 2 and tomcat. Apache is has my web app as a virtual host which lives in Tomcat (6.18 or something). I've got a flex 3.4 client trying to do an http upload to my app. Seems to work up to a point then my Java app spits the dummy saying stream connection timeout! Bah! I traced it out and found that when my servlet should be reading the file input stream the file is not in my temporary directory yet. Mac OS X has subverted the file for its own nefarious purposes and left me hanging. I tried to stop indexing, stop finder, stop any sort of virus scanning and so on but no good. OS X keeps holding onto the file. Seems to only happen on multi-page pdf and word documents. Jpgs and pngs are passed straight through.

The Fix
So I've had enough with http file upload and its various vagaries and config problems and OSes sticking their noses in where they aren't wanted. I decided to create a custom FTP solution. Everyone loves FTP! It was actually designed to upload files. But how do I do it?

Server Side
Hooray for Apache FTP Server. It's an open source Java ftp server based on the mina project. So I grabbed it source code and all and began butchering. Firstly it uses Spring for configuration and my app uses Spring for configuration so that is nice. Secondly it allows you to implement your own user manager and your own list of commands. So I created a user manager that would authenticate with my app's authentication system and added a custom command that would save uploaded files into my own file system and data base (all files are actually referenced in the app by DB entry and the DB entry points to the file system). That made for a neat little wrap.

UH OH Flex Sucks Big Ones
Flex is a fantastic bit of work from Adobe; an object oriented (mostly) language and framework for developing rich internet applications. Yum. If it's an app your building (not someones home page) then flex kicks seven kinds of poo out of html. But still, in some ways it sucks pretty hard. The hardest suck is its socket API. You can create a socket and connect to a server on any old port then read and write bytes as you'd expect. However the Flash player hides all the good stuff from you. So you can say socket.write(mybytes) and then socket.flush() but do you think the Flash player will actually send the bytes over the wire when you flush? WRONG, FAIL. The bytes go when Flash says they can go and you wont know when that is. More importantly in the case of my FTP solution you can merrily send bytes onto the socket and flush them and then close the socket only to find that the ftp server closes the socket before Flash has finished flushing! That means you get truncated files and no progress bar on the client side.

Hooray For Open Source
"No worries" says I. I've got my FTP server code and Eclipse so I can do anything. I in fact alter the server code to send back status events with the number of bytes actually read. Flex then listens to these on the control socket and updates a progress bar. Once Flex knows that all the bytes have actually been written and received it closes the socket and logs off the FTP session. The really good thing about this is that I've been able to configure the FTP server so that the only commands available are those required to authenticate and my custom upload command. So no mister hacker you can't just start mucking about with my ftp server and create accounts etc... In fact all you can do is upload files that conform to my app's specification and they aren't executed files.

That's It
So that is the solution to my http upload woes. If you or a person you know suffers from file upload issues I can post some code and such. But since no-one follows my blog anyway I can't be bothered unless requested.

Tuesday, August 11, 2009

New Improved Industrial Strength Whoop Tooshy

As advertised in my earlier blog that noone read I found my Blaze cluster solution didn't work.

The Reason:
So creating a second subscription on the second server in the cluster stopped the "no active subscription" error which is nice, but it also tended to mean you didn't get your messages when polling against that server.
Clearly I needed a solution, one that actually works (rather than the half arsed hack I did before).

The Solution:
I still used the configurableAMFEndpoint and the ClusterMessagingFilterThingy but I changed some code in the filter. Now When a subscribe command comes in I store in the Tomcat session the cluster address of the server receiving the subscribe command (I still use the flexClientId + endpoint as the key). Then I extended the AMFEndPoint and overrode the handleFlexClientPollCommand() method to check the Tomcat session to see if the current server was the subscription server or some other server. If it was the subscription server I just continue on as normal. Otherwise I use an extended MessageService to grab the cluster manager and send the poll command to the subscription server. The how of how I did that is a kind of magic. I had to extend the JGroupsCluster object and some other JGroups thingy and I had to modify the Blaze JGroupsCluster to allow my extended object access to some of its guts. The real trick lies in once your subscription server has the poll request you need to launch a new thread to do the poll otherwise the JGroups cluster is blocked by the poll wait time (in my case 50 seconds because Mac Safari has a 60 second timeout on open but unanswered requests).

All The Code:
All the code can be added, but I'd need someone to actually ask for it because there is a fair bit of it and I'm essentially pretty lazy. No. You only think you understand how lazy I am, in reality I'm even more lazy than what you think. Kind of like estimating time to perform any given task in a project; you think you've added enough padding but no matter how much you add you always need to double it.

CIAO.

Sunday, July 19, 2009

Hardware Load Balanced BlazeDS Cluster on Tomcat

GREAT BIG NOTICE: My professional blog has a good working solution to this: http://www.reignite.com.au/blaze-push-messaging-in-a-cluster/

Blaze DS:
Blaze is an open source remoting and messaging system from Adobe. I use the Java version because that's the crazy devil-may-care fool I am. Blaze is really cool (assuming first you find web technologies cool) and has made my life considerably easier. In the past I used web services (Axis anyone?) but when developing rich Internet applications I found web services to be too cumbersome in terms of transferring stacks of complex data as objects. Blaze also does "push" which means I can have multi-user real time apps where the users can interact with shared data. Of course Blaze, being from Adobe, is used in particular to communicate between Flex and my services. Flex is fantastic too! It does of course have draw backs, but in all pretty good.

Clustering:
There comes a time in every young server's life when thoughts begin to turn toward other servers and they get together to share certain aspects of their life. I had to take two Mac Minis with OS X Server, each with Tomcat, MySQL, of course Blaze and I had to turn them into a mighty cluster. The Blaze manual says that HttpFlexSession does not cluster so you need to make sure that once a client reaches a server it should "stick" to that server so it has access to the appropriate HttpFlexSession. That would be wonderful except this is the real world where clusters sit behind a hardware load balancer and we need to not only balance load but also provide failover. This is tricky because it means that a client doesn't know which machine they will hit and they also don't have direct access to the server IP addresses so no you can't use the built in Flex failover.

JGroups:
BlazeDS uses JGroups to ensure that messages generated or sent to one server reaches all the others in the cluster. It is really easy to set up and the Blaze manual show you how... Except it is wrong. oops. Caused me trouble, but I got over it. The problem is in the jgroups-tcp.xml explanation. The num_initial_members="2" is explained as having a value equal to the number of members in the element. An important point to creating a JGroups cluster that can survive restarts and such is setting up the ports well.
<TCP ... start_port="7800" end_port="7804" ...>

Not that I've created a range of ports for the TCP element. Because when old Bessy fires up port 7800 may be in use! It may be in use by JGroups because it hasn't been released yet or because some other mysterious creature wants it. Anyway it is more rugged to give a port range. Then in the TCPPING element
<TCPPING ... initial_hosts="10.1.1.1[7800]" port_range="5" num_initial_members="2" />

So it will look for host 10.1.1.1 at port 7800 but if that fails it will check ports 7801,7802,7803 and 7804. This makes it all a little more able to deal with the ups and downs of modern server life.

Tomcat Clusters:
My first hurdle was to ensure that the Tomcat sessions were being replicated across servers. The HttpFlexSession is stored in the Tomcat session so by replicating the Tomcat session I get the HttpFlexSession! Hooray! Of course that didn't really work in my case. So anyway to set up a Tomcat cluster so it replicates sessions is really easy (unless you are reading this out of frustration hoping to find a secret answer).
In your web.xml but just below your tags. Then in the server.xml in the Tomcat conf directly uncomment the tag. I also put the sendChannelOptions="4" attribute in because I wanted to make sure that the session replication occurred before the request returned (4 = synchronous while the default is 8 which is async). That's all there is to it. Though if you are dong things a bit trickier you better check the Tomcat doco.

The Cluster Problem:
I use Blaze messaging to push updates to the users of my app. That means the client creates a Consumer and subscribes to a channel / destination at an endpoint. The endpoint is the load balancer IP address and the load balancer decides which server the Consumer subscribes to. But then when the Consumer polls (I use "long polling") it hits the load balancer again and might end up polling ther server it is not subscribed to and you get an error back and you probably start crying (that's what I did). This is simply because the subscription is held in the SubscriptionManager on the Destination which is created in the MessageBrokerServlet and so is specific to the server and not held in the HttpFlexSession let alone the Tomcat session. Ack! To make things a little interesting the subscription is created by the FlexClient which is held in the HttpFlexSession but because the FlexClient and MessagingClient are not added using the setAttribute() method of the Tomcat session the Tomcat server replication doesn't copy it across.

Solution:
Warning, the following doesn't really work :(
The following doesn't work as advertised I'm sorry (not that anyone reads this blog anyway). The configurable endpoint is good and I still use the cluster filter bizzo but the subscribing on another server thing fails somewhat. It works a bit, but also doesn't a bit. A shame really. Anyway check out my other post on how to make is good and worky.
Here is what I did: I rolled my sleeves up spat on my hands and brewed a strong cup of harden up. Also I hacked up Blaze and made it excellent.
1. Create ConfigurableAMFEndpoint.java

package flex.messaging.endpoints;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import flex.messaging.config.ConfigMap;
import flex.messaging.endpoints.amf.AMFFilter;
import flex.messaging.endpoints.amf.EndPointAwareFilter;

/**
* @author surrey
*
*/
public class ConfigurableAMFEndpoint extends AMFEndpoint {

private List filters = new ArrayList();

protected AMFFilter createFilterChain() {
AMFFilter firstFilter = super.createFilterChain();
AMFFilter lastFilter = getLastFilter(firstFilter);
for (AMFFilter filter : filters) {
// add this endpoint on those filters that take an endpoint.
if (filter instanceof EndPointAwareFilter) {
((EndPointAwareFilter) filter).setEndpoint(this);
}
lastFilter.setNext(filter);
}
return firstFilter;
}

private AMFFilter getLastFilter(AMFFilter filterChain) {
AMFFilter last = filterChain;
while (last.getNext() != null) {
last = last.getNext();
}
return last;
}

public void initialize(String id, ConfigMap properties) {
super.initialize(id, properties);
ConfigMap filterMap = properties.getPropertyAsMap("filter-chain", null);
if (filterMap != null && !filterMap.isEmpty()) {
List filterNames = filterMap.getPropertyAsList("filter-class",
null);
if (filterNames != null) {
for (Iterator iter = filterNames.iterator(); iter.hasNext();) {
String className = (String) iter.next();
try {
filters.add((AMFFilter) Class.forName(className)
.newInstance());
} catch (InstantiationException e) {
log.error("Could not instantiate filter: " + className,
e);
} catch (IllegalAccessException e) {
log.error("No public constructor for filter: "
+ className, e);
} catch (ClassNotFoundException e) {
log.error("Could not find filter class: " + className,
e);
}
}
}
}
}

}
This endpoint extends the AMFEndpoint by allowing me to add extra filters to the Blaze filter chain.
2. I modified MessageBrokerFilter to have:
if (next != null) {
next.invoke(context);
}

// Service the message.
outMessage = endpoint.serviceMessage(inMessage);
So that the chain continues on if I've added any.
3. I created an EndPointAwareFilter which I could extend by my own filters so that they automagically get their EndPoint added to them. I've created a couple other filters too such as a security filter which allows me to use custom security with AMF headers. That's another story though because I had to hack up some Flex code and add more functionality to Blaze.

package flex.messaging.endpoints.amf;

import flex.messaging.endpoints.AMFEndpoint;

/**
* An abstract AMFFilter that other filters should extend to gain access to
* their endpoint. This is only populated when using a ConfigurableAMFEndpoint.
*
* @author Surrey
*
*/
public abstract class EndPointAwareFilter extends AMFFilter {

private AMFEndpoint endpoint;

public AMFEndpoint getEndpoint() {
return endpoint;
}

public void setEndpoint(AMFEndpoint endpoint) {
this.endpoint = endpoint;
}
}


4.. I created ClusterMessagingFilter.java
package au.com.truenorth.amf.filter;

import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpSession;

import flex.messaging.Destination;
import flex.messaging.FlexContext;
import flex.messaging.MessageDestination;
import flex.messaging.endpoints.amf.EndPointAwareFilter;
import flex.messaging.io.amf.ActionContext;
import flex.messaging.io.amf.MessageBody;
import flex.messaging.messages.AsyncMessage;
import flex.messaging.messages.CommandMessage;
import flex.messaging.services.Service;

public class ClusterMessagingFilter extends EndPointAwareFilter {

public ClusterMessagingFilter() {
super();
}

@SuppressWarnings("unchecked")
@Override
public void invoke(ActionContext context) throws IOException {

Object data = getData(context);

if (data instanceof CommandMessage) {
CommandMessage command = (CommandMessage) data;
int operation = command.getOperation();
Object clientId = FlexContext.getFlexClient().getId();
String endpointId = getEndpoint().getId();

if (operation == CommandMessage.POLL_OPERATION) {
// add a subscription if appropriate
HttpSession sess = FlexContext.getHttpRequest().getSession();
Object att = sess
.getAttribute(clientId.toString() + endpointId);
if (att != null && att instanceof List) {
List> destList = (List>) att;
for (Map subscribeMap : destList) {
MessageDestination msgDest = getMessageDestination(subscribeMap);
Object cId = subscribeMap.get("clientId");
if (msgDest != null
&& msgDest.getSubscriptionManager()
.getSubscriber(cId) == null) {
Object selectorExpr = subscribeMap
.get("selectorExpr");
String selectorString = getStringOrNull(selectorExpr);
Object subtopic = subscribeMap
.get("subtopicString");
String subtopicString = getStringOrNull(subtopic);

msgDest.getSubscriptionManager().addSubscriber(cId,
selectorString, subtopicString,
subscribeMap.get("endpointId").toString());
}
}
sess.removeAttribute(clientId.toString() + endpointId);
}
} else if (operation == CommandMessage.SUBSCRIBE_OPERATION) {
// get httpSession and stick a flag in with everything needed to
// make a subscription
Object att = FlexContext.getHttpRequest().getSession()
.getAttribute(clientId.toString() + endpointId);
List
> destList = (List>) att;
if (destList == null) {
destList = new ArrayList
>();
}

Map subscribeMap = createSubscribeMap(command);
destList.add(subscribeMap);
FlexContext.getHttpRequest().getSession().setAttribute(
clientId.toString() + endpointId, destList);
}
}
if (next != null) {
next.invoke(context);
}
}

private Map createSubscribeMap(CommandMessage command) {
Map subscribeMap = new HashMap();

subscribeMap.put("clientId", command.getClientId());
subscribeMap.put("endpointId", getEndpoint().getId());
subscribeMap.put("subtopicString", command
.getHeader(AsyncMessage.SUBTOPIC_HEADER_NAME));
subscribeMap.put("selectorExpr", command
.getHeader(CommandMessage.SELECTOR_HEADER));
subscribeMap.put("destination", command.getDestination());
return subscribeMap;
}

private String getStringOrNull(Object selectorExpr) {
String selectorString = null;
if (selectorExpr != null) {
selectorString = selectorExpr.toString();
}
return selectorString;
}

private MessageDestination getMessageDestination(
Map subscribeMap) {
String destId = subscribeMap.get("destination").toString();
MessageDestination msgDest = null;
for (Iterator iter = getEndpoint().getMessageBroker().getServices()
.keySet().iterator(); iter.hasNext();) {
Service service = (Service) getEndpoint().getMessageBroker()
.getServices().get((String) iter.next());
Destination dest = service.getDestination(destId);
if (dest != null && dest instanceof MessageDestination) {
msgDest = (MessageDestination) dest;
break;
}
}
return msgDest;
}

@SuppressWarnings("unchecked")
protected Object getData(ActionContext context) {
// get the in message to inspect the headers
MessageBody request = context.getRequestMessageBody();

Object data = request.getData();
if (data instanceof List) {
data = ((List) data).get(0);
} else if (data.getClass().isArray()) {
data = Array.get(data, 0);
}
return data;
}

}
This filter is a little messier than I like and the honest to goodness production version is a bit nicer but you get the picture. Essentially when a client subscribes to a server that server adds all the required info to subscribe into the Tomcat session so that when a poll hits another server I can get that info out and create a subscription on that server and this all happens before any servicing of the poll occurs so I don't get any dumb "you are not subscribed" errors.

The most amazing thing is this works! I was actually able to sit there with my colleague logged in to the system with both of us using it while I turned off first one server, brought it back up then turned off the other server. The whole time the system just kept polling and as users we didn't even know the server had died and come back up. Brilliant.

Oh the services-config.xml
<clusters><br />        <cluster id="default-cluster" properties="jgroups-tcp.xml" default="false" balancing="false"><br />    </clusters><br /><!-- the above is added so I can use clusters.  Note that I set default="false" because I'm a control freak -->
<channel-definition id="my-polling-amf" class="mx.messaging.channels.AMFChannel">
         <endpoint url="@AMF_POLLING_END_POINT_BASE@" class="flex.messaging.endpoints.ConfigurableAMFEndpoint">
         <properties>
             <filter-chain>
                 <filter-class>au.com.truenorth.amf.filter.ClusterMessagingFilter</filter-class>
             </filter-chain>
             <polling-enabled>true</polling-enabled>
             <wait-interval-millis>50000</wait-interval-millis>
             <polling-interval-seconds>0</polling-interval-seconds>
             <max-waiting-poll-requests>50</max-waiting-poll-requests>
             <serialization>
                 <type-marshaller>au.com.truenorth.service.bean.NumberHandlingTypeMarshaller</type-marshaller>
             </serialization>
         </properties>
     </endpoint>
<!-- This is my channel definition where I set up the long polling and configure the ClusterMessagingFilter -->
<!-- You'll also note, if you are clever, the NumberHandlingTypeMarshaller which allows me to send Null numbers to Flex.  I also modified Blaze to turn NaN numbers into null Numbers (like Integer) coming into Java so my Hibernate wouldn't go spaz when id fields were NaN when they should be null. --></channel-definition>


Well this has been a long one but I hope at least one person finds it helpful.

Sunday, January 18, 2009

FOP Positive Descender Underline Bug

Well here is my cowardly effort at giving back to the open source community. But first! What is the open source community?

Nerds:
It's all about nerds. I'm one and so are you if you are reading this after doing a google search for a solution to the positive descender underline bug in Apache FOP. But if not I'll give a quick run down. In the world of computer programming there is a movement which holds dear the concept that ideas and information are free. Free as in unshackled, free as in free to be exchanged. Sometimes you have to pay money for the info and ideas, but if / when you do you should have access to the whole idea. The same way when you buy a car you are allowed to open the bonnet and tinker with the guts. So programmers often allow others to see their work and we are all enriched by it. For a more detailed discussion: open source [wikipedia]

The Bug:
The bug I have solved manifests itself in a bit of software called Apache FOP. This software allows me to take some stuff drawn on the screen in Adobe Flash and turn it into pdf documents or png images. The problem happens when you try to underline text in an embedded font which has a positive descender. A positive descender means that part of the text of the font hangs below the baseline. For example in Lucida Sans the p, y, j and g all have little squiggly bits hanging below the base of the other characters. In this instance FOP fails to draw an underline because it throws an error that says it can't draw a border with a negative height.

Suspect Math:
The cause is a bit of suspect math in the underline
public abstract class AbstractPathOrientedRenderer extends PrintRenderer {
...
protected void renderTextDecoration(FontMetrics fm, int fontsize, InlineArea inline,
int baseline, int startx) {
boolean hasTextDeco = inline.hasUnderline()
|| inline.hasOverline()
|| inline.hasLineThrough();
if (hasTextDeco) {
endTextObject();
float descender = fm.getDescender(fontsize) / 1000f;
float capHeight = fm.getCapHeight(fontsize) / 1000f;
float halfLineWidth = descender / -8f / 2f;
float endx = (startx + inline.getIPD()) / 1000f;
if (inline.hasUnderline()) {
Color ct = (Color) inline.getTrait(Trait.UNDERLINE_COLOR);
float y = baseline - descender / 2f;
drawBorderLine(startx / 1000f, (y - halfLineWidth) / 1000f,
endx, (y + halfLineWidth) / 1000f,
true, true, Constants.EN_SOLID, ct);
}...
So you can see the bolded parts involve some calculation of a y position based on the descender value of the font. If the descender value is negative it all works nicely, if it is positive we end of with an inverted set of values in the call to drawBorderLine() method.

My Solution:
Here you see what I changed:
protected void renderTextDecoration(FontMetrics fm, int fontsize, InlineArea inline,
int baseline, int startx) {
boolean hasTextDeco = inline.hasUnderline()
|| inline.hasOverline()
|| inline.hasLineThrough();
if (hasTextDeco) {
endTextObject();
float descender = Math.abs(fm.getDescender(fontsize) / 1000f);
float capHeight = fm.getCapHeight(fontsize) / 1000f;
float halfLineWidth = descender / 16f;
float endx = (startx + inline.getIPD()) / 1000f;
if (inline.hasUnderline()) {
Color ct = (Color) inline.getTrait(Trait.UNDERLINE_COLOR);
float y = baseline + descender / 2f;
drawBorderLine(startx / 1000f, (y - halfLineWidth) / 1000f,
endx, (y + halfLineWidth) / 1000f,
true, true, Constants.EN_SOLID, ct);
}...
Now the bolded bits firstly show I get the absolute value of the descender, I divide it by 16 (rather than by 8 then by 2) and finally I add half the descender value to the baseline to get the y position.
The short of it is that the code now works for both negative and positive value descenders. The world rejoice!

Did I submit this back to the opensource project? No, because I've already hacked up my copy of FOP beyond recognition to reduce the on disk output size of pdfs using large svg images (by reducing the number of decimal points of precision from 8 to 3) and using a file backed caching system to step around the memory issues associated with printing said large pdfs. Feel free to ask me for more details about those fixes, if I don't get around to writing about them here.

Wednesday, January 14, 2009

Woodwork, My Bedside Table

I love making stuff. By trade I make software but I have trouble explaining to non-software types what it is that I have done. It is difficult to show my wife the amazing indexed search system I implemented or explain how I overcame the font underline issues in Apache FOP when using a font with a positive decender value. But wait! Woodwork on the other hand produces a tangible item that can be touched and felt, smelt and tasted... though you probably shouldn't.

Project 1: My Computer Desk
I used to live in Melbourne but since I failed to grow gills and/or a protective layer of fur and blubber (working on the blubber) I moved to somewhere sunny and warm. In the move I threw out my old student desk because I was struck by a sever case of good taste. I was left without a desk, but gained a large room I could work in. The short of it is that I designed and built a computer desk with three draws on one side and a shelf on the other for my computer. It turned out ok though it is clear there is a good reason we pay cabinet makers to build such things. If you cared to read my previous post you would understand that I spent a lot of time, little money and no experience in creating my desk. I really needed to spend more time I think.

Project 2: My Bedside Table
In this new house I am in there is a "granny flat" where my parents stayed while they visited. In it we put a bed but alas, no table beside it! So I am making a bedside table. I've got a couple more tools and a computer desks worth of experience.

The design:

A marvel of engineering and sensible elegance no? The whole thing is to be made of pine then stained and sealed. I've already made a start.
What I've done:

Here you can see one completed shelf and the four frame pieces for the second shelf. I'm using lap joins to cross hatch the frame because I think I'm tricky. To cut out the waste of the lap joins I initially used a hand tenon saw to cut each edge then make three cuts in the waste. I then used a chisel to clear the waste. This is what my big book of wood working tells me to do. What the big book doesn't say is that if you are an inexperienced tit with about seven thumbs, most of them on your left hand, you have little chance of making rebates that will fit together. I had to learn that the hard way.

Difficulties:
None of my joins worked. Each plank had what looked like a suitable rebate but when put together they didn't sit flat and were really hard to squeeze in to place. Not to fear I'm a software engineer! When experience lets me down (I have so little) I look for someone else's technology to save me. I essentially wanted to download the Apache Joint Maker 4 Pine but that doesn't exist. Instead I used my Christmas gift vouchers to buy a router. I set the depth and ran the router over each of my chiseled rebates and it sheared off the wood I'd missed and now all the joins meet perfectly.

The actual shelf bit is made of three planks joined side by side with glue (I just need it strong enough to hold while I attach them) and joined to two of the cross pieces by screws hidden in pocket holes.

Next:
The next step is to make the legs and cut the housing holes for the shleves then a lick of stain and estapol and she'll be right.

Another project?
After I've done this I think I might make a little TV stand for my spare TV. Really just another excuse to use power tools and learn a bit more. I've told people I'm going to retire early (about 40) and they say "But what will you do? Wont you get bored?" (they actually misspell won't when speaking... somehow) As if the only thing I do is work! Pah. I intend to make stuff out of wood and build a car (from the ground up, not just do up an existing one) and race a car and probably crash a car and injure myself with power tools. What more could a man want?

I'll update my blog with my progress. Also I might write a bit about how it is that I intend to retire so young.

Wednesday, January 7, 2009

Three Exchangable Comodities

In my world view, soon surely to be yours too, there are generally speaking three exchangable comodities that any human may possess. These can be used in some combination to achieve nearly anything you might choose to do.
  1. Time
  2. Money
  3. Experience / Knowledge
These must be considered in the context of a specific task. For that task then a person has a pool of each resource and a lack in one can be compensated for by an abundance in another.

Time:

Given enough time a person can do anything. The sad thing being that many tasks require so much time that no one person can supply it. For example, say I want to build a house: With enough time I could build the mud bricks I'd need, then lay them (having designed the house) then make tiles / shingles and so on and so forth. This is of course how they did it many years ago, possibly as early as 1982, though I'm not sure humans had discovered shelter yet...

Money:
Ah, money. Many people really don't understand what money is, but this three comodity model is reasonably good at demonstrating it. Money is an abstract representation of applicable resource. It is a way of trading your experience and time for someone else's experience and time. In the above example it would take me many long hours (lifetimes?) to build a house. However I am quite skilled at developing software solutions to business problems. I get paid to use my skill over time so I can use that money to exchange for someone else's skill and time to build my house. Lovely. That really is what money is about. How else can I trade my knowledge with a builder? What use has a brick layer for a multi-user distributed database application? Well arguments could be made, but it general they would prefer $$$, though beer is a close second.

Experience / Knowledge:
The final piece of the puzzle. If you have plenty of experience doing something, generally speaking the less time you need to do it. If you can do it you don't need to pay someone else to do it. To do a basic service of my car takes me a good 3-4 hours. That's changing oil, filters, plugs and checking brakes etc... A good mechanic can probably do it in 1 hour (maybe less? heck I'm not even experienced enough to know.)

So time, money and experience are exchangable. Or at least that is how I look at most problems I run into. I use this theory to help evaluate various decisions I have to make.

Service my car:
  1. Time: 3-4 hours
  2. Money: $100
  3. Experience: not much.
If I want to spend less money I'd need either enough time to develope and build my own oil filter or lubricant or I'd need enough experience and knowledge to find good cheaper substitutes.
To reduce time I could pay a mechanic or just know how to do everything without need to constantly look up my books.
To reduce my experience required I could pay a mechanic or spend longer learning everything.

I guess you get the picture.

Cognitive Imperial, I

Cognitive imperialism is a fancy-pants way of describing the act of convincing others to a particular way of thinking; that is building an empire of thought.

Yes, I did major in Arts at university and so undertook philosophy, psychology, literature and other perverse studies. Fear not however! I quickly saw the error of my ways and changed to a double major of computing and marketing in the school of business (I actually wanted a job when I finished).

So this blog is my cognitive imperialism, my attempt to express my world view in a way that conquors the thoughts of others and sways them to hold my beliefs. Failing that I'll just discuss my thoughts on cars, motor-sport, computing, home improvement and possibly current events relating to free thought. So in other words much like every other blog. I will attempt to provide some level of contraversial or maybe thought provoking content, or in the case of my computing discussions, useful solutions to ugly problems.

So with no more time wasted on introductions I will present my first mind expanding thought:
Farts, along with sudden groin hits, are surely the only universal comedy. I recon that if aliens were to land and we wanted to give them a good giggle all we need to do is wait for a dramatic pause in the greetings and let loose with a good, loud and squeeky fart. What a way to break the galactic ice?

That aside I'll be back to discuss my current wood-working project: a bedside table! Hooray for excitement.