<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-532350380457944298</id><updated>2011-08-16T11:57:50.267-07:00</updated><category term='apache'/><category term='wowza'/><category term='timezone'/><category term='h264'/><category term='socket'/><category term='iphone'/><category term='utc'/><category term='xuggler'/><category term='java'/><category term='Mac'/><category term='file upload'/><category term='transcoding'/><category term='streaming'/><category term='money crap comodity exchange time'/><category term='tomcat'/><category term='date'/><category term='imperialism fart computers java flex cars philosophy'/><category term='flex'/><category term='ftp'/><category term='OS X'/><title type='text'>My Cognitive Imperialism</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://cogimp.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://cogimp.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Scion Mandate</name><uri>http://www.blogger.com/profile/12674405150380723269</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://2.bp.blogspot.com/_XLDCiZjHHWY/SWVovcbR5NI/AAAAAAAAAAM/Hz6N7P379jQ/S220/myUglyMug.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>13</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-532350380457944298.post-2379970886625062866</id><published>2011-07-13T17:45:00.000-07:00</published><updated>2011-07-13T17:45:14.028-07:00</updated><title type='text'>I've gone all professional and stuff</title><content type='html'>Howdy, I'm no longer posting to this blog. &amp;nbsp;You can read more of my amazing adventures in code at:&lt;br /&gt;&lt;a href="http://www.reignite.com.au/blog/"&gt;http://www.reignite.com.au/blog/&lt;/a&gt;&lt;br /&gt;Where I blog under my professional name: Surrey.&lt;br /&gt;Click around the site and you'll see I work at Reignite. &amp;nbsp;My blog entries there will be more formal and less chatty. &amp;nbsp;Here, I can be&amp;nbsp;irreverent&amp;nbsp;and silly, there I have my business suit on so present my more reasonable and sensible cases.&lt;br /&gt;&lt;br /&gt;I may even reveal the amazing truth that is AMF using GWT. &amp;nbsp;That's a javascript AMF client I wrote / adapted to make use of the existing services I've already written.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/532350380457944298-2379970886625062866?l=cogimp.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://cogimp.blogspot.com/feeds/2379970886625062866/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://cogimp.blogspot.com/2011/07/ive-gone-all-professional-and-stuff.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/2379970886625062866'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/2379970886625062866'/><link rel='alternate' type='text/html' href='http://cogimp.blogspot.com/2011/07/ive-gone-all-professional-and-stuff.html' title='I&apos;ve gone all professional and stuff'/><author><name>Scion Mandate</name><uri>http://www.blogger.com/profile/12674405150380723269</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://2.bp.blogspot.com/_XLDCiZjHHWY/SWVovcbR5NI/AAAAAAAAAAM/Hz6N7P379jQ/S220/myUglyMug.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-532350380457944298.post-7270903738340885679</id><published>2011-02-09T18:50:00.000-08:00</published><updated>2011-02-09T18:50:32.534-08:00</updated><title type='text'>Magic Words, Magical Writers</title><content type='html'>&lt;a href="http://www.magicalwords.net/faith-hunter/applauds-wildly-and-contest-with-todays-post/"&gt;Applauds Wildly, and Contest with Today's Post&lt;/a&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Not only am I a Flex / Java nerd, but I'm a fantasy writing nerd too!  I wrote a novel, then I read magicalwords.net and rewrote my novel because of all the useful info and tips I got.  Hooray.  Now I'm linking back to it so others can find it and so I can go in the draw to win a cool prize.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/532350380457944298-7270903738340885679?l=cogimp.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://www.magicalwords.net/faith-hunter/applauds-wildly-and-contest-with-todays-post/' title='Magic Words, Magical Writers'/><link rel='replies' type='application/atom+xml' href='http://cogimp.blogspot.com/feeds/7270903738340885679/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://cogimp.blogspot.com/2011/02/magic-words-magical-writers.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/7270903738340885679'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/7270903738340885679'/><link rel='alternate' type='text/html' href='http://cogimp.blogspot.com/2011/02/magic-words-magical-writers.html' title='Magic Words, Magical Writers'/><author><name>Scion Mandate</name><uri>http://www.blogger.com/profile/12674405150380723269</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://2.bp.blogspot.com/_XLDCiZjHHWY/SWVovcbR5NI/AAAAAAAAAAM/Hz6N7P379jQ/S220/myUglyMug.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-532350380457944298.post-9007348350769230825</id><published>2010-10-10T23:14:00.000-07:00</published><updated>2010-10-10T23:14:56.145-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='wowza'/><category scheme='http://www.blogger.com/atom/ns#' term='streaming'/><category scheme='http://www.blogger.com/atom/ns#' term='flex'/><category scheme='http://www.blogger.com/atom/ns#' term='transcoding'/><category scheme='http://www.blogger.com/atom/ns#' term='h264'/><category scheme='http://www.blogger.com/atom/ns#' term='iphone'/><category scheme='http://www.blogger.com/atom/ns#' term='xuggler'/><title type='text'>Pushing Live Streams From Flash to iPhone</title><content type='html'>&lt;b&gt;iPhone == anathema&lt;/b&gt;&lt;br /&gt;So I've had to do some work with the iPhone regarding apps and that means looking at &lt;a href="http://en.wikipedia.org/wiki/Objective_c"&gt;objective-C&lt;/a&gt;. &amp;nbsp;First let me suck yuck. &amp;nbsp;I started playing with OC code the other night when 1995 called and asked if it could please have its programming language back. &amp;nbsp;Anyway, as a Java programmer and a Flex programmer I have to say OC is nasty for gui dev and for basic app dev work. &amp;nbsp;Maybe it is really good for larger applications? &amp;nbsp;I don't know, but for little mobile apps it is cumbersome, ugly and overly fiddly. &amp;nbsp;That aside this post is actually about integrating an iPhone app into an existing Flex multi-media app, so I'll leave the OC coding to Mac Fanboys (as I'm a Java fanboy) and move on.&lt;br /&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;Flash Video Encoding&lt;/b&gt;&lt;br /&gt;The existing app I have is a social networking site that allows you to stream live audio and video to an audience. &amp;nbsp;No, this is not a porn web cam, it is for musicians to broadcast their jam sessions or min-gigs to their fans.&lt;br /&gt;So the performer logs on, clicks "yes allow flash to use my camera" and they start streaming to the server. &amp;nbsp;A fan logs on and clicks on the "watch my fave act" and they get the performer's stream playing for their pleasure. &amp;nbsp;The iPhone extension allows a person to watch the same performance on their iPhone rather than on their Flash enabled device (as in everything else in the entire universe, including TVs and refrigerators).&lt;br /&gt;But, there is a problem: &amp;nbsp;iPhone only reads &lt;a href="http://en.wikipedia.org/wiki/H.264/MPEG-4_AVC"&gt;H264&lt;/a&gt; encoded video with aac / mp3 audio. &amp;nbsp;Flash will also read that, but at present only creates Sorenson Spark / Speex (in my case) encoded video / audio. &amp;nbsp;That means the performer is producing a stream that is unintelligible to the iPhone. &amp;nbsp;There is, to my knowledge, no way to load additional codecs onto the iPhone and I don't think Apple will let you write an app that does. &amp;nbsp;So how the heck do you let an iPhone participate in these new fangled interwebs that Steve Jobs has heard so much about (and yet doesn't like)? &amp;nbsp;Oh and yes I am being contentious on purpose because I'm like that.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Transcoding&lt;/b&gt;&lt;br /&gt;The magic word we are looking for is transcoder. &amp;nbsp;We take the flash video in one end and spit out h264. &amp;nbsp;This is easier said than done. &amp;nbsp;There is only one transcoder in the whole wide world, I know you don't believe me but it's true, and it is called FFMpeg. &amp;nbsp;Just try to find another application / library able to convert videos via a command line or with an API, go on I dare you.&lt;br /&gt;Somewhere in my back end I need to use FFMpeg to create the iPhone stream otherwise I can just kiss my rich-media app goodbye.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Xuggler&lt;/b&gt;&lt;br /&gt;Hooray for the Java open source community! &amp;nbsp;There is a lovely Java JNI wrapper for &lt;a href="http://www.ffmpeg.org/"&gt;FFMpeg &lt;/a&gt;called &lt;a href="http://www.xuggle.com/"&gt;Xuggler&lt;/a&gt;. &amp;nbsp;It even has some guts in it that let you read a stream from an rtmp source and publish back to an rtmp location. &amp;nbsp;As it happens my back end uses &lt;a href="http://www.wowzamedia.com/"&gt;Wowza &lt;/a&gt;media server, also Java, so it is fairly easy to call Xuggler from a Wowza application. &amp;nbsp;What isn't so easy is finding any halfway decent documentation for Wowza. Also, Wowza isn't open source so it is a bit of a mystery as to what happens inside it. &amp;nbsp;I think there might be elves involved. &amp;nbsp;Anyway here is the basic glue I used (which I stole from a forum post on the Wowza forums, so thank whoever that was):&lt;br /&gt;&lt;br /&gt;&lt;b&gt;public class TheApp extends ModuleBase {&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; ... stuff ...&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; public void publish(IClient client, RequestFunction function, AMFDataList params){&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;IMediaStream stream = getStream(client, function);&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;stream.addClientListener(new StreamWatcher());&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;ModuleCore.publish(client, function, params);&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; }&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; ... other stuff ...&lt;/b&gt;&lt;br /&gt;&lt;b&gt;}&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Ok so the above code adds a listener to the stream so that when the stream is actually published I get notified and a method in the StreamWatcher class is called (that's my class BTW)&lt;br /&gt;&lt;br /&gt;&lt;b&gt;public class StreamWatcher implements IMediaStreamActionNotify2 {&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;public void onPublish(IMediaStream stream, String streamName, boolean isRecord, boolean isAppend) {&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;H264Transcoder tc = new H264Transcoder(stream.getName());&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;tc.start();&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; }&lt;/b&gt;&lt;br /&gt;&lt;b&gt;}&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Above you can see that when the stream is publish I create a new transcoder (which is a thread) and start it.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;public class H264Transcoder extends Thread {&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; public void run() {&lt;/b&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;b&gt;  &lt;/b&gt;&lt;/span&gt;&lt;b&gt;try {&lt;/b&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;b&gt;   &lt;/b&gt;&lt;/span&gt;&lt;b&gt;Thread.sleep(5000L);&lt;/b&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;b&gt;   &lt;/b&gt;&lt;/span&gt;&lt;b&gt;RTMPPublishH264();&lt;/b&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;b&gt;  &lt;/b&gt;&lt;/span&gt;&lt;b&gt;} catch (ParseException e) {&lt;/b&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;b&gt;   &lt;/b&gt;&lt;/span&gt;&lt;b&gt;System.out.println(e);&lt;/b&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;b&gt;  &lt;/b&gt;&lt;/span&gt;&lt;b&gt;} catch (InterruptedException e) {&lt;/b&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;b&gt;   &lt;/b&gt;&lt;/span&gt;&lt;b&gt;e.printStackTrace();&lt;/b&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;b&gt;  &lt;/b&gt;&lt;/span&gt;&lt;b&gt;}&lt;/b&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;b&gt; &lt;/b&gt;&lt;/span&gt;&lt;b&gt;}&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; public void testRTMPPublishH264() throws ParseException {&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;Converter converter = new Converter();&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;Options options = converter.defineOptions();&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;String args = ""&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; /* ffmpeg -i rtmp://www.site.com.au:1935/ppv/streamName&lt;/b&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;b&gt;  &lt;/b&gt;&lt;/span&gt;&lt;b&gt; * -re&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;b&gt;  &lt;/b&gt;&lt;/span&gt;&lt;b&gt; * -acodec libfaac&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;b&gt;  &lt;/b&gt;&lt;/span&gt;&lt;b&gt; * -ar 22050&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;b&gt;  &lt;/b&gt;&lt;/span&gt;&lt;b&gt; * -vcodec libx264&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;b&gt;  &lt;/b&gt;&lt;/span&gt;&lt;b&gt; * -fpre default.ffpreset&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;b&gt;  &lt;/b&gt;&lt;/span&gt;&lt;b&gt; * -f flv rtmp://www.site.com.au:1935/live2/streamName-iphone&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; */&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;CommandLine cmdLine = converter.parseOptions(options, args);&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;converter.run(cmdLine);&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; }&lt;/b&gt;&lt;br /&gt;&lt;b&gt;}&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Wow, so the above isn't complete, but I've included the really important bits. &amp;nbsp;The reason it isn't complete is because I haven't perfected it yet. &amp;nbsp;Maybe I never will. &amp;nbsp;Oh, who am I kidding, of course I will, but not just now.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Important Points&lt;/b&gt;&lt;br /&gt;The important points for this:&lt;br /&gt;Xuggler can't connect to specific application instances, only to the _definst_ instance. &amp;nbsp;That means the rtmp url above has an application called "ppv" in which a stream called "streamName" is published. &amp;nbsp;Normally I'd have a named app instance that I'd publish into so I could track shared objects and the like, but that didn't work with Xuggler. &amp;nbsp;Also the output stream I've called streamName-iphone, is in a different application, again the _definst_ instance. &amp;nbsp;The reason for that is my ppv application has a bunch of rich user interaction stuff like chat and it gets info from the server about the performer and the people involved need to have valid accounts and so on. &amp;nbsp;I've only included the actual ffmpeg command line code that I have personally used and it works. &amp;nbsp;The preset I used is the default one that comes with Xuggler for h264 so I'll probably have to tweak that to get the best performance.&lt;br /&gt;&lt;br /&gt;I may add an update post once I get all the kinks worked out. &amp;nbsp;For example I'd love to package this up as a simple jar + config arrangement, but I do have to work for a living so I may not get the time.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/532350380457944298-9007348350769230825?l=cogimp.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://cogimp.blogspot.com/feeds/9007348350769230825/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://cogimp.blogspot.com/2010/10/pushing-live-streams-from-flash-to.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/9007348350769230825'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/9007348350769230825'/><link rel='alternate' type='text/html' href='http://cogimp.blogspot.com/2010/10/pushing-live-streams-from-flash-to.html' title='Pushing Live Streams From Flash to iPhone'/><author><name>Scion Mandate</name><uri>http://www.blogger.com/profile/12674405150380723269</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://2.bp.blogspot.com/_XLDCiZjHHWY/SWVovcbR5NI/AAAAAAAAAAM/Hz6N7P379jQ/S220/myUglyMug.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-532350380457944298.post-8644750044605231003</id><published>2010-08-26T19:25:00.000-07:00</published><updated>2010-08-26T19:26:01.820-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='flex'/><category scheme='http://www.blogger.com/atom/ns#' term='timezone'/><category scheme='http://www.blogger.com/atom/ns#' term='utc'/><category scheme='http://www.blogger.com/atom/ns#' term='date'/><title type='text'>A Date with Flex</title><content type='html'>&lt;b&gt;Date Objects&lt;/b&gt;&lt;br /&gt;So in computing we always need to deal with dates. &amp;nbsp;All my tables for example have created date and modified date columns so each record is timestamped. &amp;nbsp;Then there are dates for reports, dates for events and schedules and calendar dates for people to deal with. &amp;nbsp;But why is it there is always so much trouble dealing with dates?&lt;br /&gt;&lt;b&gt;A Date is Unique&lt;/b&gt;&lt;br /&gt;At its heart the Date object is an unique instant in time. &amp;nbsp;It is universal and absolute. &lt;br /&gt;What does that mean?&lt;br /&gt;It means that, say in Java when I say Date myDate = new Date(); &amp;nbsp;I am creating an object that stores the moment of its own creation. &amp;nbsp;The date doesn't matter what timezone I'm in, it doesn't matter what language I speak or anything like that. &amp;nbsp;Deep inside the Date object, and this holds true regardless of programming language or database, there is a long integer that is the milliseconds since an epoch. &amp;nbsp;The usual epoch is 1/1/1970 which is often called &lt;a href="http://en.wikipedia.org/wiki/Unix_time"&gt;Unix time&lt;/a&gt;, but it could also be &lt;a href="http://en.wikipedia.org/wiki/Coordinated_Universal_Time"&gt;UTC &lt;/a&gt;which was in 1972. &amp;nbsp;It isn't all that important when the epoch was. &amp;nbsp;The important point is there is no timezone offset. &amp;nbsp;Timezone and locale are human decorators added to a Date to make it relative to the human's location. &amp;nbsp;Regardless of where the computer is or the person programming it the number of milliseconds since epoch is the same. &amp;nbsp;If it is 10am for me in Australia and I call my friend in Greenwich (who won't be pleased) they would tell me it was 2am. &amp;nbsp;It is actually the same Date, the same time, the same instant in existence and it is that which the Date object captures. &amp;nbsp;We then apply formatting to that Date to produce something relative to where we are.&lt;br /&gt;&lt;b&gt;So What?&lt;/b&gt;&lt;br /&gt;This is important because it means that if I need to create a date for my friend in Greenwich, for example I need to set an alarm for them to wake up at 8am things get twisted. &amp;nbsp;In Australia, if I just create a Date and set the hour to 8am and save it the alarm will go off at midnight! &amp;nbsp;So I need to see a Greenwich clock and select 8am on that. &amp;nbsp;In Java I can do something like this:&lt;br /&gt;&lt;blockquote&gt;Calendar c = GregorianCalendar.getInstance(zone);&lt;/blockquote&gt;Where zone is Greenwich timezone. &amp;nbsp;Then I can set the hour on the calendar to 8am and get the Date from the calendar and bingo it's done.&lt;br /&gt;&lt;b&gt;Flex&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;a href="http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/Date.html?allClasses=1"&gt;Flex&lt;/a&gt;&amp;nbsp;&lt;/b&gt;has a Date object but no Calendar. &amp;nbsp;So when I create a Date object in flex it automagically get the client computer's timezone/locale settings. &amp;nbsp;Inside the Date it is still milliseconds since epoch and you can get that by using the various UTC related methods to get the universal time. &amp;nbsp;The date picker and date formatter all by default work of locale dates / times. &amp;nbsp;That means you need to write your own code to display dates in other timezones. &amp;nbsp;In the above example where I need to set an alarm for my friend in Greenwich I would need to make sure that I was viewing the Date in the correct timezone.&lt;br /&gt;&lt;b&gt;How?&lt;/b&gt;&lt;br /&gt;There are a number of ways you can do it but I think it boils down to two ways which are both correct depending on your exact situation.&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Enter and send the date / time as a string. &amp;nbsp;It is pretty easy for me to simply type 8am and send that as a string to be stored and seen by my friend. &amp;nbsp;And if all I wanted to do was tell him when I'd set his alarm that would be fine. &amp;nbsp;However what if the server and/or client needed to operate on that time? &amp;nbsp;For example I actually needed to tell the computer what time to play the alarm and also how long the snooze lasts and so on? &amp;nbsp;I'd need to start parsing the string and I'd have to tell the computer what timezone the string was in. &amp;nbsp;If I was setting lots of alarms for people all over the world things would get very messy very quickly. So...&lt;/li&gt;&lt;li&gt;On my client I have a timezone picker that selects what timezone to display dates to me in. &amp;nbsp;When I want to set the 8am alarm for my friend in Greenwich I tell my client I want to choose dates in Greenwich time, pick the date and send it. &amp;nbsp;Behind the scenes I will have selected the date that locally would look something like 4pm to me, but would equate to 8am to my friend. &amp;nbsp;Either way it is the same number of milliseconds since epoch.&lt;/li&gt;&lt;/ol&gt;&lt;b&gt;OMG WTF?&lt;/b&gt;&lt;br /&gt;Yeah it can get confusing. &amp;nbsp;Because all the date stuff in Flex hides the timezone you need to do some actual work if you want to operate in timezones other than what is set on your computer. &amp;nbsp;To do the modification might look like this:&lt;br /&gt;&lt;blockquote&gt;var selectedDate : Date = new Date(2010,7,27,8); // 8am 27th Aug 2010 (month is 0 based index)&lt;/blockquote&gt;But that date is in my timezone which is no good. &amp;nbsp;But what if I do this:&lt;br /&gt;&lt;blockquote&gt;selectedDate.minutes -= selectedDate.timezoneOffset;&lt;/blockquote&gt;&lt;blockquote&gt;selectedDate.minutes += myFriendsTimezoneOffset;&lt;/blockquote&gt;In the first line I subtract my timezone offset. &amp;nbsp;The timezone offset is the number of minutes difference between my timezone and UTC (or +0 hours). &amp;nbsp;So if I subtract the timezone offset to the selected date's minutes I will get a Date that is the time I selected (8am) as it would be in UTC, that is 8am UTC which, since I'm in Australia at +8hrs means selectedDate would be 4pm my time.&lt;br /&gt;In the second line I add my friend's timezone offset. &amp;nbsp;This will make the Date 8am in his timezone (which just happens to be +0 since he is at Greenwich). &amp;nbsp;The important thing to note is that the selectedDate object now has a UTC time (milliseconds since epoch) that is equivalent to 8am Greenwich and 4pm Australia time. &amp;nbsp;It can be confusing but stick with it.&lt;br /&gt;&lt;b&gt;Over The Wire&lt;/b&gt;&lt;br /&gt;When you &lt;a href="http://en.wikipedia.org/wiki/BlazeDS"&gt;serialize &lt;/a&gt;the date and send it to the server you are actually sending a UTC time. &amp;nbsp;There is no timezone sent with the Date. &amp;nbsp;If you remember a Date is an instance in time that is unique and absolute, a timezone / locale is something we decorate that Date with so it is relative to us humans. &amp;nbsp;Computers need a single representation of Date so they can compare them and sort them and perform calculations on them. &amp;nbsp;If you have chosen a Date and it is important that other people know what timezone you were in when you chose that Date you need to communicate this separately.&lt;br /&gt;I hope this has helped someone.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/532350380457944298-8644750044605231003?l=cogimp.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://cogimp.blogspot.com/feeds/8644750044605231003/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://cogimp.blogspot.com/2010/08/date-with-flex.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/8644750044605231003'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/8644750044605231003'/><link rel='alternate' type='text/html' href='http://cogimp.blogspot.com/2010/08/date-with-flex.html' title='A Date with Flex'/><author><name>Scion Mandate</name><uri>http://www.blogger.com/profile/12674405150380723269</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://2.bp.blogspot.com/_XLDCiZjHHWY/SWVovcbR5NI/AAAAAAAAAAM/Hz6N7P379jQ/S220/myUglyMug.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-532350380457944298.post-4059583759664438988</id><published>2010-06-09T19:49:00.000-07:00</published><updated>2010-06-09T20:38:02.274-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='flex'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='file upload'/><title type='text'>Flex File Upload to End All Uploads</title><content type='html'>&lt;b&gt;Back to the Future&lt;/b&gt;&lt;br /&gt;I previously said I had implemented an FTP based solution for file upload. &amp;nbsp;Well I did and it works pretty well but it's a bit fiddly and tricky to get working so I kept looking for solutions. &amp;nbsp;I found one. &amp;nbsp;Hooray! &amp;nbsp;Flex 4 and FileReference in Flash 10 with Blaze provide the solution.&lt;br /&gt;&lt;b&gt;Big Irritation&lt;/b&gt;&lt;br /&gt;A big irritation of mine with http uploads using multi part form stuff is that it is an all or nothing proposal. &amp;nbsp;Once you fire up the connection and start uploading you have to wait for the whole thing to go up. &amp;nbsp;For small files this isn't such a problem but if you upload a file that takes a minute or two you often don't know if it is working (from a client perspective) until you decide that 3 hours is too long for a file to take. &amp;nbsp;There are heaps of issues like socket disconnected by peer, socket read timeout and so forth. &amp;nbsp;To get things working you need to check special upload timeouts and browser timeouts and virus checkers / indexers on the server and other mysterious Mac OS X related muffery. &amp;nbsp;To use file upload over http in a true enterprise application you need some guarantees that the upload will happen and that data isn't lost. &amp;nbsp;You need a reliable system so you can tell your paying customers that their data will get to the server and if it doesn't you know what happened and you can fix it and keep going. &amp;nbsp;Anything less is amateur hour mickey mouse stuff.&lt;br /&gt;&lt;b&gt;Flash 10 FileReference&lt;/b&gt;&lt;br /&gt;Flash player 10 allows you to call fileReference.load() to load a selected file. &amp;nbsp;This puts all the file's bytes into the data property. &amp;nbsp;You can then send that as a byte[] through AMF via Blaze to the server where you can save the file. &amp;nbsp;The problem with this simplistic approach is you don't get any progress events and if it is a big file you still end up with all the problems of http upload. &amp;nbsp;The solution is really simple; break the byte array into manageable chunks and send them up one at a time waiting for a response between each one. &amp;nbsp;This way you get a progress event and the system doesn't just freeze up until it's finished. &amp;nbsp;If you are clever you can also enable pause / resume.&lt;br /&gt;&lt;b&gt;I'm Clever&lt;/b&gt;&lt;br /&gt;Yeah well so my mother says anyway. &amp;nbsp;But I wrote a nice little flex library and java library which go together to provide file upload services. &amp;nbsp;The flex part takes a byteArray and file name and queries the java service to see what progress the file is up to. &amp;nbsp;The java service replies with a progress event which either says 0 bytes uploaded, some bytes uploaded or complete. &amp;nbsp;You can use that to initialise your progress bar in flex. &amp;nbsp;Then the flex library moves to the correct position in the byteArray and sends a chunk up to java which is written to file and a response sent with progress info. &amp;nbsp;This continues until the file is complete at which point the flex library dispatches a complete event and you can do whatever you like with the file. &amp;nbsp;The java service has some methods that let you get an input stream to the uploaded file and also delete the file once your done with it. &amp;nbsp;If the user cancels midway through the upload (either on purpose or due to error) they can re-upload the file and it will resume from where you left off. &amp;nbsp;Errors are also communicated back to the flex client so you can auto retry or do whatever. &amp;nbsp;Hopefully I'll attach this stuff to my blog. &amp;nbsp;If you like it or find bugs (you probably will) then let me know.&lt;br /&gt;LINKS:&lt;br /&gt;&lt;a href="http://docs.google.com/leaf?id=0B4QEizxKbeAbMGQzMmIwODUtYjI3MS00NWZiLTk0M2ItMGFhODJjM2M3ZDcy&amp;amp;hl=en"&gt;applicationContext-upload.xml&lt;/a&gt;&lt;br /&gt;&lt;a href="http://docs.google.com/leaf?id=0B4QEizxKbeAbNDcwOWIxZTktYmNjOC00MzdiLTliMTktZjgwZDdiMzJhYWQ0&amp;amp;hl=en"&gt;FlexFileUpload.swc&lt;/a&gt;&lt;br /&gt;&lt;a href="http://docs.google.com/leaf?id=0B4QEizxKbeAbMjQ1Y2Y2MDItYjYwOS00YmQwLThjZGItOGMyMGZlMTRlOTlk&amp;amp;hl=en"&gt;reignite-upload.jar&lt;/a&gt;&lt;br /&gt;&lt;a href="http://docs.google.com/leaf?id=0B4QEizxKbeAbNzk0ODljZjYtMjQzNS00YjBkLWIwYjctMDhhYmRiNTQ2NGZh&amp;amp;hl=en"&gt;fileupload.xml&lt;/a&gt;&lt;br /&gt;&lt;b&gt;HOW?&lt;/b&gt;&lt;br /&gt;add the jar file to your lib folder&lt;br /&gt;include the swc in your flex project&lt;br /&gt;fileupload.xml goes in your web root or docroot whatever you call it&lt;br /&gt;applicationContext-upload.xml is a spring context file so put it where you load them from&lt;br /&gt;If you don't use spring you should, but if you don't, you can just create a facade that adapts the upload service and creates an instance and whatever. &amp;nbsp;Hey you're at least as clever as me so either use spring or take 5 minutes to figure out a way around it (I did for another project that used ejb)&lt;br /&gt;in your remoting-config.xml&lt;br /&gt;&lt;br /&gt;&amp;lt;destination id="fileUpload"&amp;gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;&amp;lt;properties&amp;gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;  &lt;/span&gt;&amp;lt;factory&amp;gt;spring&amp;lt;/factory&amp;gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;  &lt;/span&gt;&amp;lt;source&amp;gt;uploadService&amp;lt;/source&amp;gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;&amp;lt;/properties&amp;gt;&lt;br /&gt;&amp;lt;/destination&amp;gt;&lt;br /&gt;You'll see I'm using the spring factory, if you don't have it do a google search for spring factory for blaze there's also an ejb factory if you are that way inclined.&lt;br /&gt;The fileupload.xml has 2 things in it. &amp;nbsp;The first is the name of the destination you just configured in the remoting-config.xml and the other is the endpoint context path for your channel of choice.&lt;br /&gt;Now to use it in your code:&lt;br /&gt;&amp;nbsp;&amp;nbsp;1. Create an instance of the au.com.reignite.upload.FileUploadManager and remember to declare it in a place that will have scope for the duration of your upload. &amp;nbsp;Same as FileReference below. &amp;nbsp;You need to pass in some start up arguments when creating FileUploadManager: url to fileupload.xml, securityToken which you wont use so hand in null and repositoryId which is a unique identifier for this user's uploads. &amp;nbsp;I use user id for this so the same user can resume their upload. &amp;nbsp;If you don't use a different id for each user then you'll get stuffed up files if more than one user uploads the same file at the same time.&lt;br /&gt;&amp;nbsp;&amp;nbsp;2. create a FileReference and get the &amp;nbsp;user to select a file. &amp;nbsp;Once selected call load(). &amp;nbsp;Once complete you can do the rest.&lt;br /&gt;&amp;nbsp;&amp;nbsp;3. Once the file reference has done its load and you have the data do this:&lt;br /&gt;&lt;br /&gt;uploadManager.addEventListener(FileUploadErrorEvent.UPLOAD_ERROR, handleUploadError);&lt;br /&gt;uploadManager.addEventListener(ProgressEvent.PROGRESS, fileUploadProgressHandler);&lt;br /&gt;uploadManager.addEventListener(FileUploadCompleteEvent.COMPLETE, completeUpload);&lt;br /&gt;These are the events that get thrown so you had better handle them. &amp;nbsp;Pretty self explanatory really.&lt;br /&gt;&amp;nbsp;&amp;nbsp;4. &amp;nbsp;call uploadManager.upload(fileRef.data, fileRef.name); which starts it all off. &amp;nbsp;You'll get at least one progress event and a maximum of one complete event.&lt;br /&gt;IF a user cancels or there is an error and you want to start over make sure you call uploadManager.cancel() otherwise you'll break it. &amp;nbsp;So that means if they close the popup your doing the upload in also make sure you call cancel otherwise the upload continues.&lt;br /&gt;Maybe if I get bored I'll post an example app.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/532350380457944298-4059583759664438988?l=cogimp.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://cogimp.blogspot.com/feeds/4059583759664438988/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://cogimp.blogspot.com/2010/06/flex-file-upload-to-end-all-uploads.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/4059583759664438988'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/4059583759664438988'/><link rel='alternate' type='text/html' href='http://cogimp.blogspot.com/2010/06/flex-file-upload-to-end-all-uploads.html' title='Flex File Upload to End All Uploads'/><author><name>Scion Mandate</name><uri>http://www.blogger.com/profile/12674405150380723269</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://2.bp.blogspot.com/_XLDCiZjHHWY/SWVovcbR5NI/AAAAAAAAAAM/Hz6N7P379jQ/S220/myUglyMug.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-532350380457944298.post-6367263005334862991</id><published>2010-06-01T22:37:00.000-07:00</published><updated>2010-06-01T22:37:31.376-07:00</updated><title type='text'>I'm back with egg on my face and all</title><content type='html'>So I figured I was posting off into oblivion happily talking to myself but it turns out some people read my blog and commented and I didn't think to check back. &amp;nbsp;Oops.&lt;br /&gt;Anyway the good news is I have bonus solutions to tricky problems.&lt;br /&gt;&lt;b&gt;Clustered Blaze Messaging III&lt;/b&gt;&lt;br /&gt;I previously talked about a couple of different ways to get around the problem of having Blaze behind a hardware firewall that did round robin load balancing.&lt;br /&gt;&lt;blockquote&gt;Just a recap: the client subscription lives with the servlet context on the machine they first sent their subscription to. &amp;nbsp;This means when their next poll request hits the load balancer they tend to get fired off to the other server and have no subscription. &amp;nbsp;I tried to use JGroups and Tomcat session replication but both methods suck a fair bit and kept falling over.&lt;/blockquote&gt;The answer to my woes has been database guaranteed subscription and message queuing.&lt;br /&gt;&lt;b&gt;In Brief&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;/b&gt;Client A sends subscribe request to server A. &amp;nbsp;Server A creates an entry in the database (replicated using mysql replication) for that subscription. &amp;nbsp;Client A then polls and hits server B which looks in the database for a subscription and finds it. &amp;nbsp;Server B then checks the database message queue for any messages client A has not received and sends them or waits the prescribed time for a message to enter the queue.&lt;br /&gt;So you see the messages get stored in a database table and the subscription keeps track of the last message id received. &amp;nbsp;Join the dots and you've got a messaging system that relies on MySQL replication (or whatever DB you are using) which is very reliable. &amp;nbsp;I say reliable because in the nearly 2 years this particular MySQL replication set up has been operating it only got out of sync once and that was due to an OS failure (at Christmas while I was on leave of course so I was called up and spent a lovely holiday reinitialising databases instead of drinking beer and stuffing my gob).&lt;br /&gt;&lt;b&gt;Now I will try to add the code:&lt;/b&gt;&lt;br /&gt;In messaging-config.xml:&lt;br /&gt;&lt;br /&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;br /&gt;&amp;lt;service id="message-service" class="flex.messaging.services.MessageService"&amp;gt;&lt;br /&gt;&amp;lt;adapters&amp;gt;&lt;br /&gt;&amp;lt;adapter-definition id="actionscript" class="au.com.reignite.amf.DatabaseMessagingAdapter"&lt;br /&gt;default="true" /&amp;gt;&lt;br /&gt;&amp;lt;/adapters&amp;gt;&lt;br /&gt;&amp;lt;default-channels&amp;gt;&lt;br /&gt;&amp;lt;channel ref="my-polling-amf" /&amp;gt;&lt;br /&gt;&amp;lt;/default-channels&amp;gt;&lt;br /&gt;&amp;lt;destination id="projectLockService"/&amp;gt;&lt;br /&gt;&amp;lt;/service&amp;gt;&lt;br /&gt;Everything is pretty normal except the DatabaseMessagingAdapter which is the thing that does the database magic.&lt;br /&gt;There's a stack of stuff in the DatabaseMessagingAdapter that is a bit specific to my particular architecture so I'll just show the important bits:&lt;br /&gt;DatabaseMessagingAdapter&lt;br /&gt;&lt;br /&gt;&lt;i&gt;public class DatabaseMessagingAdapter extends MessagingAdapter&lt;/i&gt;&lt;br /&gt;Obviously I extend the MessagingAdapter.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;@Override&lt;/i&gt;&lt;br /&gt;&lt;i&gt;public Object invoke(Message message) {&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;ByteArrayOutputStream bos = new ByteArrayOutputStream();&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;ObjectOutputStream out;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;try {&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;  &lt;/span&gt;out = new ObjectOutputStream(bos);&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;  &lt;/span&gt;out.writeObject(message);&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;  &lt;/span&gt;getMessageOrchestrator().storeMessage(bos.toByteArray(),&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;    &lt;/span&gt;message.getBody().toString());&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;} catch (IOException e) {&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;  &lt;/span&gt;LogWriter.error(getClass(), "Failed to serialize message: " + e, e);&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt; &lt;/span&gt;return null;&lt;br /&gt;}&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;Here I override the invoke() method because it is what Blaze calls when it wants to store the message in its internal store. &amp;nbsp;Instead you can see I serialize the message to a byte array and use my "MessageOrchestroator" to store it. &amp;nbsp;This is just my class that saves the bytes to the database in the following table:&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;DROP TABLE IF EXISTS `truenorth`.`blaze_message`;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;CREATE TABLE &amp;nbsp;`truenorth`.`blaze_message` (&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;&amp;nbsp;&amp;nbsp;`ID` int(10) unsigned NOT NULL AUTO_INCREMENT,&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;&amp;nbsp;&amp;nbsp;`MESSAGE_BYTES` blob NOT NULL,&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;&amp;nbsp;&amp;nbsp;`MESSAGE_BODY` text,&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;&amp;nbsp;&amp;nbsp;`CREATED_DATE` datetime NOT NULL,&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;&amp;nbsp;&amp;nbsp;`MODIFIED_DATE` datetime NOT NULL,&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;&amp;nbsp;&amp;nbsp;PRIMARY KEY (`ID`)&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;) ENGINE=InnoDB AUTO_INCREMENT=54 DEFAULT CHARSET=latin1;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;My messages are in XML so I store the message bytes and the message body as a string so I can inspect them with my greedy eyes.&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;Back to DatabaseMessagingAdapter:&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;@Override&lt;/i&gt;&lt;br /&gt;&lt;i&gt;public boolean handlesSubscriptions() {&lt;/i&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;i&gt; &lt;/i&gt;&lt;/span&gt;&lt;i&gt;return true;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;}&lt;/i&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;Of course it handles subscriptions too! &amp;nbsp;so I return true (the default is false).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;@Override&lt;/i&gt;&lt;br /&gt;&lt;i&gt;public Object manage(CommandMessage commandMessage) {&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;&amp;nbsp;if (commandMessage.getOperation() == CommandMessage.SUBSCRIBE_OPERATION) {&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;div style="display: inline !important;"&gt;&lt;/div&gt;&lt;div style="display: inline !important;"&gt;&lt;/div&gt;&lt;div style="display: inline !important;"&gt;&lt;/div&gt;&lt;div style="display: inline !important;"&gt;&lt;/div&gt;&lt;div style="display: inline !important;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;  &lt;/span&gt;MessageDestination destination = (MessageDestination) getDestination();&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;/i&gt;&lt;i&gt;&lt;/i&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;i&gt;  &lt;/i&gt;&lt;/span&gt;&lt;i&gt;SubscriptionManager subscriptionManager = destination&lt;/i&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;i&gt;    &lt;/i&gt;&lt;/span&gt;&lt;i&gt;.getSubscriptionManager();&lt;/i&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;i&gt;  &lt;/i&gt;&lt;/span&gt;&lt;i&gt;String selectorExpr = (String) commandMessage&lt;/i&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;i&gt;    &lt;/i&gt;&lt;/span&gt;&lt;i&gt;.getHeader(CommandMessage.SELECTOR_HEADER);&lt;/i&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;i&gt;  &lt;/i&gt;&lt;/span&gt;&lt;i&gt;subscriptionManager.removeSubscriber(commandMessage.getClientId(),&lt;/i&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;i&gt;    &lt;/i&gt;&lt;/span&gt;&lt;i&gt;selectorExpr, (String) commandMessage&lt;/i&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;i&gt;      &lt;/i&gt;&lt;/span&gt;&lt;i&gt;.getHeader(AsyncMessage.SUBTOPIC_HEADER_NAME),&lt;/i&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;i&gt;    &lt;/i&gt;&lt;/span&gt;&lt;i&gt;(String) commandMessage.getHeader(Message.ENDPOINT_HEADER));&lt;/i&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;i&gt;  &lt;/i&gt;&lt;/span&gt;&lt;i&gt;getMessageOrchestrator().storeSubscription(&lt;/i&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;i&gt;    &lt;/i&gt;&lt;/span&gt;&lt;i&gt;FlexContext.getFlexClient().getId(),&lt;/i&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;i&gt;    &lt;/i&gt;&lt;/span&gt;&lt;i&gt;commandMessage.getClientId().toString(), selectorExpr);&lt;/i&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;i&gt; &lt;/i&gt;&lt;/span&gt;&lt;i&gt;} else if (commandMessage.getOperation() == CommandMessage.UNSUBSCRIBE_OPERATION) {&lt;/i&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;i&gt;  &lt;/i&gt;&lt;/span&gt;&lt;i&gt;if (FlexContext.getFlexClient() != null) {&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;div style="display: inline !important;"&gt;&lt;/div&gt;&lt;div style="display: inline !important;"&gt;&lt;/div&gt;&lt;div style="display: inline !important;"&gt;&lt;/div&gt;&lt;div style="display: inline !important;"&gt;&lt;/div&gt;&lt;div style="display: inline !important;"&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;   &lt;/span&gt;getMessageOrchestrator().deleteSubscription(&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;/i&gt;&lt;i&gt;&lt;/i&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;i&gt;     &lt;/i&gt;&lt;/span&gt;&lt;i&gt;FlexContext.getFlexClient().getId());&lt;/i&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;i&gt;  &lt;/i&gt;&lt;/span&gt;&lt;i&gt;}&lt;/i&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;i&gt; &lt;/i&gt;&lt;/span&gt;&lt;i&gt;}&lt;/i&gt;&lt;br /&gt;&lt;span class="Apple-tab-span" style="white-space: pre;"&gt;&lt;i&gt; &lt;/i&gt;&lt;/span&gt;&lt;i&gt;return null;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;}&lt;/i&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;This is the bit that manages subscriptions, as darkly foreshadowed by me above. &amp;nbsp;If it is a subscribe operation I clear out Blazes storage of the subscription otherwise it annoys the heck out of me by denying people who shouldn't be denied. &amp;nbsp;By I grab my trusty MessageOrchestrator and store my own subscription in a table like:&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;DROP TABLE IF EXISTS `truenorth`.`subscription`;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;CREATE TABLE &amp;nbsp;`truenorth`.`subscription` (&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;&amp;nbsp;&amp;nbsp;`ID` int(10) unsigned NOT NULL AUTO_INCREMENT,&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;&amp;nbsp;&amp;nbsp;`CLIENT_ID` varchar(100) NOT NULL,&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;&amp;nbsp;&amp;nbsp;`MESSAGE_CLIENT_ID` varchar(100) NOT NULL,&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;&amp;nbsp;&amp;nbsp;`SELECTOR` varchar(255) DEFAULT NULL,&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;&amp;nbsp;&amp;nbsp;`EXPIRY_DATE` datetime NOT NULL,&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;&amp;nbsp;&amp;nbsp;`LAST_MESSAGE_ID` int(10) unsigned NOT NULL,&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;&amp;nbsp;&amp;nbsp;`CREATED_DATE` datetime NOT NULL,&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;&amp;nbsp;&amp;nbsp;`MODIFIED_DATE` datetime NOT NULL,&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;&amp;nbsp;&amp;nbsp;PRIMARY KEY (`ID`),&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;&amp;nbsp;&amp;nbsp;UNIQUE KEY `Index_CLIENT_ID_UNIQUE` (`CLIENT_ID`)&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;) ENGINE=InnoDB DEFAULT CHARSET=latin1;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;The important bit to note is I store the client_id which is the id of the instance of flash running on the client machine and the message_client_id which is the instance of the consumer object running in the flash client on the client machine. &amp;nbsp;You can also see I keep the last message id sent and some datetime things.&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;Ok so that's how I store subscriptions and messages. &amp;nbsp;The next piece of the puzzle is how I handle poll requests.&lt;/span&gt;&lt;br /&gt;&lt;b&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;Poll requests&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;In my services-config.xml I have a special endpoint for polling:&amp;nbsp;DatabaseConfigurableAMFEndpoint&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;public class DatabaseConfigurableAMFEndpoint extends ConfigurableAMFEndpoint {&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;I extend my own ConfigurableAMFEndpoint so I can use my super special filter chain to do my filthy security processing (see previous blogs)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;@Override&lt;br /&gt;protected FlushResult handleFlexClientPoll(FlexClient flexClient,&lt;br /&gt;&lt;div style="display: inline !important;"&gt;CommandMessage pollCommand) {&lt;/div&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;div style="display: inline !important;"&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;I'll skip including all the code because it's long and boring so I'll summarise.&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;/i&gt;&lt;i&gt;&lt;div style="display: inline !important;"&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;I load my subscription, which is the data stored in the above table not the Blaze subscription based on the flex client id (that's as opposed to the message client id). &amp;nbsp;If I don't get a record I throw a subscription error. &amp;nbsp;Assuming they have a subscription I start a "do" loop where I load up the messages stored in the above table that have an id greater than the subscription's last received message. &amp;nbsp;Each of those messages that match the subscription selector (I use the JMSSelector object that comes with Blaze just like Blaze does) I rehydrate and add to my results to send. &amp;nbsp;If there are any results I break the loop and immediately send them. &amp;nbsp;If there were no messages I wait a little bit (5 seconds) and try again. &amp;nbsp;I keep doing that until the configured poll timeout (50 seconds) at which point I release the thread and let the client poll again. &amp;nbsp;I then update their subscription with the last message they received and increment their expiry date.&lt;/span&gt;&lt;/div&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="display: inline !important;"&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;To finish it all off I have a scheduled task (I use quartz with spring) that every few minutes culls any expired subscriptions and deletes messages that all subscriptions have received.&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="display: inline !important;"&gt;&lt;b&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;Victory&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="display: inline !important;"&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;This method of messaging has been working now for about 10 months without failure. &amp;nbsp;The concerns I have about it that it is fairly heavy on hitting the DB so I made sure the DB is on the same machine as the app server and the two machines in the cluster are directly connected via Gigabit Ethernet on the same switch (actually 2 switches cross linked for redundancy). &amp;nbsp;I've also got a fair query cache set up so I'm not always pulling the same messages out of the DB and I'm only really catering to a max of about 10 concurrent users. &amp;nbsp;Having said all that I'd imagine I could have a bog load more concurrent users and still have no trouble. &amp;nbsp;A big benefit is the messages are held by the DB not in memory by the tomcat instance which means tomcat can fall over and people still get their messages.&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="display: inline !important;"&gt;&lt;b&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;Next Time&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="display: inline !important;"&gt;&lt;span class="Apple-style-span" style="font-style: normal;"&gt;I've also now come up with what I consider the ultimate file upload solution even more betterer than ftp and I've set my blog to email me when someone comments so I can actually respond.&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/i&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/532350380457944298-6367263005334862991?l=cogimp.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://cogimp.blogspot.com/feeds/6367263005334862991/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://cogimp.blogspot.com/2010/06/im-back-with-egg-on-my-face-and-all.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/6367263005334862991'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/6367263005334862991'/><link rel='alternate' type='text/html' href='http://cogimp.blogspot.com/2010/06/im-back-with-egg-on-my-face-and-all.html' title='I&apos;m back with egg on my face and all'/><author><name>Scion Mandate</name><uri>http://www.blogger.com/profile/12674405150380723269</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://2.bp.blogspot.com/_XLDCiZjHHWY/SWVovcbR5NI/AAAAAAAAAAM/Hz6N7P379jQ/S220/myUglyMug.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-532350380457944298.post-7226415213111408672</id><published>2009-10-26T19:17:00.000-07:00</published><updated>2009-10-26T19:42:06.626-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='flex'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='OS X'/><category scheme='http://www.blogger.com/atom/ns#' term='ftp'/><category scheme='http://www.blogger.com/atom/ns#' term='file upload'/><category scheme='http://www.blogger.com/atom/ns#' term='Mac'/><category scheme='http://www.blogger.com/atom/ns#' term='socket'/><category scheme='http://www.blogger.com/atom/ns#' term='apache'/><category scheme='http://www.blogger.com/atom/ns#' term='tomcat'/><title type='text'>Get around file upload in Flex and Java</title><content type='html'>&lt;b&gt;Http File Upload&lt;/b&gt;&lt;div&gt;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.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;The Scenario&lt;/b&gt;&lt;/div&gt;&lt;div&gt;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.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;The Fix&lt;/b&gt;&lt;/div&gt;&lt;div&gt;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?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Server Side&lt;/b&gt;&lt;/div&gt;&lt;div&gt;Hooray for &lt;a href="http://mina.apache.org/ftpserver/"&gt;Apache FTP Server&lt;/a&gt;.  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.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;UH OH Flex Sucks Big Ones&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="www.adobe.com/products/flex/"&gt;Flex&lt;/a&gt; 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.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Hooray For Open Source&lt;/b&gt;&lt;/div&gt;&lt;div&gt;"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.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;That's It&lt;/b&gt;&lt;/div&gt;&lt;div&gt;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.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/532350380457944298-7226415213111408672?l=cogimp.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://cogimp.blogspot.com/feeds/7226415213111408672/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://cogimp.blogspot.com/2009/10/get-around-file-upload-in-flex-and-java.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/7226415213111408672'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/7226415213111408672'/><link rel='alternate' type='text/html' href='http://cogimp.blogspot.com/2009/10/get-around-file-upload-in-flex-and-java.html' title='Get around file upload in Flex and Java'/><author><name>Scion Mandate</name><uri>http://www.blogger.com/profile/12674405150380723269</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://2.bp.blogspot.com/_XLDCiZjHHWY/SWVovcbR5NI/AAAAAAAAAAM/Hz6N7P379jQ/S220/myUglyMug.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-532350380457944298.post-7513690479341663753</id><published>2009-08-11T00:31:00.000-07:00</published><updated>2009-08-11T00:46:32.892-07:00</updated><title type='text'>New Improved Industrial Strength Whoop Tooshy</title><content type='html'>As advertised in my earlier blog that noone read I found my Blaze cluster solution didn't work.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;The Reason:&lt;/span&gt;&lt;br /&gt;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.&lt;br /&gt;Clearly I needed a solution, one that actually works (rather than the half arsed hack I did before).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;The Solution:&lt;/span&gt;&lt;br /&gt;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).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;All The Code:&lt;/span&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;CIAO.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/532350380457944298-7513690479341663753?l=cogimp.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://cogimp.blogspot.com/feeds/7513690479341663753/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://cogimp.blogspot.com/2009/08/new-improved-industrial-strength-whoop.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/7513690479341663753'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/7513690479341663753'/><link rel='alternate' type='text/html' href='http://cogimp.blogspot.com/2009/08/new-improved-industrial-strength-whoop.html' title='New Improved Industrial Strength Whoop Tooshy'/><author><name>Scion Mandate</name><uri>http://www.blogger.com/profile/12674405150380723269</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://2.bp.blogspot.com/_XLDCiZjHHWY/SWVovcbR5NI/AAAAAAAAAAM/Hz6N7P379jQ/S220/myUglyMug.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-532350380457944298.post-1086739549289197355</id><published>2009-07-19T06:29:00.000-07:00</published><updated>2009-08-11T00:31:18.675-07:00</updated><title type='text'>Hardware Load Balanced BlazeDS Cluster on Tomcat</title><content type='html'>&lt;span style="font-weight: bold;"&gt;Blaze DS:&lt;/span&gt;&lt;br /&gt;Blaze is an open source remoting and messaging system from &lt;a href="http://opensource.adobe.com/wiki/display/blazeds/BlazeDS/"&gt;Adobe&lt;/a&gt;.  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.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Clustering:&lt;/span&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;JGroups:&lt;/span&gt;&lt;br /&gt;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 &lt;tcpping&gt; element.  An important point to creating a JGroups cluster that can survive restarts and such is setting up the ports well.  &lt;br/&gt;&amp;lt;TCP ... start_port="7800" end_port="7804" ...&amp;gt;&lt;br/&gt;&lt;br /&gt;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 &lt;br/&gt;&amp;lt;TCPPING ... initial_hosts="10.1.1.1[7800]" port_range="5" num_initial_members="2" /&amp;gt;&lt;br/&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Tomcat Clusters:&lt;/span&gt;&lt;br /&gt;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).&lt;br /&gt;In your web.xml but &lt;distributable&gt; just below your &lt;display-name&gt; tags.  Then in the server.xml in the Tomcat conf directly uncomment the &lt;cluster classname="org.apache.catalina.ha.tcp.SimpleTcpCluster"&gt; 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 &lt;a href="http://tomcat.apache.org/tomcat-6.0-doc/cluster-howto.html"&gt;doco&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;The Cluster Problem:&lt;/span&gt;&lt;br /&gt;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 &lt;span style="font-style: italic;"&gt;is&lt;/span&gt; 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.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Solution:&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Warning, the following doesn't really work :(&lt;/span&gt;&lt;br /&gt;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.&lt;br /&gt;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.&lt;br /&gt;1. Create ConfigurableAMFEndpoint.java&lt;br /&gt;&lt;blockquote&gt;package flex.messaging.endpoints;&lt;br /&gt;&lt;br /&gt;import java.util.ArrayList;&lt;br /&gt;import java.util.Iterator;&lt;br /&gt;import java.util.List;&lt;br /&gt;&lt;br /&gt;import flex.messaging.config.ConfigMap;&lt;br /&gt;import flex.messaging.endpoints.amf.AMFFilter;&lt;br /&gt;import flex.messaging.endpoints.amf.EndPointAwareFilter;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;* @author surrey&lt;br /&gt;*&lt;br /&gt;*/&lt;br /&gt;public class ConfigurableAMFEndpoint extends AMFEndpoint {&lt;br /&gt;&lt;br /&gt; private List&lt;amffilter&gt; filters = new ArrayList&lt;amffilter&gt;();&lt;br /&gt;&lt;br /&gt; protected AMFFilter createFilterChain() {&lt;br /&gt;     AMFFilter firstFilter = super.createFilterChain();&lt;br /&gt;     AMFFilter lastFilter = getLastFilter(firstFilter);&lt;br /&gt;     for (AMFFilter filter : filters) {&lt;br /&gt;         // add this endpoint on those filters that take an endpoint.&lt;br /&gt;         if (filter instanceof EndPointAwareFilter) {&lt;br /&gt;             ((EndPointAwareFilter) filter).setEndpoint(this);&lt;br /&gt;         }&lt;br /&gt;         lastFilter.setNext(filter);&lt;br /&gt;     }&lt;br /&gt;     return firstFilter;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; private AMFFilter getLastFilter(AMFFilter filterChain) {&lt;br /&gt;     AMFFilter last = filterChain;&lt;br /&gt;     while (last.getNext() != null) {&lt;br /&gt;         last = last.getNext();&lt;br /&gt;     }&lt;br /&gt;     return last;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; public void initialize(String id, ConfigMap properties) {&lt;br /&gt;     super.initialize(id, properties);&lt;br /&gt;     ConfigMap filterMap = properties.getPropertyAsMap("filter-chain", null);&lt;br /&gt;     if (filterMap != null &amp;amp;&amp;amp; !filterMap.isEmpty()) {&lt;br /&gt;         List filterNames = filterMap.getPropertyAsList("filter-class",&lt;br /&gt;                 null);&lt;br /&gt;         if (filterNames != null) {&lt;br /&gt;             for (Iterator iter = filterNames.iterator(); iter.hasNext();) {&lt;br /&gt;                 String className = (String) iter.next();&lt;br /&gt;                 try {&lt;br /&gt;                     filters.add((AMFFilter) Class.forName(className)&lt;br /&gt;                             .newInstance());&lt;br /&gt;                 } catch (InstantiationException e) {&lt;br /&gt;                     log.error("Could not instantiate filter: " + className,&lt;br /&gt;                             e);&lt;br /&gt;                 } catch (IllegalAccessException e) {&lt;br /&gt;                     log.error("No public constructor for filter: "&lt;br /&gt;                             + className, e);&lt;br /&gt;                 } catch (ClassNotFoundException e) {&lt;br /&gt;                     log.error("Could not find filter class: " + className,&lt;br /&gt;                             e);&lt;br /&gt;                 }&lt;br /&gt;             }&lt;br /&gt;         }&lt;br /&gt;     }&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt;}&lt;/amffilter&gt;&lt;/amffilter&gt;&lt;/blockquote&gt;This endpoint extends the AMFEndpoint by allowing me to add extra filters to the Blaze filter chain.&lt;br /&gt;2. I modified MessageBrokerFilter to have:&lt;br /&gt;         if (next != null) {&lt;br /&gt;             next.invoke(context);&lt;br /&gt;         }&lt;br /&gt;&lt;br /&gt;         // Service the message.&lt;br /&gt;         outMessage = endpoint.serviceMessage(inMessage);&lt;br /&gt;So that the chain continues on if I've added any.&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;package flex.messaging.endpoints.amf;&lt;br /&gt;&lt;br /&gt;import flex.messaging.endpoints.AMFEndpoint;&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt;* An abstract AMFFilter that other filters should extend to gain access to&lt;br /&gt;* their endpoint. This is only populated when using a ConfigurableAMFEndpoint.&lt;br /&gt;*&lt;br /&gt;* @author Surrey&lt;br /&gt;*&lt;br /&gt;*/&lt;br /&gt;public abstract class EndPointAwareFilter extends AMFFilter {&lt;br /&gt;&lt;br /&gt; private AMFEndpoint endpoint;&lt;br /&gt;&lt;br /&gt; public AMFEndpoint getEndpoint() {&lt;br /&gt;     return endpoint;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; public void setEndpoint(AMFEndpoint endpoint) {&lt;br /&gt;     this.endpoint = endpoint;&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;4.. I created ClusterMessagingFilter.java&lt;br /&gt;&lt;blockquote&gt;package au.com.truenorth.amf.filter;&lt;br /&gt;&lt;br /&gt;import java.io.IOException;&lt;br /&gt;import java.lang.reflect.Array;&lt;br /&gt;import java.util.ArrayList;&lt;br /&gt;import java.util.HashMap;&lt;br /&gt;import java.util.Iterator;&lt;br /&gt;import java.util.List;&lt;br /&gt;import java.util.Map;&lt;br /&gt;&lt;br /&gt;import javax.servlet.http.HttpSession;&lt;br /&gt;&lt;br /&gt;import flex.messaging.Destination;&lt;br /&gt;import flex.messaging.FlexContext;&lt;br /&gt;import flex.messaging.MessageDestination;&lt;br /&gt;import flex.messaging.endpoints.amf.EndPointAwareFilter;&lt;br /&gt;import flex.messaging.io.amf.ActionContext;&lt;br /&gt;import flex.messaging.io.amf.MessageBody;&lt;br /&gt;import flex.messaging.messages.AsyncMessage;&lt;br /&gt;import flex.messaging.messages.CommandMessage;&lt;br /&gt;import flex.messaging.services.Service;&lt;br /&gt;&lt;br /&gt;public class ClusterMessagingFilter extends EndPointAwareFilter {&lt;br /&gt;&lt;br /&gt; public ClusterMessagingFilter() {&lt;br /&gt;     super();&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; @SuppressWarnings("unchecked")&lt;br /&gt; @Override&lt;br /&gt; public void invoke(ActionContext context) throws IOException {&lt;br /&gt;&lt;br /&gt;     Object data = getData(context);&lt;br /&gt;&lt;br /&gt;     if (data instanceof CommandMessage) {&lt;br /&gt;         CommandMessage command = (CommandMessage) data;&lt;br /&gt;         int operation = command.getOperation();&lt;br /&gt;         Object clientId = FlexContext.getFlexClient().getId();&lt;br /&gt;         String endpointId = getEndpoint().getId();&lt;br /&gt;&lt;br /&gt;         if (operation == CommandMessage.POLL_OPERATION) {&lt;br /&gt;             // add a subscription if appropriate&lt;br /&gt;             HttpSession sess = FlexContext.getHttpRequest().getSession();&lt;br /&gt;             Object att = sess&lt;br /&gt;                     .getAttribute(clientId.toString() + endpointId);&lt;br /&gt;             if (att != null &amp;amp;&amp;amp; att instanceof List) {&lt;br /&gt;                 List&lt;map&gt;&lt;string,&gt;&gt; destList = (List&lt;/string,&gt;&lt;/map&gt;&lt;map&gt;&lt;string,&gt;&gt;) att;&lt;br /&gt;                 for (Map&lt;string,&gt; subscribeMap : destList) {&lt;br /&gt;                     MessageDestination msgDest = getMessageDestination(subscribeMap);&lt;br /&gt;                     Object cId = subscribeMap.get("clientId");&lt;br /&gt;                     if (msgDest != null&lt;br /&gt;                             &amp;amp;&amp;amp; msgDest.getSubscriptionManager()&lt;br /&gt;                                     .getSubscriber(cId) == null) {&lt;br /&gt;                         Object selectorExpr = subscribeMap&lt;br /&gt;                                 .get("selectorExpr");&lt;br /&gt;                         String selectorString = getStringOrNull(selectorExpr);&lt;br /&gt;                         Object subtopic = subscribeMap&lt;br /&gt;                                 .get("subtopicString");&lt;br /&gt;                         String subtopicString = getStringOrNull(subtopic);&lt;br /&gt;&lt;br /&gt;                         msgDest.getSubscriptionManager().addSubscriber(cId,&lt;br /&gt;                                 selectorString, subtopicString,&lt;br /&gt;                                 subscribeMap.get("endpointId").toString());&lt;br /&gt;                     }&lt;br /&gt;                 }&lt;br /&gt;                 sess.removeAttribute(clientId.toString() + endpointId);&lt;br /&gt;             }&lt;br /&gt;         } else if (operation == CommandMessage.SUBSCRIBE_OPERATION) {&lt;br /&gt;             // get httpSession and stick a flag in with everything needed to&lt;br /&gt;             // make a subscription&lt;br /&gt;             Object att = FlexContext.getHttpRequest().getSession()&lt;br /&gt;                     .getAttribute(clientId.toString() + endpointId);&lt;br /&gt;             List&lt;/string,&gt;&lt;/string,&gt;&lt;/map&gt;&lt;map&gt;&lt;string,&gt;&gt; destList = (List&lt;/string,&gt;&lt;/map&gt;&lt;map&gt;&lt;string,&gt;&gt;) att;&lt;br /&gt;             if (destList == null) {&lt;br /&gt;                 destList = new ArrayList&lt;/string,&gt;&lt;/map&gt;&lt;map&gt;&lt;string,&gt;&gt;();&lt;br /&gt;             }&lt;br /&gt;&lt;br /&gt;             Map&lt;string,&gt; subscribeMap = createSubscribeMap(command);&lt;br /&gt;             destList.add(subscribeMap);&lt;br /&gt;             FlexContext.getHttpRequest().getSession().setAttribute(&lt;br /&gt;                     clientId.toString() + endpointId, destList);&lt;br /&gt;         }&lt;br /&gt;     }&lt;br /&gt;     if (next != null) {&lt;br /&gt;         next.invoke(context);&lt;br /&gt;     }&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; private Map&lt;string,&gt; createSubscribeMap(CommandMessage command) {&lt;br /&gt;     Map&lt;string,&gt; subscribeMap = new HashMap&lt;string,&gt;();&lt;br /&gt;&lt;br /&gt;     subscribeMap.put("clientId", command.getClientId());&lt;br /&gt;     subscribeMap.put("endpointId", getEndpoint().getId());&lt;br /&gt;     subscribeMap.put("subtopicString", command&lt;br /&gt;             .getHeader(AsyncMessage.SUBTOPIC_HEADER_NAME));&lt;br /&gt;     subscribeMap.put("selectorExpr", command&lt;br /&gt;             .getHeader(CommandMessage.SELECTOR_HEADER));&lt;br /&gt;     subscribeMap.put("destination", command.getDestination());&lt;br /&gt;     return subscribeMap;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; private String getStringOrNull(Object selectorExpr) {&lt;br /&gt;     String selectorString = null;&lt;br /&gt;     if (selectorExpr != null) {&lt;br /&gt;         selectorString = selectorExpr.toString();&lt;br /&gt;     }&lt;br /&gt;     return selectorString;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; private MessageDestination getMessageDestination(&lt;br /&gt;         Map&lt;string,&gt; subscribeMap) {&lt;br /&gt;     String destId = subscribeMap.get("destination").toString();&lt;br /&gt;     MessageDestination msgDest = null;&lt;br /&gt;     for (Iterator iter = getEndpoint().getMessageBroker().getServices()&lt;br /&gt;             .keySet().iterator(); iter.hasNext();) {&lt;br /&gt;         Service service = (Service) getEndpoint().getMessageBroker()&lt;br /&gt;                 .getServices().get((String) iter.next());&lt;br /&gt;         Destination dest = service.getDestination(destId);&lt;br /&gt;         if (dest != null &amp;amp;&amp;amp; dest instanceof MessageDestination) {&lt;br /&gt;             msgDest = (MessageDestination) dest;&lt;br /&gt;             break;&lt;br /&gt;         }&lt;br /&gt;     }&lt;br /&gt;     return msgDest;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; @SuppressWarnings("unchecked")&lt;br /&gt; protected Object getData(ActionContext context) {&lt;br /&gt;     // get the in message to inspect the headers&lt;br /&gt;     MessageBody request = context.getRequestMessageBody();&lt;br /&gt;&lt;br /&gt;     Object data = request.getData();&lt;br /&gt;     if (data instanceof List) {&lt;br /&gt;         data = ((List) data).get(0);&lt;br /&gt;     } else if (data.getClass().isArray()) {&lt;br /&gt;         data = Array.get(data, 0);&lt;br /&gt;     }&lt;br /&gt;     return data;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt;}&lt;/string,&gt;&lt;/string,&gt;&lt;/string,&gt;&lt;/string,&gt;&lt;/string,&gt;&lt;/string,&gt;&lt;/map&gt;&lt;/blockquote&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;Oh the services-config.xml&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;&amp;lt;clusters&amp;gt;&amp;lt;br /&amp;gt;        &amp;lt;cluster id="default-cluster" properties="jgroups-tcp.xml" default="false" balancing="false"&amp;gt;&amp;lt;br /&amp;gt;    &amp;lt;/clusters&amp;gt;&amp;lt;br /&amp;gt;&amp;lt;!-- the above is added so I can use clusters.  Note that I set default="false" because I'm a control freak --&amp;gt;&lt;br /&gt;&amp;lt;channel-definition id="my-polling-amf" class="mx.messaging.channels.AMFChannel"&amp;gt;&lt;br /&gt;         &amp;lt;endpoint url="@AMF_POLLING_END_POINT_BASE@" class="flex.messaging.endpoints.ConfigurableAMFEndpoint"&amp;gt;&lt;br /&gt;         &amp;lt;properties&amp;gt;&lt;br /&gt;             &amp;lt;filter-chain&amp;gt;&lt;br /&gt;                 &amp;lt;filter-class&amp;gt;au.com.truenorth.amf.filter.ClusterMessagingFilter&amp;lt;/filter-class&amp;gt;&lt;br /&gt;             &amp;lt;/filter-chain&amp;gt;&lt;br /&gt;             &amp;lt;polling-enabled&amp;gt;true&amp;lt;/polling-enabled&amp;gt;&lt;br /&gt;             &amp;lt;wait-interval-millis&amp;gt;50000&amp;lt;/wait-interval-millis&amp;gt;&lt;br /&gt;             &amp;lt;polling-interval-seconds&amp;gt;0&amp;lt;/polling-interval-seconds&amp;gt;&lt;br /&gt;             &amp;lt;max-waiting-poll-requests&amp;gt;50&amp;lt;/max-waiting-poll-requests&amp;gt;&lt;br /&gt;             &amp;lt;serialization&amp;gt;&lt;br /&gt;                 &amp;lt;type-marshaller&amp;gt;au.com.truenorth.service.bean.NumberHandlingTypeMarshaller&amp;lt;/type-marshaller&amp;gt;&lt;br /&gt;             &amp;lt;/serialization&amp;gt;&lt;br /&gt;         &amp;lt;/properties&amp;gt;&lt;br /&gt;     &amp;lt;/endpoint&amp;gt;&lt;br /&gt;&amp;lt;!-- This is my channel definition where I set up the long polling and configure the ClusterMessagingFilter --&amp;gt;&lt;br /&gt;&amp;lt;!-- 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. --&amp;gt;&amp;lt;/channel-definition&amp;gt;&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;Well this has been a long one but I hope at least one person finds it helpful.&lt;br /&gt;&lt;/cluster&gt;&lt;/display-name&gt;&lt;/distributable&gt;&lt;/tcpping&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/532350380457944298-1086739549289197355?l=cogimp.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://cogimp.blogspot.com/feeds/1086739549289197355/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://cogimp.blogspot.com/2009/07/hardware-load-balanced-blazeds-cluster.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/1086739549289197355'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/1086739549289197355'/><link rel='alternate' type='text/html' href='http://cogimp.blogspot.com/2009/07/hardware-load-balanced-blazeds-cluster.html' title='Hardware Load Balanced BlazeDS Cluster on Tomcat'/><author><name>Scion Mandate</name><uri>http://www.blogger.com/profile/12674405150380723269</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://2.bp.blogspot.com/_XLDCiZjHHWY/SWVovcbR5NI/AAAAAAAAAAM/Hz6N7P379jQ/S220/myUglyMug.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-532350380457944298.post-5784482753719114585</id><published>2009-01-18T18:38:00.000-08:00</published><updated>2009-01-18T19:04:19.954-08:00</updated><title type='text'>FOP Positive Descender Underline Bug</title><content type='html'>Well here is my cowardly effort at giving back to the open source community.  But first!  What is the open source community?&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Nerds:&lt;/span&gt;&lt;br /&gt;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: &lt;a href="http://en.wikipedia.org/wiki/Open_source_software"&gt;open source [wikipedia]&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;The Bug:&lt;/span&gt;&lt;br /&gt;The bug I have solved manifests itself in a bit of software called &lt;a href="http://xmlgraphics.apache.org/fop/"&gt;Apache FOP&lt;/a&gt;.  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.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Suspect Math:&lt;/span&gt;&lt;br /&gt;The cause is a bit of suspect math in the underline&lt;br /&gt;&lt;blockquote&gt;public abstract class AbstractPathOrientedRenderer extends PrintRenderer {&lt;br /&gt;...&lt;br /&gt;protected void renderTextDecoration(FontMetrics fm, int fontsize, InlineArea inline,&lt;br /&gt;                    int baseline, int startx) {&lt;br /&gt;        boolean hasTextDeco = inline.hasUnderline()&lt;br /&gt;                || inline.hasOverline()&lt;br /&gt;                || inline.hasLineThrough();&lt;br /&gt;        if (hasTextDeco) {&lt;br /&gt;            endTextObject();&lt;br /&gt;            &lt;span style="font-weight: bold;"&gt;float descender = fm.getDescender(fontsize) / 1000f;&lt;/span&gt;&lt;br /&gt;            float capHeight = fm.getCapHeight(fontsize) / 1000f;&lt;br /&gt;           &lt;span style="font-weight: bold;"&gt; float halfLineWidth = descender / -8f / 2f;&lt;/span&gt;&lt;br /&gt;            float endx = (startx + inline.getIPD()) / 1000f;&lt;br /&gt;            if (inline.hasUnderline()) {&lt;br /&gt;                Color ct = (Color) inline.getTrait(Trait.UNDERLINE_COLOR);&lt;br /&gt;                &lt;span style="font-weight: bold;"&gt;float y = baseline - descender / 2f;&lt;/span&gt;&lt;br /&gt;                drawBorderLine(startx / 1000f, (y - halfLineWidth) / 1000f,&lt;br /&gt;                        endx, (y + halfLineWidth) / 1000f,&lt;br /&gt;                        true, true, Constants.EN_SOLID, ct);&lt;br /&gt;            }...&lt;/blockquote&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;My Solution:&lt;/span&gt;&lt;br /&gt;Here you see what I changed:&lt;br /&gt;&lt;blockquote&gt;protected void renderTextDecoration(FontMetrics fm, int fontsize, InlineArea inline,&lt;br /&gt;                    int baseline, int startx) {&lt;br /&gt;        boolean hasTextDeco = inline.hasUnderline()&lt;br /&gt;                || inline.hasOverline()&lt;br /&gt;                || inline.hasLineThrough();&lt;br /&gt;        if (hasTextDeco) {&lt;br /&gt;            endTextObject();&lt;br /&gt;            &lt;span style="font-weight: bold;"&gt;float descender = Math.abs(fm.getDescender(fontsize) / 1000f);&lt;/span&gt;&lt;br /&gt;            float capHeight = fm.getCapHeight(fontsize) / 1000f;&lt;br /&gt;            &lt;span style="font-weight: bold;"&gt;float halfLineWidth = descender / 16f;&lt;/span&gt;&lt;br /&gt;            float endx = (startx + inline.getIPD()) / 1000f;&lt;br /&gt;            if (inline.hasUnderline()) {&lt;br /&gt;                Color ct = (Color) inline.getTrait(Trait.UNDERLINE_COLOR);&lt;br /&gt;                &lt;span style="font-weight: bold;"&gt;float y = baseline + descender / 2f;&lt;/span&gt;&lt;br /&gt;                drawBorderLine(startx / 1000f, (y - halfLineWidth) / 1000f,&lt;br /&gt;                        endx, (y + halfLineWidth) / 1000f,&lt;br /&gt;                        true, true, Constants.EN_SOLID, ct);&lt;br /&gt;            }...&lt;/blockquote&gt;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.&lt;br /&gt;The short of it is that the code now works for both negative and positive value descenders.  The world rejoice!&lt;br /&gt;&lt;br /&gt;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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/532350380457944298-5784482753719114585?l=cogimp.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://cogimp.blogspot.com/feeds/5784482753719114585/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://cogimp.blogspot.com/2009/01/fop-positive-descender-underline-bug.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/5784482753719114585'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/5784482753719114585'/><link rel='alternate' type='text/html' href='http://cogimp.blogspot.com/2009/01/fop-positive-descender-underline-bug.html' title='FOP Positive Descender Underline Bug'/><author><name>Scion Mandate</name><uri>http://www.blogger.com/profile/12674405150380723269</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://2.bp.blogspot.com/_XLDCiZjHHWY/SWVovcbR5NI/AAAAAAAAAAM/Hz6N7P379jQ/S220/myUglyMug.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-532350380457944298.post-8947570936648158754</id><published>2009-01-14T21:16:00.000-08:00</published><updated>2009-01-14T21:52:30.071-08:00</updated><title type='text'>Woodwork, My Bedside Table</title><content type='html'>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.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Project 1: My Computer Desk&lt;/span&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Project 2: My Bedside Table&lt;/span&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;The design:&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_XLDCiZjHHWY/SW7JOxJxIFI/AAAAAAAAAAo/NE0yDaNlHxI/s1600-h/bedside3d.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 286px; height: 320px;" src="http://1.bp.blogspot.com/_XLDCiZjHHWY/SW7JOxJxIFI/AAAAAAAAAAo/NE0yDaNlHxI/s320/bedside3d.JPG" alt="" id="BLOGGER_PHOTO_ID_5291387867756568658" border="0" /&gt;&lt;/a&gt;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.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;What I've done:&lt;/span&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_XLDCiZjHHWY/SW7KQnMZWpI/AAAAAAAAAA4/caxR3IoDri4/s1600-h/bedside.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 311px;" src="http://4.bp.blogspot.com/_XLDCiZjHHWY/SW7KQnMZWpI/AAAAAAAAAA4/caxR3IoDri4/s320/bedside.JPG" alt="" id="BLOGGER_PHOTO_ID_5291388998954605202" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Difficulties:&lt;/span&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Next:&lt;/span&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Another project?&lt;/span&gt;&lt;br /&gt;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?&lt;br /&gt;&lt;br /&gt;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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/532350380457944298-8947570936648158754?l=cogimp.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://cogimp.blogspot.com/feeds/8947570936648158754/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://cogimp.blogspot.com/2009/01/woodwork-my-bedside-table.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/8947570936648158754'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/8947570936648158754'/><link rel='alternate' type='text/html' href='http://cogimp.blogspot.com/2009/01/woodwork-my-bedside-table.html' title='Woodwork, My Bedside Table'/><author><name>Scion Mandate</name><uri>http://www.blogger.com/profile/12674405150380723269</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://2.bp.blogspot.com/_XLDCiZjHHWY/SWVovcbR5NI/AAAAAAAAAAM/Hz6N7P379jQ/S220/myUglyMug.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_XLDCiZjHHWY/SW7JOxJxIFI/AAAAAAAAAAo/NE0yDaNlHxI/s72-c/bedside3d.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-532350380457944298.post-5431533721676322766</id><published>2009-01-07T22:20:00.000-08:00</published><updated>2009-01-07T22:42:29.707-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='money crap comodity exchange time'/><title type='text'>Three Exchangable Comodities</title><content type='html'>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.&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Time&lt;/li&gt;&lt;li&gt;Money&lt;/li&gt;&lt;li&gt;Experience / Knowledge&lt;/li&gt;&lt;/ol&gt;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.&lt;span style="font-weight: bold;"&gt;&lt;br /&gt;&lt;br /&gt;Time:&lt;/span&gt;&lt;br /&gt;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...&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Money:&lt;/span&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Experience / Knowledge:&lt;/span&gt;&lt;br /&gt;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.)&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;Service my car:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Time: 3-4 hours&lt;/li&gt;&lt;li&gt;Money: $100&lt;/li&gt;&lt;li&gt;Experience: not much.&lt;/li&gt;&lt;/ol&gt;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.&lt;br /&gt;To reduce time I could pay a mechanic or just know how to do everything without need to constantly look up my books.&lt;br /&gt;To reduce my experience required I could pay a mechanic or spend longer learning everything.&lt;br /&gt;&lt;br /&gt;I guess you get the picture.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/532350380457944298-5431533721676322766?l=cogimp.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://cogimp.blogspot.com/feeds/5431533721676322766/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://cogimp.blogspot.com/2009/01/three-exchangable-comodities.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/5431533721676322766'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/5431533721676322766'/><link rel='alternate' type='text/html' href='http://cogimp.blogspot.com/2009/01/three-exchangable-comodities.html' title='Three Exchangable Comodities'/><author><name>Scion Mandate</name><uri>http://www.blogger.com/profile/12674405150380723269</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://2.bp.blogspot.com/_XLDCiZjHHWY/SWVovcbR5NI/AAAAAAAAAAM/Hz6N7P379jQ/S220/myUglyMug.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-532350380457944298.post-4536023237516585636</id><published>2009-01-07T18:26:00.000-08:00</published><updated>2009-01-07T18:43:35.394-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='imperialism fart computers java flex cars philosophy'/><title type='text'>Cognitive Imperial, I</title><content type='html'>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.&lt;br /&gt;&lt;br /&gt;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).&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;So with no more time wasted on introductions I will present my first mind expanding thought:&lt;br /&gt;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?&lt;br /&gt;&lt;br /&gt;That aside I'll be back to discuss my current wood-working project: a bedside table!  Hooray for excitement.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/532350380457944298-4536023237516585636?l=cogimp.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://cogimp.blogspot.com/feeds/4536023237516585636/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://cogimp.blogspot.com/2009/01/cognitive-imperial-i.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/4536023237516585636'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/532350380457944298/posts/default/4536023237516585636'/><link rel='alternate' type='text/html' href='http://cogimp.blogspot.com/2009/01/cognitive-imperial-i.html' title='Cognitive Imperial, I'/><author><name>Scion Mandate</name><uri>http://www.blogger.com/profile/12674405150380723269</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='26' height='32' src='http://2.bp.blogspot.com/_XLDCiZjHHWY/SWVovcbR5NI/AAAAAAAAAAM/Hz6N7P379jQ/S220/myUglyMug.jpg'/></author><thr:total>0</thr:total></entry></feed>
